diff options
author | Tyler Gunn <tgunn@google.com> | 2014-09-12 22:16:17 -0700 |
---|---|---|
committer | Tyler Gunn <tgunn@google.com> | 2014-09-12 22:16:17 -0700 |
commit | ef9f6f957d897ea0ed82114185b8fa3fefd4917b (patch) | |
tree | 4aff42f3d54f4454e598f27829c4983ba808afa7 /telecomm/java/android/telecom/ConnectionService.java | |
parent | 4b5c2d3cfc8aec4ab90097734a3556a0d0c0e68d (diff) |
Renaming Telecomm to Telecom.
- Changing package from android.telecomm to android.telecom
- Changing package from com.android.telecomm to
com.android.server.telecomm.
- Renaming TelecommManager to TelecomManager.
Bug: 17364651
Change-Id: I192cb5d189f55db012ea72ee82ccc5aedbc21638
Diffstat (limited to 'telecomm/java/android/telecom/ConnectionService.java')
-rw-r--r-- | telecomm/java/android/telecom/ConnectionService.java | 973 |
1 files changed, 973 insertions, 0 deletions
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java new file mode 100644 index 000000000000..cc80e2260d2b --- /dev/null +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -0,0 +1,973 @@ +/* + * 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.SdkConstant; +import android.app.Service; +import android.content.ComponentName; +import android.content.Intent; +import android.net.Uri; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.telephony.DisconnectCause; + +import com.android.internal.os.SomeArgs; +import com.android.internal.telecom.IConnectionService; +import com.android.internal.telecom.IConnectionServiceAdapter; +import com.android.internal.telecom.RemoteServiceCallback; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * A {@link android.app.Service} that provides telephone connections to processes running on an + * Android device. + */ +public abstract class ConnectionService extends Service { + /** + * The {@link Intent} that must be declared as handled by the service. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = "android.telecom.ConnectionService"; + + // Flag controlling whether PII is emitted into the logs + private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG); + + private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1; + private static final int MSG_CREATE_CONNECTION = 2; + private static final int MSG_ABORT = 3; + private static final int MSG_ANSWER = 4; + private static final int MSG_REJECT = 5; + private static final int MSG_DISCONNECT = 6; + private static final int MSG_HOLD = 7; + private static final int MSG_UNHOLD = 8; + private static final int MSG_ON_AUDIO_STATE_CHANGED = 9; + private static final int MSG_PLAY_DTMF_TONE = 10; + private static final int MSG_STOP_DTMF_TONE = 11; + private static final int MSG_CONFERENCE = 12; + private static final int MSG_SPLIT_FROM_CONFERENCE = 13; + private static final int MSG_ON_POST_DIAL_CONTINUE = 14; + private static final int MSG_REMOVE_CONNECTION_SERVICE_ADAPTER = 16; + private static final int MSG_ANSWER_VIDEO = 17; + private static final int MSG_MERGE_CONFERENCE = 18; + private static final int MSG_SWAP_CONFERENCE = 19; + + private static Connection sNullConnection; + + private final Map<String, Connection> mConnectionById = new HashMap<>(); + private final Map<Connection, String> mIdByConnection = new HashMap<>(); + private final Map<String, Conference> mConferenceById = new HashMap<>(); + private final Map<Conference, String> mIdByConference = new HashMap<>(); + private final RemoteConnectionManager mRemoteConnectionManager = + new RemoteConnectionManager(this); + private final List<Runnable> mPreInitializationConnectionRequests = new ArrayList<>(); + private final ConnectionServiceAdapter mAdapter = new ConnectionServiceAdapter(); + + private boolean mAreAccountsInitialized = false; + private Conference sNullConference; + + private final IBinder mBinder = new IConnectionService.Stub() { + @Override + public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) { + mHandler.obtainMessage(MSG_ADD_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget(); + } + + public void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter) { + mHandler.obtainMessage(MSG_REMOVE_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget(); + } + + @Override + public void createConnection( + PhoneAccountHandle connectionManagerPhoneAccount, + String id, + ConnectionRequest request, + boolean isIncoming) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = connectionManagerPhoneAccount; + args.arg2 = id; + args.arg3 = request; + args.argi1 = isIncoming ? 1 : 0; + mHandler.obtainMessage(MSG_CREATE_CONNECTION, args).sendToTarget(); + } + + @Override + public void abort(String callId) { + mHandler.obtainMessage(MSG_ABORT, callId).sendToTarget(); + } + + @Override + /** @hide */ + public void answerVideo(String callId, int videoState) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.argi1 = videoState; + mHandler.obtainMessage(MSG_ANSWER_VIDEO, args).sendToTarget(); + } + + @Override + public void answer(String callId) { + mHandler.obtainMessage(MSG_ANSWER, callId).sendToTarget(); + } + + @Override + public void reject(String callId) { + mHandler.obtainMessage(MSG_REJECT, callId).sendToTarget(); + } + + @Override + public void disconnect(String callId) { + mHandler.obtainMessage(MSG_DISCONNECT, callId).sendToTarget(); + } + + @Override + public void hold(String callId) { + mHandler.obtainMessage(MSG_HOLD, callId).sendToTarget(); + } + + @Override + public void unhold(String callId) { + mHandler.obtainMessage(MSG_UNHOLD, callId).sendToTarget(); + } + + @Override + public void onAudioStateChanged(String callId, AudioState audioState) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = audioState; + mHandler.obtainMessage(MSG_ON_AUDIO_STATE_CHANGED, args).sendToTarget(); + } + + @Override + public void playDtmfTone(String callId, char digit) { + mHandler.obtainMessage(MSG_PLAY_DTMF_TONE, digit, 0, callId).sendToTarget(); + } + + @Override + public void stopDtmfTone(String callId) { + mHandler.obtainMessage(MSG_STOP_DTMF_TONE, callId).sendToTarget(); + } + + @Override + public void conference(String callId1, String callId2) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId1; + args.arg2 = callId2; + mHandler.obtainMessage(MSG_CONFERENCE, args).sendToTarget(); + } + + @Override + public void splitFromConference(String callId) { + mHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, callId).sendToTarget(); + } + + @Override + public void mergeConference(String callId) { + mHandler.obtainMessage(MSG_MERGE_CONFERENCE, callId).sendToTarget(); + } + + @Override + public void swapConference(String callId) { + mHandler.obtainMessage(MSG_SWAP_CONFERENCE, callId).sendToTarget(); + } + + @Override + public void onPostDialContinue(String callId, boolean proceed) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.argi1 = proceed ? 1 : 0; + mHandler.obtainMessage(MSG_ON_POST_DIAL_CONTINUE, args).sendToTarget(); + } + }; + + private final Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_ADD_CONNECTION_SERVICE_ADAPTER: + mAdapter.addAdapter((IConnectionServiceAdapter) msg.obj); + onAdapterAttached(); + break; + case MSG_REMOVE_CONNECTION_SERVICE_ADAPTER: + mAdapter.removeAdapter((IConnectionServiceAdapter) msg.obj); + break; + case MSG_CREATE_CONNECTION: { + SomeArgs args = (SomeArgs) msg.obj; + try { + final PhoneAccountHandle connectionManagerPhoneAccount = + (PhoneAccountHandle) args.arg1; + final String id = (String) args.arg2; + final ConnectionRequest request = (ConnectionRequest) args.arg3; + final boolean isIncoming = args.argi1 == 1; + if (!mAreAccountsInitialized) { + Log.d(this, "Enqueueing pre-init request %s", id); + mPreInitializationConnectionRequests.add(new Runnable() { + @Override + public void run() { + createConnection( + connectionManagerPhoneAccount, + id, + request, + isIncoming); + } + }); + } else { + createConnection( + connectionManagerPhoneAccount, + id, + request, + isIncoming); + } + } finally { + args.recycle(); + } + break; + } + case MSG_ABORT: + abort((String) msg.obj); + break; + case MSG_ANSWER: + answer((String) msg.obj); + break; + case MSG_ANSWER_VIDEO: { + SomeArgs args = (SomeArgs) msg.obj; + try { + String callId = (String) args.arg1; + int videoState = args.argi1; + answerVideo(callId, videoState); + } finally { + args.recycle(); + } + break; + } + case MSG_REJECT: + reject((String) msg.obj); + break; + case MSG_DISCONNECT: + disconnect((String) msg.obj); + break; + case MSG_HOLD: + hold((String) msg.obj); + break; + case MSG_UNHOLD: + unhold((String) msg.obj); + break; + case MSG_ON_AUDIO_STATE_CHANGED: { + SomeArgs args = (SomeArgs) msg.obj; + try { + String callId = (String) args.arg1; + AudioState audioState = (AudioState) args.arg2; + onAudioStateChanged(callId, audioState); + } finally { + args.recycle(); + } + break; + } + case MSG_PLAY_DTMF_TONE: + playDtmfTone((String) msg.obj, (char) msg.arg1); + break; + case MSG_STOP_DTMF_TONE: + stopDtmfTone((String) msg.obj); + break; + case MSG_CONFERENCE: { + SomeArgs args = (SomeArgs) msg.obj; + try { + String callId1 = (String) args.arg1; + String callId2 = (String) args.arg2; + conference(callId1, callId2); + } finally { + args.recycle(); + } + break; + } + case MSG_SPLIT_FROM_CONFERENCE: + splitFromConference((String) msg.obj); + break; + case MSG_MERGE_CONFERENCE: + mergeConference((String) msg.obj); + break; + case MSG_SWAP_CONFERENCE: + swapConference((String) msg.obj); + break; + case MSG_ON_POST_DIAL_CONTINUE: { + SomeArgs args = (SomeArgs) msg.obj; + try { + String callId = (String) args.arg1; + boolean proceed = (args.argi1 == 1); + onPostDialContinue(callId, proceed); + } finally { + args.recycle(); + } + break; + } + default: + break; + } + } + }; + + private final Conference.Listener mConferenceListener = new Conference.Listener() { + @Override + public void onStateChanged(Conference conference, int oldState, int newState) { + String id = mIdByConference.get(conference); + switch (newState) { + case Connection.STATE_ACTIVE: + mAdapter.setActive(id); + break; + case Connection.STATE_HOLDING: + mAdapter.setOnHold(id); + break; + case Connection.STATE_DISCONNECTED: + // handled by onDisconnected + break; + } + } + + @Override + public void onDisconnected(Conference conference, int cause, String message) { + String id = mIdByConference.get(conference); + mAdapter.setDisconnected(id, cause, message); + } + + @Override + public void onConnectionAdded(Conference conference, Connection connection) { + } + + @Override + public void onConnectionRemoved(Conference conference, Connection connection) { + } + + @Override + public void onDestroyed(Conference conference) { + removeConference(conference); + } + + @Override + public void onCapabilitiesChanged(Conference conference, int capabilities) { + String id = mIdByConference.get(conference); + Log.d(this, "call capabilities: conference: %s", + PhoneCapabilities.toString(capabilities)); + mAdapter.setCallCapabilities(id, capabilities); + } + }; + + private final Connection.Listener mConnectionListener = new Connection.Listener() { + @Override + public void onStateChanged(Connection c, int state) { + String id = mIdByConnection.get(c); + Log.d(this, "Adapter set state %s %s", id, Connection.stateToString(state)); + switch (state) { + case Connection.STATE_ACTIVE: + mAdapter.setActive(id); + break; + case Connection.STATE_DIALING: + mAdapter.setDialing(id); + break; + case Connection.STATE_DISCONNECTED: + // Handled in onDisconnected() + break; + case Connection.STATE_HOLDING: + mAdapter.setOnHold(id); + break; + case Connection.STATE_NEW: + // Nothing to tell Telecom + break; + case Connection.STATE_RINGING: + mAdapter.setRinging(id); + break; + } + } + + @Override + public void onDisconnected(Connection c, int cause, String message) { + String id = mIdByConnection.get(c); + Log.d(this, "Adapter set disconnected %d %s", cause, message); + mAdapter.setDisconnected(id, cause, message); + } + + @Override + public void onVideoStateChanged(Connection c, int videoState) { + String id = mIdByConnection.get(c); + Log.d(this, "Adapter set video state %d", videoState); + mAdapter.setVideoState(id, videoState); + } + + @Override + public void onAddressChanged(Connection c, Uri address, int presentation) { + String id = mIdByConnection.get(c); + mAdapter.setAddress(id, address, presentation); + } + + @Override + public void onCallerDisplayNameChanged( + Connection c, String callerDisplayName, int presentation) { + String id = mIdByConnection.get(c); + mAdapter.setCallerDisplayName(id, callerDisplayName, presentation); + } + + @Override + public void onDestroyed(Connection c) { + removeConnection(c); + } + + @Override + public void onPostDialWait(Connection c, String remaining) { + String id = mIdByConnection.get(c); + Log.d(this, "Adapter onPostDialWait %s, %s", c, remaining); + mAdapter.onPostDialWait(id, remaining); + } + + @Override + public void onRingbackRequested(Connection c, boolean ringback) { + String id = mIdByConnection.get(c); + Log.d(this, "Adapter onRingback %b", ringback); + mAdapter.setRingbackRequested(id, ringback); + } + + @Override + public void onCallCapabilitiesChanged(Connection c, int capabilities) { + String id = mIdByConnection.get(c); + Log.d(this, "capabilities: parcelableconnection: %s", + PhoneCapabilities.toString(capabilities)); + mAdapter.setCallCapabilities(id, capabilities); + } + + @Override + public void onVideoProviderChanged(Connection c, Connection.VideoProvider videoProvider) { + String id = mIdByConnection.get(c); + mAdapter.setVideoProvider(id, videoProvider); + } + + @Override + public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) { + String id = mIdByConnection.get(c); + mAdapter.setIsVoipAudioMode(id, isVoip); + } + + @Override + public void onStatusHintsChanged(Connection c, StatusHints statusHints) { + String id = mIdByConnection.get(c); + mAdapter.setStatusHints(id, statusHints); + } + + @Override + public void onConferenceableConnectionsChanged( + Connection connection, List<Connection> conferenceableConnections) { + mAdapter.setConferenceableConnections( + mIdByConnection.get(connection), + createConnectionIdList(conferenceableConnections)); + } + + @Override + public void onConferenceChanged(Connection connection, Conference conference) { + String id = mIdByConnection.get(connection); + if (id != null) { + String conferenceId = null; + if (conference != null) { + conferenceId = mIdByConference.get(conference); + } + mAdapter.setIsConferenced(id, conferenceId); + } + } + }; + + /** {@inheritDoc} */ + @Override + public final IBinder onBind(Intent intent) { + return mBinder; + } + + /** {@inheritDoc} */ + @Override + public boolean onUnbind(Intent intent) { + endAllConnections(); + return super.onUnbind(intent); + } + + /** + * This can be used by telecom to either create a new outgoing call or attach to an existing + * incoming call. In either case, telecom will cycle through a set of services and call + * createConnection util a connection service cancels the process or completes it successfully. + */ + private void createConnection( + final PhoneAccountHandle callManagerAccount, + final String callId, + final ConnectionRequest request, + boolean isIncoming) { + Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " + + "isIncoming: %b", callManagerAccount, callId, request, isIncoming); + + Connection connection = isIncoming + ? onCreateIncomingConnection(callManagerAccount, request) + : onCreateOutgoingConnection(callManagerAccount, request); + Log.d(this, "createConnection, connection: %s", connection); + if (connection == null) { + connection = Connection.createFailedConnection(DisconnectCause.OUTGOING_FAILURE, null); + } + + if (connection.getState() != Connection.STATE_DISCONNECTED) { + addConnection(callId, connection); + } + + Uri address = connection.getAddress(); + String number = address == null ? "null" : address.getSchemeSpecificPart(); + Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s", + Connection.toLogSafePhoneNumber(number), + Connection.stateToString(connection.getState()), + PhoneCapabilities.toString(connection.getCallCapabilities())); + + Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId); + mAdapter.handleCreateConnectionComplete( + callId, + request, + new ParcelableConnection( + request.getAccountHandle(), + connection.getState(), + connection.getCallCapabilities(), + connection.getAddress(), + connection.getAddressPresentation(), + connection.getCallerDisplayName(), + connection.getCallerDisplayNamePresentation(), + connection.getVideoProvider() == null ? + null : connection.getVideoProvider().getInterface(), + connection.getVideoState(), + connection.isRingbackRequested(), + connection.getAudioModeIsVoip(), + connection.getStatusHints(), + connection.getDisconnectCause(), + connection.getDisconnectMessage(), + createConnectionIdList(connection.getConferenceableConnections()))); + } + + private void abort(String callId) { + Log.d(this, "abort %s", callId); + findConnectionForAction(callId, "abort").onAbort(); + } + + private void answerVideo(String callId, int videoState) { + Log.d(this, "answerVideo %s", callId); + findConnectionForAction(callId, "answer").onAnswer(videoState); + } + + private void answer(String callId) { + Log.d(this, "answer %s", callId); + findConnectionForAction(callId, "answer").onAnswer(); + } + + private void reject(String callId) { + Log.d(this, "reject %s", callId); + findConnectionForAction(callId, "reject").onReject(); + } + + private void disconnect(String callId) { + Log.d(this, "disconnect %s", callId); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "disconnect").onDisconnect(); + } else { + findConferenceForAction(callId, "disconnect").onDisconnect(); + } + } + + private void hold(String callId) { + Log.d(this, "hold %s", callId); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "hold").onHold(); + } else { + findConferenceForAction(callId, "hold").onHold(); + } + } + + private void unhold(String callId) { + Log.d(this, "unhold %s", callId); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "unhold").onUnhold(); + } else { + findConferenceForAction(callId, "unhold").onUnhold(); + } + } + + private void onAudioStateChanged(String callId, AudioState audioState) { + Log.d(this, "onAudioStateChanged %s %s", callId, audioState); + findConnectionForAction(callId, "onAudioStateChanged").setAudioState(audioState); + } + + private void playDtmfTone(String callId, char digit) { + Log.d(this, "playDtmfTone %s %c", callId, digit); + findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit); + } + + private void stopDtmfTone(String callId) { + Log.d(this, "stopDtmfTone %s", callId); + findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone(); + } + + private void conference(String callId1, String callId2) { + Log.d(this, "conference %s, %s", callId1, callId2); + + Connection connection1 = findConnectionForAction(callId1, "conference"); + if (connection1 == getNullConnection()) { + Log.w(this, "Connection1 missing in conference request %s.", callId1); + return; + } + + Connection connection2 = findConnectionForAction(callId2, "conference"); + if (connection2 == getNullConnection()) { + Log.w(this, "Connection2 missing in conference request %s.", callId2); + return; + } + + onConference(connection1, connection2); + } + + private void splitFromConference(String callId) { + Log.d(this, "splitFromConference(%s)", callId); + + Connection connection = findConnectionForAction(callId, "splitFromConference"); + if (connection == getNullConnection()) { + Log.w(this, "Connection missing in conference request %s.", callId); + return; + } + + Conference conference = connection.getConference(); + if (conference != null) { + conference.onSeparate(connection); + } + } + + private void mergeConference(String callId) { + Log.d(this, "mergeConference(%s)", callId); + Conference conference = findConferenceForAction(callId, "mergeConference"); + if (conference != null) { + conference.onMerge(); + } + } + + private void swapConference(String callId) { + Log.d(this, "swapConference(%s)", callId); + Conference conference = findConferenceForAction(callId, "swapConference"); + if (conference != null) { + conference.onSwap(); + } + } + + private void onPostDialContinue(String callId, boolean proceed) { + Log.d(this, "onPostDialContinue(%s)", callId); + findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed); + } + + private void onAdapterAttached() { + if (mAreAccountsInitialized) { + // No need to query again if we already did it. + return; + } + + mAdapter.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() { + @Override + public void onResult( + final List<ComponentName> componentNames, + final List<IBinder> services) { + mHandler.post(new Runnable() { + @Override + public void run() { + for (int i = 0; i < componentNames.size() && i < services.size(); i++) { + mRemoteConnectionManager.addConnectionService( + componentNames.get(i), + IConnectionService.Stub.asInterface(services.get(i))); + } + onAccountsInitialized(); + Log.d(this, "remote connection services found: " + services); + } + }); + } + + @Override + public void onError() { + mHandler.post(new Runnable() { + @Override + public void run() { + mAreAccountsInitialized = true; + } + }); + } + }); + } + + /** + * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an + * incoming request. This is used to attach to existing incoming calls. + * + * @param connectionManagerPhoneAccount See description at + * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. + * @param request Details about the incoming call. + * @return The {@code Connection} object to satisfy this call, or {@code null} to + * not handle the call. + */ + public final RemoteConnection createRemoteIncomingConnection( + PhoneAccountHandle connectionManagerPhoneAccount, + ConnectionRequest request) { + return mRemoteConnectionManager.createRemoteConnection( + connectionManagerPhoneAccount, request, true); + } + + /** + * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an + * outgoing request. This is used to initiate new outgoing calls. + * + * @param connectionManagerPhoneAccount See description at + * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. + * @param request Details about the incoming call. + * @return The {@code Connection} object to satisfy this call, or {@code null} to + * not handle the call. + */ + public final RemoteConnection createRemoteOutgoingConnection( + PhoneAccountHandle connectionManagerPhoneAccount, + ConnectionRequest request) { + return mRemoteConnectionManager.createRemoteConnection( + connectionManagerPhoneAccount, request, false); + } + + /** + * Adds two {@code RemoteConnection}s to some {@code RemoteConference}. + */ + public final void conferenceRemoteConnections( + RemoteConnection a, + RemoteConnection b) { + mRemoteConnectionManager.conferenceRemoteConnections(a, b); + } + + /** + * Adds a new conference call. When a conference call is created either as a result of an + * explicit request via {@link #onConference} or otherwise, the connection service should supply + * an instance of {@link Conference} by invoking this method. A conference call provided by this + * method will persist until {@link Conference#destroy} is invoked on the conference instance. + * + * @param conference The new conference object. + */ + public final void addConference(Conference conference) { + String id = addConferenceInternal(conference); + if (id != null) { + List<String> connectionIds = new ArrayList<>(2); + for (Connection connection : conference.getConnections()) { + if (mIdByConnection.containsKey(connection)) { + connectionIds.add(mIdByConnection.get(connection)); + } + } + ParcelableConference parcelableConference = new ParcelableConference( + conference.getPhoneAccountHandle(), + conference.getState(), + conference.getCapabilities(), + connectionIds); + mAdapter.addConferenceCall(id, parcelableConference); + + // Go through any child calls and set the parent. + for (Connection connection : conference.getConnections()) { + String connectionId = mIdByConnection.get(connection); + if (connectionId != null) { + mAdapter.setIsConferenced(connectionId, id); + } + } + } + } + + /** + * Returns all the active {@code Connection}s for which this {@code ConnectionService} + * has taken responsibility. + * + * @return A collection of {@code Connection}s created by this {@code ConnectionService}. + */ + public final Collection<Connection> getAllConnections() { + return mConnectionById.values(); + } + + /** + * Create a {@code Connection} given an incoming request. This is used to attach to existing + * incoming calls. + * + * @param connectionManagerPhoneAccount See description at + * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. + * @param request Details about the incoming call. + * @return The {@code Connection} object to satisfy this call, or {@code null} to + * not handle the call. + */ + public Connection onCreateIncomingConnection( + PhoneAccountHandle connectionManagerPhoneAccount, + ConnectionRequest request) { + return null; + } + + /** + * Create a {@code Connection} given an outgoing request. This is used to initiate new + * outgoing calls. + * + * @param connectionManagerPhoneAccount The connection manager account to use for managing + * this call. + * <p> + * If this parameter is not {@code null}, it means that this {@code ConnectionService} + * has registered one or more {@code PhoneAccount}s having + * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain + * one of these {@code PhoneAccount}s, while the {@code request} will contain another + * (usually but not always distinct) {@code PhoneAccount} to be used for actually + * making the connection. + * <p> + * If this parameter is {@code null}, it means that this {@code ConnectionService} is + * being asked to make a direct connection. The + * {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be + * a {@code PhoneAccount} registered by this {@code ConnectionService} to use for + * making the connection. + * @param request Details about the outgoing call. + * @return The {@code Connection} object to satisfy this call, or the result of an invocation + * of {@link Connection#createFailedConnection(int, String)} to not handle the call. + */ + public Connection onCreateOutgoingConnection( + PhoneAccountHandle connectionManagerPhoneAccount, + ConnectionRequest request) { + return null; + } + + /** + * Conference two specified connections. Invoked when the user has made a request to merge the + * specified connections into a conference call. In response, the connection service should + * create an instance of {@link Conference} and pass it into {@link #addConference}. + * + * @param connection1 A connection to merge into a conference call. + * @param connection2 A connection to merge into a conference call. + */ + public void onConference(Connection connection1, Connection connection2) {} + + public void onRemoteConferenceAdded(RemoteConference conference) {} + + /** + * @hide + */ + public boolean containsConference(Conference conference) { + return mIdByConference.containsKey(conference); + } + + /** {@hide} */ + void addRemoteConference(RemoteConference remoteConference) { + onRemoteConferenceAdded(remoteConference); + } + + private void onAccountsInitialized() { + mAreAccountsInitialized = true; + for (Runnable r : mPreInitializationConnectionRequests) { + r.run(); + } + mPreInitializationConnectionRequests.clear(); + } + + private void addConnection(String callId, Connection connection) { + mConnectionById.put(callId, connection); + mIdByConnection.put(connection, callId); + connection.addConnectionListener(mConnectionListener); + connection.setConnectionService(this); + } + + private void removeConnection(Connection connection) { + String id = mIdByConnection.get(connection); + connection.unsetConnectionService(this); + connection.removeConnectionListener(mConnectionListener); + mConnectionById.remove(mIdByConnection.get(connection)); + mIdByConnection.remove(connection); + mAdapter.removeCall(id); + } + + private String addConferenceInternal(Conference conference) { + if (mIdByConference.containsKey(conference)) { + Log.w(this, "Re-adding an existing conference: %s.", conference); + } else if (conference != null) { + String id = UUID.randomUUID().toString(); + mConferenceById.put(id, conference); + mIdByConference.put(conference, id); + conference.addListener(mConferenceListener); + return id; + } + + return null; + } + + private void removeConference(Conference conference) { + if (mIdByConference.containsKey(conference)) { + conference.removeListener(mConferenceListener); + + String id = mIdByConference.get(conference); + mConferenceById.remove(id); + mIdByConference.remove(conference); + mAdapter.removeCall(id); + } + } + + private Connection findConnectionForAction(String callId, String action) { + if (mConnectionById.containsKey(callId)) { + return mConnectionById.get(callId); + } + Log.w(this, "%s - Cannot find Connection %s", action, callId); + return getNullConnection(); + } + + static synchronized Connection getNullConnection() { + if (sNullConnection == null) { + sNullConnection = new Connection() {}; + } + return sNullConnection; + } + + private Conference findConferenceForAction(String conferenceId, String action) { + if (mConferenceById.containsKey(conferenceId)) { + return mConferenceById.get(conferenceId); + } + Log.w(this, "%s - Cannot find conference %s", action, conferenceId); + return getNullConference(); + } + + private List<String> createConnectionIdList(List<Connection> connections) { + List<String> ids = new ArrayList<>(); + for (Connection c : connections) { + if (mIdByConnection.containsKey(c)) { + ids.add(mIdByConnection.get(c)); + } + } + Collections.sort(ids); + return ids; + } + + private Conference getNullConference() { + if (sNullConference == null) { + sNullConference = new Conference(null) {}; + } + return sNullConference; + } + + private void endAllConnections() { + // Unbound from telecomm. We should end all connections and conferences. + for (Connection connection : mIdByConnection.keySet()) { + // only operate on top-level calls. Conference calls will be removed on their own. + if (connection.getConference() == null) { + connection.onDisconnect(); + } + } + for (Conference conference : mIdByConference.keySet()) { + conference.onDisconnect(); + } + } +} |