diff options
author | Tyler Gunn <tgunn@google.com> | 2014-09-12 22:17:27 -0700 |
---|---|---|
committer | Tyler Gunn <tgunn@google.com> | 2014-09-12 22:17:27 -0700 |
commit | 7cc70b4f0ad1064a4a0dce6056ad82b205887160 (patch) | |
tree | 4a55232d57828bdf932cd611a17a3c09842c0a0d /src/com/android/server/telecom/ConnectionServiceWrapper.java | |
parent | b9fc537ff7913bda4fd7efc8c511bc3e493e4d06 (diff) | |
download | android_packages_services_Telecomm-7cc70b4f0ad1064a4a0dce6056ad82b205887160.tar.gz android_packages_services_Telecomm-7cc70b4f0ad1064a4a0dce6056ad82b205887160.tar.bz2 android_packages_services_Telecomm-7cc70b4f0ad1064a4a0dce6056ad82b205887160.zip |
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: Ib7b20ba6348948afb391450b4eef8919261f3272
Diffstat (limited to 'src/com/android/server/telecom/ConnectionServiceWrapper.java')
-rw-r--r-- | src/com/android/server/telecom/ConnectionServiceWrapper.java | 1003 |
1 files changed, 1003 insertions, 0 deletions
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java new file mode 100644 index 00000000..669f9a58 --- /dev/null +++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java @@ -0,0 +1,1003 @@ +/* + * Copyright 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 com.android.server.telecom; + +import android.content.ComponentName; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.telecom.AudioState; +import android.telecom.Connection; +import android.telecom.ConnectionRequest; +import android.telecom.ConnectionService; +import android.telecom.GatewayInfo; +import android.telecom.ParcelableConference; +import android.telecom.ParcelableConnection; +import android.telecom.PhoneAccount; +import android.telecom.PhoneAccountHandle; +import android.telecom.StatusHints; +import android.telecom.TelecomManager; +import android.telecom.VideoProfile; +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.IVideoProvider; +import com.android.internal.telecom.RemoteServiceCallback; +import com.google.common.base.Preconditions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Wrapper for {@link IConnectionService}s, handles binding to {@link IConnectionService} and keeps + * track of when the object can safely be unbound. Other classes should not use + * {@link IConnectionService} directly and instead should use this class to invoke methods of + * {@link IConnectionService}. + */ +final class ConnectionServiceWrapper extends ServiceBinder<IConnectionService> { + private static final int MSG_HANDLE_CREATE_CONNECTION_COMPLETE = 1; + private static final int MSG_SET_ACTIVE = 2; + private static final int MSG_SET_RINGING = 3; + private static final int MSG_SET_DIALING = 4; + private static final int MSG_SET_DISCONNECTED = 5; + private static final int MSG_SET_ON_HOLD = 6; + private static final int MSG_SET_RINGBACK_REQUESTED = 7; + private static final int MSG_SET_CALL_CAPABILITIES = 8; + private static final int MSG_SET_IS_CONFERENCED = 9; + private static final int MSG_ADD_CONFERENCE_CALL = 10; + private static final int MSG_REMOVE_CALL = 11; + private static final int MSG_ON_POST_DIAL_WAIT = 12; + private static final int MSG_QUERY_REMOTE_CALL_SERVICES = 13; + private static final int MSG_SET_VIDEO_PROVIDER = 14; + private static final int MSG_SET_IS_VOIP_AUDIO_MODE = 15; + private static final int MSG_SET_STATUS_HINTS = 16; + private static final int MSG_SET_ADDRESS = 17; + private static final int MSG_SET_CALLER_DISPLAY_NAME = 18; + private static final int MSG_SET_VIDEO_STATE = 19; + private static final int MSG_SET_CONFERENCEABLE_CONNECTIONS = 20; + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + Call call; + switch (msg.what) { + case MSG_HANDLE_CREATE_CONNECTION_COMPLETE: { + SomeArgs args = (SomeArgs) msg.obj; + try { + String callId = (String) args.arg1; + ConnectionRequest request = (ConnectionRequest) args.arg2; + ParcelableConnection connection = (ParcelableConnection) args.arg3; + handleCreateConnectionComplete(callId, request, connection); + } finally { + args.recycle(); + } + break; + } + case MSG_SET_ACTIVE: + call = mCallIdMapper.getCall(msg.obj); + if (call != null) { + mCallsManager.markCallAsActive(call); + } else { + //Log.w(this, "setActive, unknown call id: %s", msg.obj); + } + break; + case MSG_SET_RINGING: + call = mCallIdMapper.getCall(msg.obj); + if (call != null) { + mCallsManager.markCallAsRinging(call); + } else { + //Log.w(this, "setRinging, unknown call id: %s", msg.obj); + } + break; + case MSG_SET_DIALING: + call = mCallIdMapper.getCall(msg.obj); + if (call != null) { + mCallsManager.markCallAsDialing(call); + } else { + //Log.w(this, "setDialing, unknown call id: %s", msg.obj); + } + break; + case MSG_SET_DISCONNECTED: { + SomeArgs args = (SomeArgs) msg.obj; + try { + call = mCallIdMapper.getCall(args.arg1); + String disconnectMessage = (String) args.arg2; + int disconnectCause = args.argi1; + Log.d(this, "disconnect call %s %s", args.arg1, call); + if (call != null) { + mCallsManager.markCallAsDisconnected(call, disconnectCause, + disconnectMessage); + } else { + //Log.w(this, "setDisconnected, unknown call id: %s", args.arg1); + } + } finally { + args.recycle(); + } + break; + } + case MSG_SET_ON_HOLD: + call = mCallIdMapper.getCall(msg.obj); + if (call != null) { + mCallsManager.markCallAsOnHold(call); + } else { + //Log.w(this, "setOnHold, unknown call id: %s", msg.obj); + } + break; + case MSG_SET_RINGBACK_REQUESTED: { + call = mCallIdMapper.getCall(msg.obj); + if (call != null) { + call.setRingbackRequested(msg.arg1 == 1); + } else { + //Log.w(this, "setRingback, unknown call id: %s", args.arg1); + } + break; + } + case MSG_SET_CALL_CAPABILITIES: { + call = mCallIdMapper.getCall(msg.obj); + if (call != null) { + call.setCallCapabilities(msg.arg1); + } else { + //Log.w(ConnectionServiceWrapper.this, + // "setCallCapabilities, unknown call id: %s", msg.obj); + } + break; + } + case MSG_SET_IS_CONFERENCED: { + SomeArgs args = (SomeArgs) msg.obj; + try { + Call childCall = mCallIdMapper.getCall(args.arg1); + Log.d(this, "SET_IS_CONFERENCE: %s %s", args.arg1, args.arg2); + if (childCall != null) { + String conferenceCallId = (String) args.arg2; + if (conferenceCallId == null) { + Log.d(this, "unsetting parent: %s", args.arg1); + childCall.setParentCall(null); + } else { + Call conferenceCall = mCallIdMapper.getCall(conferenceCallId); + childCall.setParentCall(conferenceCall); + } + } else { + //Log.w(this, "setIsConferenced, unknown call id: %s", args.arg1); + } + } finally { + args.recycle(); + } + break; + } + case MSG_ADD_CONFERENCE_CALL: { + SomeArgs args = (SomeArgs) msg.obj; + try { + String id = (String) args.arg1; + if (mCallIdMapper.getCall(id) != null) { + Log.w(this, "Attempting to add a conference call using an existing " + + "call id %s", id); + break; + } + ParcelableConference parcelableConference = + (ParcelableConference) args.arg2; + // need to create a new Call + Call conferenceCall = mCallsManager.createConferenceCall( + null, parcelableConference); + mCallIdMapper.addCall(conferenceCall, id); + conferenceCall.setConnectionService(ConnectionServiceWrapper.this); + + Log.d(this, "adding children to conference %s", + parcelableConference.getConnectionIds()); + for (String callId : parcelableConference.getConnectionIds()) { + Call childCall = mCallIdMapper.getCall(callId); + Log.d(this, "found child: %s", callId); + if (childCall != null) { + childCall.setParentCall(conferenceCall); + } + } + } finally { + args.recycle(); + } + break; + } + case MSG_REMOVE_CALL: { + call = mCallIdMapper.getCall(msg.obj); + if (call != null) { + if (call.isActive()) { + mCallsManager.markCallAsDisconnected( + call, DisconnectCause.NORMAL, null); + } else { + mCallsManager.markCallAsRemoved(call); + } + } + break; + } + case MSG_ON_POST_DIAL_WAIT: { + SomeArgs args = (SomeArgs) msg.obj; + try { + call = mCallIdMapper.getCall(args.arg1); + if (call != null) { + String remaining = (String) args.arg2; + call.onPostDialWait(remaining); + } else { + //Log.w(this, "onPostDialWait, unknown call id: %s", args.arg1); + } + } finally { + args.recycle(); + } + break; + } + case MSG_QUERY_REMOTE_CALL_SERVICES: { + queryRemoteConnectionServices((RemoteServiceCallback) msg.obj); + break; + } + case MSG_SET_VIDEO_PROVIDER: { + SomeArgs args = (SomeArgs) msg.obj; + try { + call = mCallIdMapper.getCall(args.arg1); + IVideoProvider videoProvider = (IVideoProvider) args.arg2; + if (call != null) { + call.setVideoProvider(videoProvider); + } + } finally { + args.recycle(); + } + break; + } + case MSG_SET_IS_VOIP_AUDIO_MODE: { + call = mCallIdMapper.getCall(msg.obj); + if (call != null) { + call.setIsVoipAudioMode(msg.arg1 == 1); + } + break; + } + case MSG_SET_STATUS_HINTS: { + SomeArgs args = (SomeArgs) msg.obj; + try { + call = mCallIdMapper.getCall(args.arg1); + StatusHints statusHints = (StatusHints) args.arg2; + if (call != null) { + call.setStatusHints(statusHints); + } + } finally { + args.recycle(); + } + break; + } + case MSG_SET_ADDRESS: { + SomeArgs args = (SomeArgs) msg.obj; + try { + call = mCallIdMapper.getCall(args.arg1); + if (call != null) { + call.setHandle((Uri) args.arg2, args.argi1); + } + } finally { + args.recycle(); + } + break; + } + case MSG_SET_CALLER_DISPLAY_NAME: { + SomeArgs args = (SomeArgs) msg.obj; + try { + call = mCallIdMapper.getCall(args.arg1); + if (call != null) { + call.setCallerDisplayName((String) args.arg2, args.argi1); + } + } finally { + args.recycle(); + } + break; + } + case MSG_SET_VIDEO_STATE: { + call = mCallIdMapper.getCall(msg.obj); + if (call != null) { + call.setVideoState(msg.arg1); + } + break; + } + case MSG_SET_CONFERENCEABLE_CONNECTIONS: { + SomeArgs args = (SomeArgs) msg.obj; + try { + call = mCallIdMapper.getCall(args.arg1); + if (call != null ){ + @SuppressWarnings("unchecked") + List<String> conferenceableIds = (List<String>) args.arg2; + List<Call> conferenceableCalls = + new ArrayList<>(conferenceableIds.size()); + for (String otherId : (List<String>) args.arg2) { + Call otherCall = mCallIdMapper.getCall(otherId); + if (otherCall != null && otherCall != call) { + conferenceableCalls.add(otherCall); + } + } + call.setConferenceableCalls(conferenceableCalls); + } + } finally { + args.recycle(); + } + break; + } + } + } + }; + + private final class Adapter extends IConnectionServiceAdapter.Stub { + + @Override + public void handleCreateConnectionComplete( + String callId, + ConnectionRequest request, + ParcelableConnection connection) { + logIncoming("handleCreateConnectionComplete %s", request); + if (mCallIdMapper.isValidCallId(callId)) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = request; + args.arg3 = connection; + mHandler.obtainMessage(MSG_HANDLE_CREATE_CONNECTION_COMPLETE, args) + .sendToTarget(); + } + } + + @Override + public void setActive(String callId) { + logIncoming("setActive %s", callId); + if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) { + mHandler.obtainMessage(MSG_SET_ACTIVE, callId).sendToTarget(); + } + } + + @Override + public void setRinging(String callId) { + logIncoming("setRinging %s", callId); + if (mCallIdMapper.isValidCallId(callId)) { + mHandler.obtainMessage(MSG_SET_RINGING, callId).sendToTarget(); + } + } + + @Override + public void setVideoProvider(String callId, IVideoProvider videoProvider) { + logIncoming("setVideoProvider %s", callId); + if (mCallIdMapper.isValidCallId(callId)) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = videoProvider; + mHandler.obtainMessage(MSG_SET_VIDEO_PROVIDER, args).sendToTarget(); + } + } + + @Override + public void setDialing(String callId) { + logIncoming("setDialing %s", callId); + if (mCallIdMapper.isValidCallId(callId)) { + mHandler.obtainMessage(MSG_SET_DIALING, callId).sendToTarget(); + } + } + + @Override + public void setDisconnected( + String callId, int disconnectCause, String disconnectMessage) { + logIncoming("setDisconnected %s %d %s", callId, disconnectCause, disconnectMessage); + if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) { + Log.d(this, "disconnect call %s", callId); + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = disconnectMessage; + args.argi1 = disconnectCause; + mHandler.obtainMessage(MSG_SET_DISCONNECTED, args).sendToTarget(); + } + } + + @Override + public void setOnHold(String callId) { + logIncoming("setOnHold %s", callId); + if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) { + mHandler.obtainMessage(MSG_SET_ON_HOLD, callId).sendToTarget(); + } + } + + @Override + public void setRingbackRequested(String callId, boolean ringback) { + logIncoming("setRingbackRequested %s %b", callId, ringback); + if (mCallIdMapper.isValidCallId(callId)) { + mHandler.obtainMessage(MSG_SET_RINGBACK_REQUESTED, ringback ? 1 : 0, 0, callId) + .sendToTarget(); + } + } + + @Override + public void removeCall(String callId) { + logIncoming("removeCall %s", callId); + if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) { + mHandler.obtainMessage(MSG_REMOVE_CALL, callId).sendToTarget(); + mHandler.obtainMessage(MSG_REMOVE_CALL, callId); + } + } + + @Override + public void setCallCapabilities(String callId, int callCapabilities) { + logIncoming("setCallCapabilities %s %d", callId, callCapabilities); + if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) { + mHandler.obtainMessage(MSG_SET_CALL_CAPABILITIES, callCapabilities, 0, callId) + .sendToTarget(); + } else { + Log.w(this, "ID not valid for setCallCapabilities"); + } + } + + @Override + public void setIsConferenced(String callId, String conferenceCallId) { + logIncoming("setIsConferenced %s %s", callId, conferenceCallId); + if (mCallIdMapper.isValidCallId(callId)) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = conferenceCallId; + mHandler.obtainMessage(MSG_SET_IS_CONFERENCED, args).sendToTarget(); + } + } + + @Override + public void addConferenceCall(String callId, ParcelableConference parcelableConference) { + logIncoming("addConferenceCall %s %s", callId, parcelableConference); + // We do not check call Ids here because we do not yet know the call ID for new + // conference calls. + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = parcelableConference; + mHandler.obtainMessage(MSG_ADD_CONFERENCE_CALL, args).sendToTarget(); + } + + @Override + public void onPostDialWait(String callId, String remaining) throws RemoteException { + logIncoming("onPostDialWait %s %s", callId, remaining); + if (mCallIdMapper.isValidCallId(callId)) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = remaining; + mHandler.obtainMessage(MSG_ON_POST_DIAL_WAIT, args).sendToTarget(); + } + } + + @Override + public void queryRemoteConnectionServices(RemoteServiceCallback callback) { + logIncoming("queryRemoteCSs"); + mHandler.obtainMessage(MSG_QUERY_REMOTE_CALL_SERVICES, callback).sendToTarget(); + } + + @Override + public void setVideoState(String callId, int videoState) { + logIncoming("setVideoState %s %d", callId, videoState); + if (mCallIdMapper.isValidCallId(callId)) { + mHandler.obtainMessage(MSG_SET_VIDEO_STATE, videoState, 0, callId).sendToTarget(); + } + } + + @Override + public void setIsVoipAudioMode(String callId, boolean isVoip) { + logIncoming("setIsVoipAudioMode %s %b", callId, isVoip); + if (mCallIdMapper.isValidCallId(callId)) { + mHandler.obtainMessage(MSG_SET_IS_VOIP_AUDIO_MODE, isVoip ? 1 : 0, 0, + callId).sendToTarget(); + } + } + + @Override + public void setStatusHints(String callId, StatusHints statusHints) { + logIncoming("setStatusHints %s %s", callId, statusHints); + if (mCallIdMapper.isValidCallId(callId)) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = statusHints; + mHandler.obtainMessage(MSG_SET_STATUS_HINTS, args).sendToTarget(); + } + } + + @Override + public void setAddress(String callId, Uri address, int presentation) { + logIncoming("setAddress %s %s %d", callId, address, presentation); + if (mCallIdMapper.isValidCallId(callId)) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = address; + args.argi1 = presentation; + mHandler.obtainMessage(MSG_SET_ADDRESS, args).sendToTarget(); + } + } + + @Override + public void setCallerDisplayName( + String callId, String callerDisplayName, int presentation) { + logIncoming("setCallerDisplayName %s %s %d", callId, callerDisplayName, presentation); + if (mCallIdMapper.isValidCallId(callId)) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = callerDisplayName; + args.argi1 = presentation; + mHandler.obtainMessage(MSG_SET_CALLER_DISPLAY_NAME, args).sendToTarget(); + } + } + + @Override + public void setConferenceableConnections( + String callId, List<String> conferenceableCallIds) { + logIncoming("setConferenceableConnections %s %s", callId, conferenceableCallIds); + if (mCallIdMapper.isValidCallId(callId)) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = conferenceableCallIds; + mHandler.obtainMessage(MSG_SET_CONFERENCEABLE_CONNECTIONS, args).sendToTarget(); + } + } + } + + private final Adapter mAdapter = new Adapter(); + private final CallsManager mCallsManager = CallsManager.getInstance(); + /** + * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is + * load factor before resizing, 1 means we only expect a single thread to + * access the map so make only a single shard + */ + private final Set<Call> mPendingConferenceCalls = Collections.newSetFromMap( + new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1)); + private final CallIdMapper mCallIdMapper = new CallIdMapper("ConnectionService"); + private final Map<String, CreateConnectionResponse> mPendingResponses = new HashMap<>(); + + private Binder mBinder = new Binder(); + private IConnectionService mServiceInterface; + private final ConnectionServiceRepository mConnectionServiceRepository; + + /** + * Creates a connection service. + * + * @param componentName The component name of the service with which to bind. + * @param connectionServiceRepository Connection service repository. + * @param phoneAccountRegistrar Phone account registrar + */ + ConnectionServiceWrapper( + ComponentName componentName, + ConnectionServiceRepository connectionServiceRepository, + PhoneAccountRegistrar phoneAccountRegistrar) { + super(ConnectionService.SERVICE_INTERFACE, componentName); + mConnectionServiceRepository = connectionServiceRepository; + phoneAccountRegistrar.addListener(new PhoneAccountRegistrar.Listener() { + // TODO -- Upon changes to PhoneAccountRegistrar, need to re-wire connections + // To do this, we must proxy remote ConnectionService objects + }); + } + + /** See {@link IConnectionService#addConnectionServiceAdapter}. */ + private void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) { + if (isServiceValid("addConnectionServiceAdapter")) { + try { + logOutgoing("addConnectionServiceAdapter %s", adapter); + mServiceInterface.addConnectionServiceAdapter(adapter); + } catch (RemoteException e) { + } + } + } + + /** + * Creates a new connection for a new outgoing call or to attach to an existing incoming call. + */ + void createConnection(final Call call, final CreateConnectionResponse response) { + Log.d(this, "createConnection(%s) via %s.", call, getComponentName()); + BindCallback callback = new BindCallback() { + @Override + public void onSuccess() { + String callId = mCallIdMapper.getCallId(call); + mPendingResponses.put(callId, response); + + GatewayInfo gatewayInfo = call.getGatewayInfo(); + Bundle extras = call.getExtras(); + if (gatewayInfo != null && gatewayInfo.getGatewayProviderPackageName() != null && + gatewayInfo.getOriginalAddress() != null) { + extras = (Bundle) extras.clone(); + extras.putString( + TelecomManager.GATEWAY_PROVIDER_PACKAGE, + gatewayInfo.getGatewayProviderPackageName()); + extras.putParcelable( + TelecomManager.GATEWAY_ORIGINAL_ADDRESS, + gatewayInfo.getOriginalAddress()); + } + + try { + mServiceInterface.createConnection( + call.getConnectionManagerPhoneAccount(), + callId, + new ConnectionRequest( + call.getTargetPhoneAccount(), + call.getHandle(), + extras, + call.getVideoState()), + call.isIncoming()); + } catch (RemoteException e) { + Log.e(this, e, "Failure to createConnection -- %s", getComponentName()); + mPendingResponses.remove(callId).handleCreateConnectionFailure( + DisconnectCause.OUTGOING_FAILURE, e.toString()); + } + } + + @Override + public void onFailure() { + Log.e(this, new Exception(), "Failure to call %s", getComponentName()); + response.handleCreateConnectionFailure(DisconnectCause.OUTGOING_FAILURE, null); + } + }; + + mBinder.bind(callback); + } + + /** @see ConnectionService#abort(String) */ + void abort(Call call) { + // Clear out any pending outgoing call data + final String callId = mCallIdMapper.getCallId(call); + + // If still bound, tell the connection service to abort. + if (callId != null && isServiceValid("abort")) { + try { + logOutgoing("abort %s", callId); + mServiceInterface.abort(callId); + } catch (RemoteException e) { + } + } + + removeCall(call, DisconnectCause.LOCAL, null); + } + + /** @see ConnectionService#hold(String) */ + void hold(Call call) { + final String callId = mCallIdMapper.getCallId(call); + if (callId != null && isServiceValid("hold")) { + try { + logOutgoing("hold %s", callId); + mServiceInterface.hold(callId); + } catch (RemoteException e) { + } + } + } + + /** @see ConnectionService#unhold(String) */ + void unhold(Call call) { + final String callId = mCallIdMapper.getCallId(call); + if (callId != null && isServiceValid("unhold")) { + try { + logOutgoing("unhold %s", callId); + mServiceInterface.unhold(callId); + } catch (RemoteException e) { + } + } + } + + /** @see ConnectionService#onAudioStateChanged(String,AudioState) */ + void onAudioStateChanged(Call activeCall, AudioState audioState) { + final String callId = mCallIdMapper.getCallId(activeCall); + if (callId != null && isServiceValid("onAudioStateChanged")) { + try { + logOutgoing("onAudioStateChanged %s %s", callId, audioState); + mServiceInterface.onAudioStateChanged(callId, audioState); + } catch (RemoteException e) { + } + } + } + + /** @see ConnectionService#disconnect(String) */ + void disconnect(Call call) { + final String callId = mCallIdMapper.getCallId(call); + if (callId != null && isServiceValid("disconnect")) { + try { + logOutgoing("disconnect %s", callId); + mServiceInterface.disconnect(callId); + } catch (RemoteException e) { + } + } + } + + /** @see ConnectionService#answer(String,int) */ + void answer(Call call, int videoState) { + final String callId = mCallIdMapper.getCallId(call); + if (callId != null && isServiceValid("answer")) { + try { + logOutgoing("answer %s %d", callId, videoState); + if (videoState == VideoProfile.VideoState.AUDIO_ONLY) { + mServiceInterface.answer(callId); + } else { + mServiceInterface.answerVideo(callId, videoState); + } + } catch (RemoteException e) { + } + } + } + + /** @see ConnectionService#reject(String) */ + void reject(Call call) { + final String callId = mCallIdMapper.getCallId(call); + if (callId != null && isServiceValid("reject")) { + try { + logOutgoing("reject %s", callId); + mServiceInterface.reject(callId); + } catch (RemoteException e) { + } + } + } + + /** @see ConnectionService#playDtmfTone(String,char) */ + void playDtmfTone(Call call, char digit) { + final String callId = mCallIdMapper.getCallId(call); + if (callId != null && isServiceValid("playDtmfTone")) { + try { + logOutgoing("playDtmfTone %s %c", callId, digit); + mServiceInterface.playDtmfTone(callId, digit); + } catch (RemoteException e) { + } + } + } + + /** @see ConnectionService#stopDtmfTone(String) */ + void stopDtmfTone(Call call) { + final String callId = mCallIdMapper.getCallId(call); + if (callId != null && isServiceValid("stopDtmfTone")) { + try { + logOutgoing("stopDtmfTone %s",callId); + mServiceInterface.stopDtmfTone(callId); + } catch (RemoteException e) { + } + } + } + + void addCall(Call call) { + if (mCallIdMapper.getCallId(call) == null) { + mCallIdMapper.addCall(call); + } + } + + /** + * Associates newCall with this connection service by replacing callToReplace. + */ + void replaceCall(Call newCall, Call callToReplace) { + Preconditions.checkState(callToReplace.getConnectionService() == this); + mCallIdMapper.replaceCall(newCall, callToReplace); + } + + void removeCall(Call call) { + removeCall(call, DisconnectCause.ERROR_UNSPECIFIED, null); + } + + void removeCall(String callId, int disconnectCause, String disconnectMessage) { + CreateConnectionResponse response = mPendingResponses.remove(callId); + if (response != null) { + response.handleCreateConnectionFailure(disconnectCause, disconnectMessage); + } + + mCallIdMapper.removeCall(callId); + } + + void removeCall(Call call, int disconnectCause, String disconnectMessage) { + CreateConnectionResponse response = mPendingResponses.remove(mCallIdMapper.getCallId(call)); + if (response != null) { + response.handleCreateConnectionFailure(disconnectCause, disconnectMessage); + } + + mCallIdMapper.removeCall(call); + } + + void onPostDialContinue(Call call, boolean proceed) { + final String callId = mCallIdMapper.getCallId(call); + if (callId != null && isServiceValid("onPostDialContinue")) { + try { + logOutgoing("onPostDialContinue %s %b", callId, proceed); + mServiceInterface.onPostDialContinue(callId, proceed); + } catch (RemoteException ignored) { + } + } + } + + void conference(final Call call, Call otherCall) { + final String callId = mCallIdMapper.getCallId(call); + final String otherCallId = mCallIdMapper.getCallId(otherCall); + if (callId != null && otherCallId != null && isServiceValid("conference")) { + try { + logOutgoing("conference %s %s", callId, otherCallId); + mServiceInterface.conference(callId, otherCallId); + } catch (RemoteException ignored) { + } + } + } + + void splitFromConference(Call call) { + final String callId = mCallIdMapper.getCallId(call); + if (callId != null && isServiceValid("splitFromConference")) { + try { + logOutgoing("splitFromConference %s", callId); + mServiceInterface.splitFromConference(callId); + } catch (RemoteException ignored) { + } + } + } + + void mergeConference(Call call) { + final String callId = mCallIdMapper.getCallId(call); + if (callId != null && isServiceValid("mergeConference")) { + try { + logOutgoing("mergeConference %s", callId); + mServiceInterface.mergeConference(callId); + } catch (RemoteException ignored) { + } + } + } + + void swapConference(Call call) { + final String callId = mCallIdMapper.getCallId(call); + if (callId != null && isServiceValid("swapConference")) { + try { + logOutgoing("swapConference %s", callId); + mServiceInterface.swapConference(callId); + } catch (RemoteException ignored) { + } + } + } + + /** {@inheritDoc} */ + @Override + protected void setServiceInterface(IBinder binder) { + if (binder == null) { + // We have lost our service connection. Notify the world that this service is done. + // We must notify the adapter before CallsManager. The adapter will force any pending + // outgoing calls to try the next service. This needs to happen before CallsManager + // tries to clean up any calls still associated with this service. + handleConnectionServiceDeath(); + CallsManager.getInstance().handleConnectionServiceDeath(this); + mServiceInterface = null; + } else { + mServiceInterface = IConnectionService.Stub.asInterface(binder); + addConnectionServiceAdapter(mAdapter); + } + } + + private void handleCreateConnectionComplete( + String callId, + ConnectionRequest request, + ParcelableConnection connection) { + // TODO: Note we are not using parameter "request", which is a side effect of our tacit + // assumption that we have at most one outgoing connection attempt per ConnectionService. + // This may not continue to be the case. + if (connection.getState() == Connection.STATE_DISCONNECTED) { + // A connection that begins in the DISCONNECTED state is an indication of + // failure to connect; we handle all failures uniformly + removeCall(callId, connection.getDisconnectCause(), connection.getDisconnectMessage()); + } else { + // Successful connection + if (mPendingResponses.containsKey(callId)) { + mPendingResponses.remove(callId) + .handleCreateConnectionSuccess(mCallIdMapper, connection); + } + } + } + + /** + * Called when the associated connection service dies. + */ + private void handleConnectionServiceDeath() { + if (!mPendingResponses.isEmpty()) { + CreateConnectionResponse[] responses = mPendingResponses.values().toArray( + new CreateConnectionResponse[mPendingResponses.values().size()]); + mPendingResponses.clear(); + for (int i = 0; i < responses.length; i++) { + responses[i].handleCreateConnectionFailure(DisconnectCause.ERROR_UNSPECIFIED, null); + } + } + mCallIdMapper.clear(); + } + + private void logIncoming(String msg, Object... params) { + Log.d(this, "ConnectionService -> Telecom: " + msg, params); + } + + private void logOutgoing(String msg, Object... params) { + Log.d(this, "Telecom -> ConnectionService: " + msg, params); + } + + private void queryRemoteConnectionServices(final RemoteServiceCallback callback) { + PhoneAccountRegistrar registrar = TelecomApp.getInstance().getPhoneAccountRegistrar(); + + // Only give remote connection services to this connection service if it is listed as + // the connection manager. + PhoneAccountHandle simCallManager = registrar.getSimCallManager(); + Log.d(this, "queryRemoteConnectionServices finds simCallManager = %s", simCallManager); + if (simCallManager == null || + !simCallManager.getComponentName().equals(getComponentName())) { + noRemoteServices(callback); + return; + } + + // Make a list of ConnectionServices that are listed as being associated with SIM accounts + final Set<ConnectionServiceWrapper> simServices = Collections.newSetFromMap( + new ConcurrentHashMap<ConnectionServiceWrapper, Boolean>(8, 0.9f, 1)); + for (PhoneAccountHandle handle : registrar.getEnabledPhoneAccounts()) { + PhoneAccount account = registrar.getPhoneAccount(handle); + if ((account.getCapabilities() & PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) != 0) { + ConnectionServiceWrapper service = + mConnectionServiceRepository.getService(handle.getComponentName()); + if (service != null) { + simServices.add(service); + } + } + } + + final List<ComponentName> simServiceComponentNames = new ArrayList<>(); + final List<IBinder> simServiceBinders = new ArrayList<>(); + + Log.v(this, "queryRemoteConnectionServices, simServices = %s", simServices); + + for (ConnectionServiceWrapper simService : simServices) { + if (simService == this) { + // Only happens in the unlikely case that a SIM service is also a SIM call manager + continue; + } + + final ConnectionServiceWrapper currentSimService = simService; + + currentSimService.mBinder.bind(new BindCallback() { + @Override + public void onSuccess() { + Log.d(this, "Adding simService %s", currentSimService.getComponentName()); + simServiceComponentNames.add(currentSimService.getComponentName()); + simServiceBinders.add(currentSimService.mServiceInterface.asBinder()); + maybeComplete(); + } + + @Override + public void onFailure() { + Log.d(this, "Failed simService %s", currentSimService.getComponentName()); + // We know maybeComplete() will always be a no-op from now on, so go ahead and + // signal failure of the entire request + noRemoteServices(callback); + } + + private void maybeComplete() { + if (simServiceComponentNames.size() == simServices.size()) { + setRemoteServices(callback, simServiceComponentNames, simServiceBinders); + } + } + }); + } + } + + private void setRemoteServices( + RemoteServiceCallback callback, + List<ComponentName> componentNames, + List<IBinder> binders) { + try { + callback.onResult(componentNames, binders); + } catch (RemoteException e) { + Log.e(this, e, "Contacting ConnectionService %s", + ConnectionServiceWrapper.this.getComponentName()); + } + } + + private void noRemoteServices(RemoteServiceCallback callback) { + try { + callback.onResult(Collections.EMPTY_LIST, Collections.EMPTY_LIST); + } catch (RemoteException e) { + Log.e(this, e, "Contacting ConnectionService %s", this.getComponentName()); + } + } +} |