Rename CallServiceWrapper to ConnectionServiceWrapper
Change-Id: I5a082e50dafea0104174cb97d3ae8af168944fc3
diff --git a/src/com/android/telecomm/ConnectionServiceWrapper.java b/src/com/android/telecomm/ConnectionServiceWrapper.java
new file mode 100644
index 0000000..0ae8d20
--- /dev/null
+++ b/src/com/android/telecomm/ConnectionServiceWrapper.java
@@ -0,0 +1,886 @@
+/*
+ * 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.telecomm;
+
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.telecomm.CallAudioState;
+import android.telecomm.ConnectionService;
+import android.telecomm.CallServiceDescriptor;
+import android.telecomm.ConnectionRequest;
+import android.telecomm.GatewayInfo;
+import android.telecomm.TelecommConstants;
+import android.telephony.DisconnectCause;
+
+import com.android.internal.os.SomeArgs;
+
+import com.android.internal.telecomm.IConnectionService;
+import com.android.internal.telecomm.IConnectionServiceAdapter;
+import com.android.internal.telecomm.ICallServiceProvider;
+import com.android.internal.telecomm.ICallVideoProvider;
+import com.android.internal.telecomm.RemoteServiceCallback;
+import com.android.telecomm.BaseRepository.LookupCallback;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import org.apache.http.conn.ClientConnectionRequest;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 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_NOTIFY_INCOMING_CALL = 1;
+ private static final int MSG_HANDLE_SUCCESSFUL_OUTGOING_CALL = 2;
+ private static final int MSG_HANDLE_FAILED_OUTGOING_CALL = 3;
+ private static final int MSG_CANCEL_OUTGOING_CALL = 4;
+ private static final int MSG_SET_ACTIVE = 5;
+ private static final int MSG_SET_RINGING = 6;
+ private static final int MSG_SET_DIALING = 7;
+ private static final int MSG_SET_DISCONNECTED = 8;
+ private static final int MSG_SET_ON_HOLD = 9;
+ private static final int MSG_SET_REQUESTING_RINGBACK = 10;
+ private static final int MSG_CAN_CONFERENCE = 11;
+ private static final int MSG_SET_IS_CONFERENCED = 12;
+ private static final int MSG_ADD_CONFERENCE_CALL = 13;
+ private static final int MSG_REMOVE_CALL = 14;
+ private static final int MSG_ON_POST_DIAL_WAIT = 15;
+ private static final int MSG_QUERY_REMOTE_CALL_SERVICES = 16;
+ private static final int MSG_SET_CALL_VIDEO_PROVIDER = 17;
+ private static final int MSG_SET_FEATURES = 18;
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ Call call;
+ switch (msg.what) {
+ case MSG_NOTIFY_INCOMING_CALL: {
+ ConnectionRequest request = (ConnectionRequest) msg.obj;
+ call = mCallIdMapper.getCall(request.getCallId());
+ if (call != null && mPendingIncomingCalls.remove(call) &&
+ call.isIncoming()) {
+ mIncomingCallsManager.handleSuccessfulIncomingCall(call, request);
+ } else {
+ // TODO(santoscordon): For this an the other commented logging, we need
+ // to reenable it. At the moment all ConnectionServiceAdapters receive
+ // notification of changes to all calls, even calls which it may not own
+ // (ala remote connections). We need to fix that and then uncomment the
+ // logging calls here.
+ //Log.w(this, "notifyIncomingCall, unknown incoming call: %s, id: %s",
+ // call, request.getId());
+ }
+ break;
+ }
+ case MSG_HANDLE_SUCCESSFUL_OUTGOING_CALL: {
+ ConnectionRequest request = (ConnectionRequest) msg.obj;
+ if (mPendingOutgoingCalls.containsKey(request.getCallId())) {
+ mPendingOutgoingCalls.remove(
+ request.getCallId()).onOutgoingCallSuccess();
+ } else {
+ //Log.w(this, "handleSuccessfulOutgoingCall, unknown call: %s", callId);
+ }
+ break;
+ }
+ case MSG_HANDLE_FAILED_OUTGOING_CALL: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ ConnectionRequest request = (ConnectionRequest) msg.obj;
+ int statusCode = args.argi1;
+ String statusMsg = (String) args.arg2;
+ // TODO(santoscordon): Do something with 'reason' or get rid of it.
+
+ if (mPendingOutgoingCalls.containsKey(request.getCallId())) {
+ mPendingOutgoingCalls.remove(request.getCallId())
+ .onOutgoingCallFailure(statusCode, statusMsg);
+ mCallIdMapper.removeCall(request.getCallId());
+ } else {
+ //Log.w(this, "handleFailedOutgoingCall, unknown call: %s", callId);
+ }
+ } finally {
+ args.recycle();
+ }
+ break;
+ }
+ case MSG_CANCEL_OUTGOING_CALL: {
+ ConnectionRequest request = (ConnectionRequest) msg.obj;
+ if (mPendingOutgoingCalls.containsKey(request.getCallId())) {
+ mPendingOutgoingCalls.remove(
+ request.getCallId()).onOutgoingCallCancel();
+ } else {
+ //Log.w(this, "cancelOutgoingCall, unknown call: %s", callId);
+ }
+ 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;
+ 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_REQUESTING_RINGBACK: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ call = mCallIdMapper.getCall(args.arg1);
+ boolean ringback = (boolean) args.arg2;
+ if (call != null) {
+ call.setRequestingRingback(ringback);
+ } else {
+ //Log.w(this, "setRingback, unknown call id: %s", args.arg1);
+ }
+ } finally {
+ args.recycle();
+ }
+ break;
+ }
+ case MSG_CAN_CONFERENCE: {
+ call = mCallIdMapper.getCall(msg.obj);
+ if (call != null) {
+ call.setIsConferenceCapable(msg.arg1 == 1);
+ } else {
+ //Log.w(ConnectionServiceWrapper.this,
+ // "canConference, unknown call id: %s", msg.obj);
+ }
+ break;
+ }
+ case MSG_SET_IS_CONFERENCED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ Call childCall = mCallIdMapper.getCall(args.arg1);
+ if (childCall != null) {
+ String conferenceCallId = (String) args.arg2;
+ if (conferenceCallId == null) {
+ childCall.setParentCall(null);
+ } else {
+ Call conferenceCall = mCallIdMapper.getCall(conferenceCallId);
+ if (conferenceCall != null &&
+ !mPendingConferenceCalls.contains(conferenceCall)) {
+ childCall.setParentCall(conferenceCall);
+ } else {
+ //Log.w(this, "setIsConferenced, unknown conference id %s",
+ // conferenceCallId);
+ }
+ }
+ } else {
+ //Log.w(this, "setIsConferenced, unknown call id: %s", args.arg1);
+ }
+ } finally {
+ args.recycle();
+ }
+ break;
+ }
+ case MSG_ADD_CONFERENCE_CALL: {
+ Call conferenceCall = mCallIdMapper.getCall(msg.obj);
+ if (mPendingConferenceCalls.remove(conferenceCall)) {
+ Log.v(this, "confirming conf call %s", conferenceCall);
+ conferenceCall.confirmConference();
+ } else {
+ //Log.w(this, "addConference, unknown call id: %s", callId);
+ }
+ break;
+ }
+ case MSG_REMOVE_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: {
+ ConnectionServiceWrapper.this.queryRemoteConnectionServices(
+ (RemoteServiceCallback) msg.obj);
+ break;
+ }
+ case MSG_SET_CALL_VIDEO_PROVIDER: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ call = mCallIdMapper.getCall(args.arg1);
+ ICallVideoProvider callVideoProvider = (ICallVideoProvider) args.arg2;
+ if (call != null) {
+ call.setCallVideoProvider(callVideoProvider);
+ }
+ } finally {
+ args.recycle();
+ }
+ break;
+ }
+ case MSG_SET_FEATURES: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ call = mCallIdMapper.getCall(args.arg1);
+ int features = (int) args.arg2;
+ if (call != null) {
+ call.setFeatures(features);
+ }
+ } finally {
+ args.recycle();
+ }
+ break;
+ }
+ }
+ }
+ };
+
+ private final class Adapter extends IConnectionServiceAdapter.Stub {
+ /** {@inheritDoc} */
+ @Override
+ public void notifyIncomingCall(ConnectionRequest request) {
+ logIncoming("notifyIncomingCall %s", request);
+ mCallIdMapper.checkValidCallId(request.getCallId());
+ mHandler.obtainMessage(MSG_NOTIFY_INCOMING_CALL, request).sendToTarget();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void handleSuccessfulOutgoingCall(ConnectionRequest request) {
+ logIncoming("handleSuccessfulOutgoingCall %s", request);
+ mCallIdMapper.checkValidCallId(request.getCallId());
+ mHandler.obtainMessage(MSG_HANDLE_SUCCESSFUL_OUTGOING_CALL, request).sendToTarget();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void handleFailedOutgoingCall(
+ ConnectionRequest request,
+ int errorCode,
+ String errorMsg) {
+ logIncoming("handleFailedOutgoingCall %s %d %s", request, errorCode, errorMsg);
+ mCallIdMapper.checkValidCallId(request.getCallId());
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = request;
+ args.argi1 = errorCode;
+ args.arg2 = errorMsg;
+ mHandler.obtainMessage(MSG_HANDLE_FAILED_OUTGOING_CALL, args).sendToTarget();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void cancelOutgoingCall(ConnectionRequest request) {
+ logIncoming("cancelOutgoingCall %s", request);
+ mCallIdMapper.checkValidCallId(request.getCallId());
+ mHandler.obtainMessage(MSG_CANCEL_OUTGOING_CALL, request).sendToTarget();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setActive(String callId) {
+ logIncoming("setActive %s", callId);
+ mCallIdMapper.checkValidCallId(callId);
+ mHandler.obtainMessage(MSG_SET_ACTIVE, callId).sendToTarget();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setRinging(String callId) {
+ logIncoming("setRinging %s", callId);
+ mCallIdMapper.checkValidCallId(callId);
+ mHandler.obtainMessage(MSG_SET_RINGING, callId).sendToTarget();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setCallVideoProvider(String callId, ICallVideoProvider callVideoProvider) {
+ logIncoming("setCallVideoProvider %s", callId);
+ mCallIdMapper.checkValidCallId(callId);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = callVideoProvider;
+ mHandler.obtainMessage(MSG_SET_CALL_VIDEO_PROVIDER, args).sendToTarget();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setDialing(String callId) {
+ logIncoming("setDialing %s", callId);
+ mCallIdMapper.checkValidCallId(callId);
+ mHandler.obtainMessage(MSG_SET_DIALING, callId).sendToTarget();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setDisconnected(
+ String callId, int disconnectCause, String disconnectMessage) {
+ logIncoming("setDisconnected %s %d %s", callId, disconnectCause, disconnectMessage);
+ mCallIdMapper.checkValidCallId(callId);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = disconnectMessage;
+ args.argi1 = disconnectCause;
+ mHandler.obtainMessage(MSG_SET_DISCONNECTED, args).sendToTarget();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setOnHold(String callId) {
+ logIncoming("setOnHold %s", callId);
+ mCallIdMapper.checkValidCallId(callId);
+ mHandler.obtainMessage(MSG_SET_ON_HOLD, callId).sendToTarget();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setRequestingRingback(String callId, boolean ringback) {
+ logIncoming("setRequestingRingback %s %b", callId, ringback);
+ mCallIdMapper.checkValidCallId(callId);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = ringback;
+ mHandler.obtainMessage(MSG_SET_REQUESTING_RINGBACK, args).sendToTarget();
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void removeCall(String callId) {
+ logIncoming("removeCall %s", callId);
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void setCanConference(String callId, boolean canConference) {
+ logIncoming("setCanConference %s %b", callId, canConference);
+ mHandler.obtainMessage(MSG_CAN_CONFERENCE, canConference ? 1 : 0, 0, callId)
+ .sendToTarget();
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void setIsConferenced(String callId, String conferenceCallId) {
+ logIncoming("setIsConferenced %s %s", callId, conferenceCallId);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = conferenceCallId;
+ mHandler.obtainMessage(MSG_SET_IS_CONFERENCED, args).sendToTarget();
+ }
+
+ /** ${InheritDoc} */
+ @Override
+ public void addConferenceCall(String callId) {
+ logIncoming("addConferenceCall %s", callId);
+ mCallIdMapper.checkValidCallId(callId);
+ mHandler.obtainMessage(MSG_ADD_CONFERENCE_CALL, callId).sendToTarget();
+ }
+
+ @Override
+ public void onPostDialWait(String callId, String remaining) throws RemoteException {
+ logIncoming("onPostDialWait %s %s", callId, remaining);
+ mCallIdMapper.checkValidCallId(callId);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = remaining;
+ mHandler.obtainMessage(MSG_ON_POST_DIAL_WAIT, args).sendToTarget();
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
+ logIncoming("queryRemoteCSs");
+ mHandler.obtainMessage(MSG_QUERY_REMOTE_CALL_SERVICES, callback).sendToTarget();
+ }
+
+ @Override
+ public void setFeatures(String callId, int features) {
+ logIncoming("setFeatures %s %d", callId, features);
+ mCallIdMapper.checkValidCallId(callId);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = features;
+ mHandler.obtainMessage(MSG_SET_FEATURES, args).sendToTarget();
+ }
+ }
+
+ private final Adapter mAdapter = new Adapter();
+ private final CallsManager mCallsManager = CallsManager.getInstance();
+ private final Set<Call> mPendingIncomingCalls = new HashSet<>();
+ private final Set<Call> mPendingConferenceCalls = new HashSet<>();
+ private final CallServiceDescriptor mDescriptor;
+ private final CallIdMapper mCallIdMapper = new CallIdMapper("ConnectionService");
+ private final IncomingCallsManager mIncomingCallsManager;
+ private final Map<String, OutgoingCallResponse> mPendingOutgoingCalls = new HashMap<>();
+
+ private Binder mBinder = new Binder();
+ private IConnectionService mServiceInterface;
+ private final CallServiceRepository mCallServiceRepository;
+
+ /**
+ * Creates a call-service for the specified descriptor.
+ *
+ * @param descriptor The call-service descriptor from
+ * {@link ICallServiceProvider#lookupCallServices}.
+ * @param incomingCallsManager Manages the incoming call initialization flow.
+ * @param callServiceRepository Connection service repository.
+ */
+ ConnectionServiceWrapper(
+ CallServiceDescriptor descriptor,
+ IncomingCallsManager incomingCallsManager,
+ CallServiceRepository callServiceRepository) {
+ super(TelecommConstants.ACTION_CONNECTION_SERVICE, descriptor.getServiceComponent());
+ mDescriptor = descriptor;
+ mIncomingCallsManager = incomingCallsManager;
+ mCallServiceRepository = callServiceRepository;
+ }
+
+ CallServiceDescriptor getDescriptor() {
+ return mDescriptor;
+ }
+
+ /** See {@link IConnectionService#addConnectionServiceAdapter}. */
+ private void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
+ if (isServiceValid("addConnectionServiceAdapter")) {
+ try {
+ logOutgoing("addConnectionServiceAdapter%s", adapter);
+ mServiceInterface.addConnectionServiceAdapter(adapter);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * Attempts to place the specified call, see {@link IConnectionService#call}. Returns the result
+ * asynchronously through the specified callback.
+ */
+ void call(final Call call, final OutgoingCallResponse callResponse) {
+ Log.d(this, "call(%s) via %s.", call, getComponentName());
+ BindCallback callback = new BindCallback() {
+ @Override
+ public void onSuccess() {
+ String callId = mCallIdMapper.getCallId(call);
+ mPendingOutgoingCalls.put(callId, callResponse);
+
+ GatewayInfo gatewayInfo = call.getGatewayInfo();
+ Bundle extras = call.getExtras();
+ if (gatewayInfo != null && gatewayInfo.getGatewayProviderPackageName() != null &&
+ gatewayInfo.getOriginalHandle() != null) {
+ extras = (Bundle) extras.clone();
+ extras.putString(
+ NewOutgoingCallIntentBroadcaster.EXTRA_GATEWAY_PROVIDER_PACKAGE,
+ gatewayInfo.getGatewayProviderPackageName());
+ extras.putParcelable(
+ NewOutgoingCallIntentBroadcaster.EXTRA_GATEWAY_ORIGINAL_URI,
+ gatewayInfo.getOriginalHandle());
+ }
+ ConnectionRequest request = new ConnectionRequest(callId, call.getHandle(), extras);
+
+ try {
+ mServiceInterface.call(request);
+ } catch (RemoteException e) {
+ Log.e(this, e, "Failure to call -- %s", getDescriptor());
+ mPendingOutgoingCalls.remove(callId).onOutgoingCallFailure(
+ DisconnectCause.ERROR_UNSPECIFIED, e.toString());
+ }
+ }
+
+ @Override
+ public void onFailure() {
+ Log.e(this, new Exception(), "Failure to call %s", getDescriptor());
+ callResponse.onOutgoingCallFailure(DisconnectCause.ERROR_UNSPECIFIED, null);
+ }
+ };
+
+ mBinder.bind(callback);
+ }
+
+ /** @see ConnectionService#abort(String) */
+ void abort(Call call) {
+ // Clear out any pending outgoing call data
+ String callId = mCallIdMapper.getCallId(call);
+
+ // If still bound, tell the connection service to abort.
+ if (isServiceValid("abort")) {
+ try {
+ logOutgoing("abort %s", callId);
+ mServiceInterface.abort(callId);
+ } catch (RemoteException e) {
+ }
+ }
+
+ removeCall(call);
+ }
+
+ /** @see ConnectionService#hold(String) */
+ void hold(Call call) {
+ if (isServiceValid("hold")) {
+ try {
+ logOutgoing("hold %s", mCallIdMapper.getCallId(call));
+ mServiceInterface.hold(mCallIdMapper.getCallId(call));
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /** @see ConnectionService#unhold(String) */
+ void unhold(Call call) {
+ if (isServiceValid("unhold")) {
+ try {
+ logOutgoing("unhold %s", mCallIdMapper.getCallId(call));
+ mServiceInterface.unhold(mCallIdMapper.getCallId(call));
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /** @see ConnectionService#onAudioStateChanged(String,CallAudioState) */
+ void onAudioStateChanged(Call activeCall, CallAudioState audioState) {
+ if (isServiceValid("onAudioStateChanged")) {
+ try {
+ logOutgoing("onAudioStateChanged %s %s",
+ mCallIdMapper.getCallId(activeCall), audioState);
+ mServiceInterface.onAudioStateChanged(mCallIdMapper.getCallId(activeCall),
+ audioState);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * Starts retrieval of details for an incoming call. Details are returned through the
+ * call-service adapter using the specified call ID. Upon failure, the specified error callback
+ * is invoked. Can be invoked even when the connection service is unbound. See
+ * {@link IConnectionService#createIncomingCall}.
+ *
+ * @param call The call used for the incoming call.
+ * @param extras The {@link ConnectionService}-provided extras which need to be sent back.
+ * @param errorCallback The callback to invoke upon failure.
+ */
+ void createIncomingCall(final Call call, final Bundle extras, final Runnable errorCallback) {
+ Log.d(this, "createIncomingCall(%s) via %s.", call, getComponentName());
+ BindCallback callback = new BindCallback() {
+ @Override
+ public void onSuccess() {
+ if (isServiceValid("createIncomingCall")) {
+ mPendingIncomingCalls.add(call);
+ String callId = mCallIdMapper.getCallId(call);
+ logOutgoing("createIncomingCall %s %s", callId, extras);
+ ConnectionRequest request = new ConnectionRequest(
+ callId, call.getHandle(), extras);
+ try {
+ mServiceInterface.createIncomingCall(request);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ @Override
+ public void onFailure() {
+ errorCallback.run();
+ }
+ };
+
+ mBinder.bind(callback);
+ }
+
+ /** @see ConnectionService#disconnect(String) */
+ void disconnect(Call call) {
+ if (isServiceValid("disconnect")) {
+ try {
+ logOutgoing("disconnect %s", mCallIdMapper.getCallId(call));
+ mServiceInterface.disconnect(mCallIdMapper.getCallId(call));
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /** @see ConnectionService#answer(String) */
+ void answer(Call call) {
+ if (isServiceValid("answer")) {
+ try {
+ logOutgoing("answer %s", mCallIdMapper.getCallId(call));
+ mServiceInterface.answer(mCallIdMapper.getCallId(call));
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /** @see ConnectionService#reject(String) */
+ void reject(Call call) {
+ if (isServiceValid("reject")) {
+ try {
+ logOutgoing("reject %s", mCallIdMapper.getCallId(call));
+ mServiceInterface.reject(mCallIdMapper.getCallId(call));
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /** @see ConnectionService#playDtmfTone(String,char) */
+ void playDtmfTone(Call call, char digit) {
+ if (isServiceValid("playDtmfTone")) {
+ try {
+ logOutgoing("playDtmfTone %s %c", mCallIdMapper.getCallId(call), digit);
+ mServiceInterface.playDtmfTone(mCallIdMapper.getCallId(call), digit);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /** @see ConnectionService#stopDtmfTone(String) */
+ void stopDtmfTone(Call call) {
+ if (isServiceValid("stopDtmfTone")) {
+ try {
+ logOutgoing("stopDtmfTone %s", mCallIdMapper.getCallId(call));
+ mServiceInterface.stopDtmfTone(mCallIdMapper.getCallId(call));
+ } 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) {
+ mPendingIncomingCalls.remove(call);
+
+ OutgoingCallResponse outgoingResultCallback =
+ mPendingOutgoingCalls.remove(mCallIdMapper.getCallId(call));
+ if (outgoingResultCallback != null) {
+ outgoingResultCallback.onOutgoingCallFailure(DisconnectCause.ERROR_UNSPECIFIED, null);
+ }
+
+ mCallIdMapper.removeCall(call);
+ }
+
+ void onPostDialContinue(Call call, boolean proceed) {
+ if (isServiceValid("onPostDialContinue")) {
+ try {
+ logOutgoing("onPostDialContinue %s %b", mCallIdMapper.getCallId(call), proceed);
+ mServiceInterface.onPostDialContinue(mCallIdMapper.getCallId(call), proceed);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
+ void onPhoneAccountClicked(Call call) {
+ if (isServiceValid("onPhoneAccountClicked")) {
+ try {
+ logOutgoing("onPhoneAccountClicked %s", mCallIdMapper.getCallId(call));
+ mServiceInterface.onPhoneAccountClicked(mCallIdMapper.getCallId(call));
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
+ void conference(final Call conferenceCall, Call call) {
+ if (isServiceValid("conference")) {
+ try {
+ conferenceCall.setConnectionService(this);
+ mPendingConferenceCalls.add(conferenceCall);
+ mHandler.postDelayed(new Runnable() {
+ @Override public void run() {
+ if (mPendingConferenceCalls.remove(conferenceCall)) {
+ conferenceCall.expireConference();
+ Log.i(this, "Conference call expired: %s", conferenceCall);
+ }
+ }
+ }, Timeouts.getConferenceCallExpireMillis());
+
+ logOutgoing("conference %s %s",
+ mCallIdMapper.getCallId(conferenceCall),
+ mCallIdMapper.getCallId(call));
+ mServiceInterface.conference(
+ mCallIdMapper.getCallId(conferenceCall),
+ mCallIdMapper.getCallId(call));
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
+ void splitFromConference(Call call) {
+ if (isServiceValid("splitFromConference")) {
+ try {
+ logOutgoing("splitFromConference %s", mCallIdMapper.getCallId(call));
+ mServiceInterface.splitFromConference(mCallIdMapper.getCallId(call));
+ } 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);
+ }
+ }
+
+ /**
+ * Called when the associated connection service dies.
+ */
+ private void handleConnectionServiceDeath() {
+ if (!mPendingOutgoingCalls.isEmpty()) {
+ for (OutgoingCallResponse callback : mPendingOutgoingCalls.values()) {
+ callback.onOutgoingCallFailure(DisconnectCause.ERROR_UNSPECIFIED, null);
+ }
+ mPendingOutgoingCalls.clear();
+ }
+
+ if (!mPendingIncomingCalls.isEmpty()) {
+ // Iterate through a copy because the code inside the loop will modify the original
+ // list.
+ for (Call call : ImmutableList.copyOf(mPendingIncomingCalls)) {
+ Preconditions.checkState(call.isIncoming());
+ mIncomingCallsManager.handleFailedIncomingCall(call);
+ }
+
+ if (!mPendingIncomingCalls.isEmpty()) {
+ Log.wtf(this, "Pending calls did not get cleared.");
+ mPendingIncomingCalls.clear();
+ }
+ }
+
+ mCallIdMapper.clear();
+ }
+
+ private void logIncoming(String msg, Object... params) {
+ Log.d(this, "ConnectionService -> Telecomm: " + msg, params);
+ }
+
+ private void logOutgoing(String msg, Object... params) {
+ Log.d(this, "Telecomm -> ConnectionService: " + msg, params);
+ }
+
+ private void queryRemoteConnectionServices(final RemoteServiceCallback callback) {
+ final List<IBinder> connectionServices = new ArrayList<>();
+ final List<ComponentName> components = new ArrayList<>();
+
+ mCallServiceRepository.lookupServices(new LookupCallback<ConnectionServiceWrapper>() {
+ private int mRemainingResponses;
+
+ /** ${inheritDoc} */
+ @Override
+ public void onComplete(Collection<ConnectionServiceWrapper> services) {
+ mRemainingResponses = services.size() - 1;
+ for (ConnectionServiceWrapper cs : services) {
+ if (cs != ConnectionServiceWrapper.this) {
+ final ConnectionServiceWrapper currentConnectionService = cs;
+ cs.mBinder.bind(new BindCallback() {
+ @Override
+ public void onSuccess() {
+ Log.d(this, "Adding ***** %s",
+ currentConnectionService.getDescriptor());
+ connectionServices.add(
+ currentConnectionService.mServiceInterface.asBinder());
+ components.add(currentConnectionService.getComponentName());
+ maybeComplete();
+ }
+
+ @Override
+ public void onFailure() {
+ // add null so that we always add up to totalExpected even if
+ // some of the connection services fail to bind.
+ maybeComplete();
+ }
+
+ private void maybeComplete() {
+ if (--mRemainingResponses == 0) {
+ try {
+ callback.onResult(components, connectionServices);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+ });
+ }
+ }
+ }
+ });
+ }
+}