diff options
Diffstat (limited to 'telecomm/java/android/telecom/ConnectionService.java')
-rw-r--r-- | telecomm/java/android/telecom/ConnectionService.java | 1025 |
1 files changed, 1025 insertions, 0 deletions
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java new file mode 100644 index 0000000..6eee99d --- /dev/null +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -0,0 +1,1025 @@ +/* + * 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.SystemApi; +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 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.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A {@link android.app.Service} that provides telephone connections to processes running on an + * Android device. + * @hide + */ +@SystemApi +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 ConcurrentHashMap<>(); + private final Map<Connection, String> mIdByConnection = new ConcurrentHashMap<>(); + private final Map<String, Conference> mConferenceById = new ConcurrentHashMap<>(); + private final Map<Conference, String> mIdByConference = new ConcurrentHashMap<>(); + 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, + boolean isUnknown) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = connectionManagerPhoneAccount; + args.arg2 = id; + args.arg3 = request; + args.argi1 = isIncoming ? 1 : 0; + args.argi2 = isUnknown ? 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; + final boolean isUnknown = args.argi2 == 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, + isUnknown); + } + }); + } else { + createConnection( + connectionManagerPhoneAccount, + id, + request, + isIncoming, + isUnknown); + } + } 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, DisconnectCause disconnectCause) { + String id = mIdByConference.get(conference); + mAdapter.setDisconnected(id, disconnectCause); + } + + @Override + public void onConnectionAdded(Conference conference, Connection connection) { + } + + @Override + public void onConnectionRemoved(Conference conference, Connection connection) { + } + + @Override + public void onConferenceableConnectionsChanged( + Conference conference, List<Connection> conferenceableConnections) { + mAdapter.setConferenceableConnections( + mIdByConference.get(conference), + createConnectionIdList(conferenceableConnections)); + } + + @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, DisconnectCause disconnectCause) { + String id = mIdByConnection.get(c); + Log.d(this, "Adapter set disconnected %s", disconnectCause); + mAdapter.setDisconnected(id, disconnectCause); + } + + @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, + boolean isUnknown) { + Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " + + "isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request, isIncoming, + isUnknown); + + Connection connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request) + : isIncoming ? onCreateIncomingConnection(callManagerAccount, request) + : onCreateOutgoingConnection(callManagerAccount, request); + Log.d(this, "createConnection, connection: %s", connection); + if (connection == null) { + connection = Connection.createFailedConnection( + new DisconnectCause(DisconnectCause.ERROR)); + } + + 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(), + 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); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "onAudioStateChanged").setAudioState(audioState); + } else { + findConferenceForAction(callId, "onAudioStateChanged").setAudioState(audioState); + } + } + + private void playDtmfTone(String callId, char digit) { + Log.d(this, "playDtmfTone %s %c", callId, digit); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit); + } else { + findConferenceForAction(callId, "playDtmfTone").onPlayDtmfTone(digit); + } + } + + private void stopDtmfTone(String callId) { + Log.d(this, "stopDtmfTone %s", callId); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone(); + } else { + findConferenceForAction(callId, "stopDtmfTone").onStopDtmfTone(); + } + } + + private void conference(String callId1, String callId2) { + Log.d(this, "conference %s, %s", callId1, callId2); + + Connection connection2 = findConnectionForAction(callId2, "conference"); + if (connection2 == getNullConnection()) { + Log.w(this, "Connection2 missing in conference request %s.", callId2); + return; + } + + Connection connection1 = findConnectionForAction(callId1, "conference"); + if (connection1 == getNullConnection()) { + Conference conference1 = findConferenceForAction(callId1, "addConnection"); + if (conference1 == getNullConference()) { + Log.w(this, + "Connection1 or Conference1 missing in conference request %s.", + callId1); + } else { + conference1.onMerge(connection2); + } + } else { + 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(DisconnectCause)} to not handle the call. + */ + public Connection onCreateOutgoingConnection( + PhoneAccountHandle connectionManagerPhoneAccount, + ConnectionRequest request) { + return null; + } + + /** + * Create a {@code Connection} for a new unknown call. An unknown call is a call originating + * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming + * call created using + * {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}. + * + * @param connectionManagerPhoneAccount + * @param request + * @return + * + * @hide + */ + public Connection onCreateUnknownConnection(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(); + } + } +} |