blob: e355890c972618d9f97751f7ae5b1dfee1146890 [file] [log] [blame]
/*
* 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.StatusHints;
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 static final int MSG_SET_AUDIO_MODE_IS_VOIP = 19;
private static final int MSG_SET_STATUS_HINTS = 20;
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) args.arg1;
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.argi1;
if (call != null) {
call.setFeatures(features);
}
} finally {
args.recycle();
}
break;
}
case MSG_SET_AUDIO_MODE_IS_VOIP: {
SomeArgs args = (SomeArgs) msg.obj;
try {
call = mCallIdMapper.getCall(args.arg1);
boolean isVoip = args.argi1 == 1;
if (call != null) {
call.setAudioModeIsVoip(isVoip);
}
} finally {
args.recycle();
}
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;
}
}
}
};
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.argi1 = features;
mHandler.obtainMessage(MSG_SET_FEATURES, args).sendToTarget();
}
@Override
public void setAudioModeIsVoip(String callId, boolean isVoip) {
logIncoming("setAudioModeIsVoip %s %d", callId, isVoip);
mCallIdMapper.checkValidCallId(callId);
SomeArgs args = SomeArgs.obtain();
args.arg1 = callId;
args.argi1 = isVoip ? 1 : 0;
mHandler.obtainMessage(MSG_SET_AUDIO_MODE_IS_VOIP, args).sendToTarget();
}
@Override
public void setStatusHints(String callId, StatusHints statusHints) {
logIncoming("setStatusHints %s %s", callId, statusHints);
mCallIdMapper.checkValidCallId(callId);
SomeArgs args = SomeArgs.obtain();
args.arg1 = callId;
args.arg2 = statusHints;
mHandler.obtainMessage(MSG_SET_STATUS_HINTS, 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,
call.getVideoState());
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, call.getVideoState());
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) {
}
}
}
});
}
}
}
});
}
}