blob: 4621558d17d2a283aa8bbd7acaecf6a9897124f2 [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.server.telecom;
import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.UserHandle;
import android.telecom.CallAudioState;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
import android.telecom.ConnectionService;
import android.telecom.DisconnectCause;
import android.telecom.GatewayInfo;
import android.telecom.Log;
import android.telecom.Logging.Session;
import android.telecom.ParcelableConference;
import android.telecom.ParcelableConnection;
import android.telecom.PhoneAccountHandle;
import android.telecom.StatusHints;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telephony.TelephonyManager;
import com.android.internal.annotations.VisibleForTesting;
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.android.internal.util.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}.
*/
@VisibleForTesting
public class ConnectionServiceWrapper extends ServiceBinder implements
ConnectionServiceFocusManager.ConnectionServiceFocus {
private final class Adapter extends IConnectionServiceAdapter.Stub {
@Override
public void handleCreateConnectionComplete(String callId, ConnectionRequest request,
ParcelableConnection connection, Session.Info sessionInfo) {
Log.startSession(sessionInfo, LogUtils.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("handleCreateConnectionComplete %s", callId);
ConnectionServiceWrapper.this
.handleCreateConnectionComplete(callId, request, connection);
if (mServiceInterface != null) {
logOutgoing("createConnectionComplete %s", callId);
try {
mServiceInterface.createConnectionComplete(callId,
Log.getExternalSession());
} catch (RemoteException e) {
}
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setActive(String callId, Session.Info sessionInfo) {
Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_ACTIVE);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setActive %s", callId);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
mCallsManager.markCallAsActive(call);
} else {
// Log.w(this, "setActive, unknown call id: %s", msg.obj);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setRinging(String callId, Session.Info sessionInfo) {
Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_RINGING);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setRinging %s", callId);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
mCallsManager.markCallAsRinging(call);
} else {
// Log.w(this, "setRinging, unknown call id: %s", msg.obj);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void resetConnectionTime(String callId, Session.Info sessionInfo) {
Log.startSession(sessionInfo, "CSW.rCCT");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("resetConnectionTime %s", callId);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
mCallsManager.resetConnectionTime(call);
} else {
// Log.w(this, "resetConnectionTime, unknown call id: %s", msg.obj);
}
}
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setVideoProvider(String callId, IVideoProvider videoProvider,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, "CSW.sVP");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setVideoProvider %s", callId);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.setVideoProvider(videoProvider);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setDialing(String callId, Session.Info sessionInfo) {
Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_DIALING);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setDialing %s", callId);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
mCallsManager.markCallAsDialing(call);
} else {
// Log.w(this, "setDialing, unknown call id: %s", msg.obj);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setPulling(String callId, Session.Info sessionInfo) {
Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_PULLING);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setPulling %s", callId);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
mCallsManager.markCallAsPulling(call);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setDisconnected(String callId, DisconnectCause disconnectCause,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_DISCONNECTED);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setDisconnected %s %s", callId, disconnectCause);
Call call = mCallIdMapper.getCall(callId);
Log.d(this, "disconnect call %s %s", disconnectCause, call);
if (call != null) {
mCallsManager.markCallAsDisconnected(call, disconnectCause);
} else {
// Log.w(this, "setDisconnected, unknown call id: %s", args.arg1);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setOnHold(String callId, Session.Info sessionInfo) {
Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_ON_HOLD);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setOnHold %s", callId);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
mCallsManager.markCallAsOnHold(call);
} else {
// Log.w(this, "setOnHold, unknown call id: %s", msg.obj);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setRingbackRequested(String callId, boolean ringback,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, "CSW.SRR");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setRingbackRequested %s %b", callId, ringback);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.setRingbackRequested(ringback);
} else {
// Log.w(this, "setRingback, unknown call id: %s", args.arg1);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void removeCall(String callId, Session.Info sessionInfo) {
Log.startSession(sessionInfo, LogUtils.Sessions.CSW_REMOVE_CALL);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("removeCall %s", callId);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
if (call.isAlive()) {
mCallsManager.markCallAsDisconnected(
call, new DisconnectCause(DisconnectCause.REMOTE));
} else {
mCallsManager.markCallAsRemoved(call);
}
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setConnectionCapabilities(String callId, int connectionCapabilities,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, "CSW.sCC");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setConnectionCapabilities %s %d", callId, connectionCapabilities);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.setConnectionCapabilities(connectionCapabilities);
} else {
// Log.w(ConnectionServiceWrapper.this,
// "setConnectionCapabilities, unknown call id: %s", msg.obj);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setConnectionProperties(String callId, int connectionProperties,
Session.Info sessionInfo) {
Log.startSession("CSW.sCP");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setConnectionProperties %s %d", callId, connectionProperties);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.setConnectionProperties(connectionProperties);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setIsConferenced(String callId, String conferenceCallId,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_IS_CONFERENCED);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setIsConferenced %s %s", callId, conferenceCallId);
Call childCall = mCallIdMapper.getCall(callId);
if (childCall != null) {
if (conferenceCallId == null) {
Log.d(this, "unsetting parent: %s", conferenceCallId);
childCall.setParentAndChildCall(null);
} else {
Call conferenceCall = mCallIdMapper.getCall(conferenceCallId);
childCall.setParentAndChildCall(conferenceCall);
}
} else {
// Log.w(this, "setIsConferenced, unknown call id: %s", args.arg1);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setConferenceMergeFailed(String callId, Session.Info sessionInfo) {
Log.startSession(sessionInfo, "CSW.sCMF");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setConferenceMergeFailed %s", callId);
// TODO: we should move the UI for indication a merge failure here
// from CallNotifier.onSuppServiceFailed(). This way the InCallUI can
// deliver the message anyway that they want. b/20530631.
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.onConnectionEvent(Connection.EVENT_CALL_MERGE_FAILED, null);
} else {
Log.w(this, "setConferenceMergeFailed, unknown call id: %s", callId);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void addConferenceCall(String callId, ParcelableConference parcelableConference,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, LogUtils.Sessions.CSW_ADD_CONFERENCE_CALL);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
if (mCallIdMapper.getCall(callId) != null) {
Log.w(this, "Attempting to add a conference call using an existing " +
"call id %s", callId);
return;
}
logIncoming("addConferenceCall %s %s [%s]", callId, parcelableConference,
parcelableConference.getConnectionIds());
// Make sure that there's at least one valid call. For remote connections
// we'll get a add conference msg from both the remote connection service
// and from the real connection service.
boolean hasValidCalls = false;
for (String connId : parcelableConference.getConnectionIds()) {
if (mCallIdMapper.getCall(connId) != null) {
hasValidCalls = true;
}
}
// But don't bail out if the connection count is 0, because that is a valid
// IMS conference state.
if (!hasValidCalls && parcelableConference.getConnectionIds().size() > 0) {
Log.d(this, "Attempting to add a conference with no valid calls");
return;
}
PhoneAccountHandle phAcc = null;
if (parcelableConference != null &&
parcelableConference.getPhoneAccount() != null) {
phAcc = parcelableConference.getPhoneAccount();
}
Bundle connectionExtras = parcelableConference.getExtras();
String connectIdToCheck = null;
if (connectionExtras != null && connectionExtras
.containsKey(Connection.EXTRA_ORIGINAL_CONNECTION_ID)) {
// Conference was added via a connection manager, see if its original id is
// known.
connectIdToCheck = connectionExtras
.getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID);
} else {
connectIdToCheck = callId;
}
Call conferenceCall;
// Check to see if this conference has already been added.
Call alreadyAddedConnection = mCallsManager
.getAlreadyAddedConnection(connectIdToCheck);
if (alreadyAddedConnection != null && mCallIdMapper.getCall(callId) == null) {
// We are currently attempting to add the conference via a connection mgr,
// and the originating ConnectionService has already added it. Instead of
// making a new Telecom call, we will simply add it to the ID mapper here,
// and replace the ConnectionService on the call.
mCallIdMapper.addCall(alreadyAddedConnection, callId);
alreadyAddedConnection.replaceConnectionService(
ConnectionServiceWrapper.this);
conferenceCall = alreadyAddedConnection;
} else {
// need to create a new Call
Call newConferenceCall = mCallsManager.createConferenceCall(callId,
phAcc, parcelableConference);
mCallIdMapper.addCall(newConferenceCall, callId);
newConferenceCall.setConnectionService(ConnectionServiceWrapper.this);
conferenceCall = newConferenceCall;
}
Log.d(this, "adding children to conference %s phAcc %s",
parcelableConference.getConnectionIds(), phAcc);
for (String connId : parcelableConference.getConnectionIds()) {
Call childCall = mCallIdMapper.getCall(connId);
Log.d(this, "found child: %s", connId);
if (childCall != null) {
childCall.setParentAndChildCall(conferenceCall);
}
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void onPostDialWait(String callId, String remaining,
Session.Info sessionInfo) throws RemoteException {
Log.startSession(sessionInfo, "CSW.oPDW");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("onPostDialWait %s %s", callId, remaining);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.onPostDialWait(remaining);
} else {
// Log.w(this, "onPostDialWait, unknown call id: %s", args.arg1);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void onPostDialChar(String callId, char nextChar,
Session.Info sessionInfo) throws RemoteException {
Log.startSession(sessionInfo, "CSW.oPDC");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("onPostDialChar %s %s", callId, nextChar);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.onPostDialChar(nextChar);
} else {
// Log.w(this, "onPostDialChar, unknown call id: %s", args.arg1);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void queryRemoteConnectionServices(RemoteServiceCallback callback,
String callingPackage, Session.Info sessionInfo) {
final UserHandle callingUserHandle = Binder.getCallingUserHandle();
Log.startSession(sessionInfo, "CSW.qRCS");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("queryRemoteConnectionServices callingPackage=" + callingPackage);
ConnectionServiceWrapper.this
.queryRemoteConnectionServices(callingUserHandle, callingPackage,
callback);
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setVideoState(String callId, int videoState, Session.Info sessionInfo) {
Log.startSession(sessionInfo, "CSW.sVS");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setVideoState %s %d", callId, videoState);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.setVideoState(videoState);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setIsVoipAudioMode(String callId, boolean isVoip, Session.Info sessionInfo) {
Log.startSession(sessionInfo, "CSW.sIVAM");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setIsVoipAudioMode %s %b", callId, isVoip);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.setIsVoipAudioMode(isVoip);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setAudioRoute(String callId, int audioRoute,
String bluetoothAddress, Session.Info sessionInfo) {
Log.startSession(sessionInfo, "CSW.sAR");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setAudioRoute %s %s", callId,
CallAudioState.audioRouteToString(audioRoute));
mCallsManager.setAudioRoute(audioRoute, bluetoothAddress);
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setStatusHints(String callId, StatusHints statusHints,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, "CSW.sSH");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setStatusHints %s %s", callId, statusHints);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.setStatusHints(statusHints);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void putExtras(String callId, Bundle extras, Session.Info sessionInfo) {
Log.startSession(sessionInfo, "CSW.pE");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
Bundle.setDefusable(extras, true);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.putExtras(Call.SOURCE_CONNECTION_SERVICE, extras);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void removeExtras(String callId, List<String> keys, Session.Info sessionInfo) {
Log.startSession(sessionInfo, "CSW.rE");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("removeExtra %s %s", callId, keys);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.removeExtras(Call.SOURCE_CONNECTION_SERVICE, keys);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setAddress(String callId, Uri address, int presentation,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, "CSW.sA");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setAddress %s %s %d", callId, address, presentation);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.setHandle(address, presentation);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setCallerDisplayName(String callId, String callerDisplayName, int presentation,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, "CSW.sCDN");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setCallerDisplayName %s %s %d", callId, callerDisplayName,
presentation);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.setCallerDisplayName(callerDisplayName, presentation);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setConferenceableConnections(String callId, List<String> conferenceableCallIds,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, "CSW.sCC");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
logIncoming("setConferenceableConnections %s %s", callId,
conferenceableCallIds);
List<Call> conferenceableCalls =
new ArrayList<>(conferenceableCallIds.size());
for (String otherId : conferenceableCallIds) {
Call otherCall = mCallIdMapper.getCall(otherId);
if (otherCall != null && otherCall != call) {
conferenceableCalls.add(otherCall);
}
}
call.setConferenceableCalls(conferenceableCalls);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void addExistingConnection(String callId, ParcelableConnection connection,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, "CSW.aEC");
UserHandle userHandle = Binder.getCallingUserHandle();
// Check that the Calling Package matches PhoneAccountHandle's Component Package
PhoneAccountHandle callingPhoneAccountHandle = connection.getPhoneAccount();
if (callingPhoneAccountHandle != null) {
mAppOpsManager.checkPackage(Binder.getCallingUid(),
callingPhoneAccountHandle.getComponentName().getPackageName());
}
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
// Make sure that the PhoneAccount associated with the incoming
// ParcelableConnection is in fact registered to Telecom and is being called
// from the correct user.
List<PhoneAccountHandle> accountHandles =
mPhoneAccountRegistrar.getCallCapablePhoneAccounts(null /*uriScheme*/,
false /*includeDisabledAccounts*/, userHandle);
PhoneAccountHandle phoneAccountHandle = null;
for (PhoneAccountHandle accountHandle : accountHandles) {
if(accountHandle.equals(callingPhoneAccountHandle)) {
phoneAccountHandle = accountHandle;
}
}
// Allow the Sim call manager account as well, even if its disabled.
if (phoneAccountHandle == null && callingPhoneAccountHandle != null) {
// Search all SIM PhoneAccounts to see if there is a SIM call manager
// associated with any of them and verify that the calling handle matches.
for (PhoneAccountHandle handle :
mPhoneAccountRegistrar.getSimPhoneAccounts(userHandle)) {
int subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(
handle);
PhoneAccountHandle connectionMgrHandle =
mPhoneAccountRegistrar.getSimCallManager(subId, userHandle);
if (callingPhoneAccountHandle.equals(connectionMgrHandle)) {
phoneAccountHandle = connectionMgrHandle;
break;
}
}
}
if (phoneAccountHandle != null) {
logIncoming("addExistingConnection %s %s", callId, connection);
Bundle connectionExtras = connection.getExtras();
String connectIdToCheck = null;
if (connectionExtras != null && connectionExtras
.containsKey(Connection.EXTRA_ORIGINAL_CONNECTION_ID)) {
connectIdToCheck = connectionExtras
.getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID);
} else {
connectIdToCheck = callId;
}
// Check to see if this Connection has already been added.
Call alreadyAddedConnection = mCallsManager
.getAlreadyAddedConnection(connectIdToCheck);
if (alreadyAddedConnection != null
&& mCallIdMapper.getCall(callId) == null) {
mCallIdMapper.addCall(alreadyAddedConnection, callId);
alreadyAddedConnection
.replaceConnectionService(ConnectionServiceWrapper.this);
return;
}
Call existingCall = mCallsManager
.createCallForExistingConnection(callId, connection);
mCallIdMapper.addCall(existingCall, callId);
existingCall.setConnectionService(ConnectionServiceWrapper.this);
} else {
Log.e(this, new RemoteException("The PhoneAccount being used is not " +
"currently registered with Telecom."), "Unable to " +
"addExistingConnection.");
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void onConnectionEvent(String callId, String event, Bundle extras,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, "CSW.oCE");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
Bundle.setDefusable(extras, true);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.onConnectionEvent(event, extras);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void onRttInitiationSuccess(String callId, Session.Info sessionInfo)
throws RemoteException {
}
@Override
public void onRttInitiationFailure(String callId, int reason, Session.Info sessionInfo)
throws RemoteException {
Log.startSession(sessionInfo, "CSW.oRIF");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.onRttConnectionFailure(reason);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void onRttSessionRemotelyTerminated(String callId, Session.Info sessionInfo)
throws RemoteException {
}
@Override
public void onRemoteRttRequest(String callId, Session.Info sessionInfo)
throws RemoteException {
Log.startSession(sessionInfo, "CSW.oRRR");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.onRemoteRttRequest();
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void onPhoneAccountChanged(String callId, PhoneAccountHandle pHandle,
Session.Info sessionInfo) throws RemoteException {
// Check that the Calling Package matches PhoneAccountHandle's Component Package
if (pHandle != null) {
mAppOpsManager.checkPackage(Binder.getCallingUid(),
pHandle.getComponentName().getPackageName());
}
Log.startSession(sessionInfo, "CSW.oPAC");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.setTargetPhoneAccount(pHandle);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void onConnectionServiceFocusReleased(Session.Info sessionInfo)
throws RemoteException {
Log.startSession(sessionInfo, "CSW.oCSFR");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
mConnSvrFocusListener.onConnectionServiceReleased(
ConnectionServiceWrapper.this);
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void setConferenceState(String callId, boolean isConference,
Session.Info sessionInfo) throws RemoteException {
Log.startSession(sessionInfo, "CSW.sCS");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.setConferenceState(isConference);
}
}
} catch (Throwable t) {
Log.e(ConnectionServiceWrapper.this, t, "");
throw t;
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
}
private final Adapter mAdapter = new Adapter();
private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getConnectionId);
private final Map<String, CreateConnectionResponse> mPendingResponses = new HashMap<>();
private Binder2 mBinder = new Binder2();
private IConnectionService mServiceInterface;
private final ConnectionServiceRepository mConnectionServiceRepository;
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final CallsManager mCallsManager;
private final AppOpsManager mAppOpsManager;
private ConnectionServiceFocusManager.ConnectionServiceFocusListener mConnSvrFocusListener;
/**
* 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
* @param callsManager Calls manager
* @param context The context.
* @param userHandle The {@link UserHandle} to use when binding.
*/
ConnectionServiceWrapper(
ComponentName componentName,
ConnectionServiceRepository connectionServiceRepository,
PhoneAccountRegistrar phoneAccountRegistrar,
CallsManager callsManager,
Context context,
TelecomSystem.SyncRoot lock,
UserHandle userHandle) {
super(ConnectionService.SERVICE_INTERFACE, componentName, context, lock, userHandle);
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
});
mPhoneAccountRegistrar = phoneAccountRegistrar;
mCallsManager = callsManager;
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
}
/** See {@link IConnectionService#addConnectionServiceAdapter}. */
private void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
if (isServiceValid("addConnectionServiceAdapter")) {
try {
logOutgoing("addConnectionServiceAdapter %s", adapter);
mServiceInterface.addConnectionServiceAdapter(adapter, Log.getExternalSession());
} catch (RemoteException e) {
}
}
}
/** See {@link IConnectionService#removeConnectionServiceAdapter}. */
private void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
if (isServiceValid("removeConnectionServiceAdapter")) {
try {
logOutgoing("removeConnectionServiceAdapter %s", adapter);
mServiceInterface.removeConnectionServiceAdapter(adapter, Log.getExternalSession());
} catch (RemoteException e) {
}
}
}
/**
* Creates a new connection for a new outgoing call or to attach to an existing incoming call.
*/
@VisibleForTesting
public 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.getIntentExtras();
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());
}
if (call.isIncoming() && mCallsManager.getEmergencyCallHelper()
.getLastEmergencyCallTimeMillis() > 0) {
// Add the last emergency call time to the connection request for incoming calls
if (extras == call.getIntentExtras()) {
extras = (Bundle) extras.clone();
}
extras.putLong(android.telecom.Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS,
mCallsManager.getEmergencyCallHelper().getLastEmergencyCallTimeMillis());
}
// Call is incoming and added because we're handing over from another; tell CS
// that its expected to handover.
if (call.isIncoming() && call.getHandoverSourceCall() != null) {
extras.putBoolean(TelecomManager.EXTRA_IS_HANDOVER, true);
extras.putParcelable(TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT,
call.getHandoverSourceCall().getTargetPhoneAccount());
}
Log.addEvent(call, LogUtils.Events.START_CONNECTION,
Log.piiHandle(call.getHandle()));
ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
.setAccountHandle(call.getTargetPhoneAccount())
.setAddress(call.getHandle())
.setExtras(extras)
.setVideoState(call.getVideoState())
.setTelecomCallId(callId)
// For self-managed incoming calls, if there is another ongoing call Telecom
// is responsible for showing a UI to ask the user if they'd like to answer
// this new incoming call.
.setShouldShowIncomingCallUi(
!mCallsManager.shouldShowSystemIncomingCallUi(call))
.setRttPipeFromInCall(call.getInCallToCsRttPipeForCs())
.setRttPipeToInCall(call.getCsToInCallRttPipeForCs())
.build();
try {
mServiceInterface.createConnection(
call.getConnectionManagerPhoneAccount(),
callId,
connectionRequest,
call.shouldAttachToExistingConnection(),
call.isUnknown(),
Log.getExternalSession());
} catch (RemoteException e) {
Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
mPendingResponses.remove(callId).handleCreateConnectionFailure(
new DisconnectCause(DisconnectCause.ERROR, e.toString()));
}
}
@Override
public void onFailure() {
Log.e(this, new Exception(), "Failure to call %s", getComponentName());
response.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.ERROR));
}
};
mBinder.bind(callback, call);
}
/**
* Notifies the {@link ConnectionService} associated with a {@link Call} that the request to
* create a connection has been denied or failed.
* @param call The call.
*/
void createConnectionFailed(final Call call) {
Log.d(this, "createConnectionFailed(%s) via %s.", call, getComponentName());
BindCallback callback = new BindCallback() {
@Override
public void onSuccess() {
final String callId = mCallIdMapper.getCallId(call);
// If still bound, tell the connection service create connection has failed.
if (callId != null && isServiceValid("createConnectionFailed")) {
Log.addEvent(call, LogUtils.Events.CREATE_CONNECTION_FAILED,
Log.piiHandle(call.getHandle()));
try {
logOutgoing("createConnectionFailed %s", callId);
mServiceInterface.createConnectionFailed(
call.getConnectionManagerPhoneAccount(),
callId,
new ConnectionRequest(
call.getTargetPhoneAccount(),
call.getHandle(),
call.getIntentExtras(),
call.getVideoState(),
callId,
false),
call.isIncoming(),
Log.getExternalSession());
call.setDisconnectCause(new DisconnectCause(DisconnectCause.CANCELED));
call.disconnect();
} catch (RemoteException e) {
}
}
}
@Override
public void onFailure() {
// Binding failed. Oh no.
Log.w(this, "onFailure - could not bind to CS for call %s", call.getId());
}
};
mBinder.bind(callback, call);
}
void handoverFailed(final Call call, final int reason) {
Log.d(this, "handoverFailed(%s) via %s.", call, getComponentName());
BindCallback callback = new BindCallback() {
@Override
public void onSuccess() {
final String callId = mCallIdMapper.getCallId(call);
// If still bound, tell the connection service create connection has failed.
if (callId != null && isServiceValid("handoverFailed")) {
Log.addEvent(call, LogUtils.Events.HANDOVER_FAILED,
Log.piiHandle(call.getHandle()));
try {
mServiceInterface.handoverFailed(
callId,
new ConnectionRequest(
call.getTargetPhoneAccount(),
call.getHandle(),
call.getIntentExtras(),
call.getVideoState(),
callId,
false), reason, Log.getExternalSession());
} catch (RemoteException e) {
}
}
}
@Override
public void onFailure() {
// Binding failed.
Log.w(this, "onFailure - could not bind to CS for call %s",
call.getId());
}
};
mBinder.bind(callback, call);
}
void handoverComplete(final Call call) {
Log.d(this, "handoverComplete(%s) via %s.", call, getComponentName());
BindCallback callback = new BindCallback() {
@Override
public void onSuccess() {
final String callId = mCallIdMapper.getCallId(call);
// If still bound, tell the connection service create connection has failed.
if (callId != null && isServiceValid("handoverComplete")) {
try {
mServiceInterface.handoverComplete(
callId,
Log.getExternalSession());
} catch (RemoteException e) {
}
}
}
@Override
public void onFailure() {
// Binding failed.
Log.w(this, "onFailure - could not bind to CS for call %s",
call.getId());
}
};
mBinder.bind(callback, call);
}
/** @see IConnectionService#abort(String, Session.Info) */
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, Log.getExternalSession());
} catch (RemoteException e) {
}
}
removeCall(call, new DisconnectCause(DisconnectCause.LOCAL));
}
/** @see IConnectionService#silence(String, Session.Info) */
void silence(Call call) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("silence")) {
try {
logOutgoing("silence %s", callId);
mServiceInterface.silence(callId, Log.getExternalSession());
} catch (RemoteException e) {
}
}
}
/** @see IConnectionService#hold(String, Session.Info) */
void hold(Call call) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("hold")) {
try {
logOutgoing("hold %s", callId);
mServiceInterface.hold(callId, Log.getExternalSession());
} catch (RemoteException e) {
}
}
}
/** @see IConnectionService#unhold(String, Session.Info) */
void unhold(Call call) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("unhold")) {
try {
logOutgoing("unhold %s", callId);
mServiceInterface.unhold(callId, Log.getExternalSession());
} catch (RemoteException e) {
}
}
}
/** @see IConnectionService#onCallAudioStateChanged(String, CallAudioState, Session.Info) */
@VisibleForTesting
public void onCallAudioStateChanged(Call activeCall, CallAudioState audioState) {
final String callId = mCallIdMapper.getCallId(activeCall);
if (callId != null && isServiceValid("onCallAudioStateChanged")) {
try {
logOutgoing("onCallAudioStateChanged %s %s", callId, audioState);
mServiceInterface.onCallAudioStateChanged(callId, audioState,
Log.getExternalSession());
} catch (RemoteException e) {
}
}
}
/** @see IConnectionService#disconnect(String, Session.Info) */
void disconnect(Call call) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("disconnect")) {
try {
logOutgoing("disconnect %s", callId);
mServiceInterface.disconnect(callId, Log.getExternalSession());
} catch (RemoteException e) {
}
}
}
/** @see IConnectionService#answer(String, Session.Info) */
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 (VideoProfile.isAudioOnly(videoState)) {
mServiceInterface.answer(callId, Log.getExternalSession());
} else {
mServiceInterface.answerVideo(callId, videoState, Log.getExternalSession());
}
} catch (RemoteException e) {
}
}
}
/** @see IConnectionService#deflect(String, Uri , Session.Info) */
void deflect(Call call, Uri address) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("deflect")) {
try {
logOutgoing("deflect %s", callId);
mServiceInterface.deflect(callId, address, Log.getExternalSession());
} catch (RemoteException e) {
}
}
}
/** @see IConnectionService#reject(String, Session.Info) */
void reject(Call call, boolean rejectWithMessage, String message) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("reject")) {
try {
logOutgoing("reject %s", callId);
if (rejectWithMessage && call.can(
Connection.CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION)) {
mServiceInterface.rejectWithMessage(callId, message, Log.getExternalSession());
} else {
mServiceInterface.reject(callId, Log.getExternalSession());
}
} catch (RemoteException e) {
}
}
}
/** @see IConnectionService#playDtmfTone(String, char, Session.Info) */
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, Log.getExternalSession());
} catch (RemoteException e) {
}
}
}
/** @see IConnectionService#stopDtmfTone(String, Session.Info) */
void stopDtmfTone(Call call) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("stopDtmfTone")) {
try {
logOutgoing("stopDtmfTone %s", callId);
mServiceInterface.stopDtmfTone(callId, Log.getExternalSession());
} 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, new DisconnectCause(DisconnectCause.ERROR));
}
void removeCall(String callId, DisconnectCause disconnectCause) {
CreateConnectionResponse response = mPendingResponses.remove(callId);
if (response != null) {
response.handleCreateConnectionFailure(disconnectCause);
}
mCallIdMapper.removeCall(callId);
}
void removeCall(Call call, DisconnectCause disconnectCause) {
CreateConnectionResponse response = mPendingResponses.remove(mCallIdMapper.getCallId(call));
if (response != null) {
response.handleCreateConnectionFailure(disconnectCause);
}
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, Log.getExternalSession());
} 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, Log.getExternalSession());
} 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, Log.getExternalSession());
} 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, Log.getExternalSession());
} 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, Log.getExternalSession());
} catch (RemoteException ignored) {
}
}
}
void pullExternalCall(Call call) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("pullExternalCall")) {
try {
logOutgoing("pullExternalCall %s", callId);
mServiceInterface.pullExternalCall(callId, Log.getExternalSession());
} catch (RemoteException ignored) {
}
}
}
void sendCallEvent(Call call, String event, Bundle extras) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("sendCallEvent")) {
try {
logOutgoing("sendCallEvent %s %s", callId, event);
mServiceInterface.sendCallEvent(callId, event, extras, Log.getExternalSession());
} catch (RemoteException ignored) {
}
}
}
void onExtrasChanged(Call call, Bundle extras) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("onExtrasChanged")) {
try {
logOutgoing("onExtrasChanged %s %s", callId, extras);
mServiceInterface.onExtrasChanged(callId, extras, Log.getExternalSession());
} catch (RemoteException ignored) {
}
}
}
void startRtt(Call call, ParcelFileDescriptor fromInCall, ParcelFileDescriptor toInCall) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("startRtt")) {
try {
logOutgoing("startRtt: %s %s %s", callId, fromInCall, toInCall);
mServiceInterface.startRtt(callId, fromInCall, toInCall, Log.getExternalSession());
} catch (RemoteException ignored) {
}
}
}
void stopRtt(Call call) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("stopRtt")) {
try {
logOutgoing("stopRtt: %s", callId);
mServiceInterface.stopRtt(callId, Log.getExternalSession());
} catch (RemoteException ignored) {
}
}
}
void respondToRttRequest(
Call call, ParcelFileDescriptor fromInCall, ParcelFileDescriptor toInCall) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("respondToRttRequest")) {
try {
logOutgoing("respondToRttRequest: %s %s %s", callId, fromInCall, toInCall);
mServiceInterface.respondToRttUpgradeRequest(
callId, fromInCall, toInCall, Log.getExternalSession());
} catch (RemoteException ignored) {
}
}
}
/** {@inheritDoc} */
@Override
protected void setServiceInterface(IBinder binder) {
mServiceInterface = IConnectionService.Stub.asInterface(binder);
Log.v(this, "Adding Connection Service Adapter.");
addConnectionServiceAdapter(mAdapter);
}
/** {@inheritDoc} */
@Override
protected void removeServiceInterface() {
Log.v(this, "Removing Connection Service Adapter.");
removeConnectionServiceAdapter(mAdapter);
// 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();
mCallsManager.handleConnectionServiceDeath(this);
mServiceInterface = null;
}
@Override
public void connectionServiceFocusLost() {
// Immediately response to the Telecom that it has released the call resources.
// TODO(mpq): Change back to the default implementation once b/69651192 done.
if (mConnSvrFocusListener != null) {
mConnSvrFocusListener.onConnectionServiceReleased(ConnectionServiceWrapper.this);
}
BindCallback callback = new BindCallback() {
@Override
public void onSuccess() {
try {
mServiceInterface.connectionServiceFocusLost(Log.getExternalSession());
} catch (RemoteException ignored) {
Log.d(this, "failed to inform the focus lost event");
}
}
@Override
public void onFailure() {}
};
mBinder.bind(callback, null /* null call */);
}
@Override
public void connectionServiceFocusGained() {
BindCallback callback = new BindCallback() {
@Override
public void onSuccess() {
try {
mServiceInterface.connectionServiceFocusGained(Log.getExternalSession());
} catch (RemoteException ignored) {
Log.d(this, "failed to inform the focus gained event");
}
}
@Override
public void onFailure() {}
};
mBinder.bind(callback, null /* null call */);
}
@Override
public void setConnectionServiceFocusListener(
ConnectionServiceFocusManager.ConnectionServiceFocusListener listener) {
mConnSvrFocusListener = listener;
}
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
Call foundCall = mCallIdMapper.getCall(callId);
if (foundCall != null) {
// The post-dial digits are created when the call is first created. Normally
// the ConnectionService is responsible for stripping them from the address, but
// since a failed connection will not have done this, we could end up with duplicate
// post-dial digits.
foundCall.clearPostDialDigits();
}
removeCall(callId, connection.getDisconnectCause());
} 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(
new DisconnectCause(DisconnectCause.ERROR, "CS_DEATH"));
}
}
mCallIdMapper.clear();
if (mConnSvrFocusListener != null) {
mConnSvrFocusListener.onConnectionServiceDeath(this);
}
}
private void logIncoming(String msg, Object... params) {
Log.d(this, "ConnectionService -> Telecom[" + mComponentName.flattenToShortString() + "]: "
+ msg, params);
}
private void logOutgoing(String msg, Object... params) {
Log.d(this, "Telecom -> ConnectionService[" + mComponentName.flattenToShortString() + "]: "
+ msg, params);
}
private void queryRemoteConnectionServices(final UserHandle userHandle,
final String callingPackage, final RemoteServiceCallback callback) {
boolean isCallerConnectionManager = false;
// For each Sim ConnectionService, use its subid to find the correct connection manager for
// that ConnectionService; return those Sim ConnectionServices which match the connection
// manager.
final Set<ConnectionServiceWrapper> simServices = Collections.newSetFromMap(
new ConcurrentHashMap<ConnectionServiceWrapper, Boolean>(8, 0.9f, 1));
for (PhoneAccountHandle handle : mPhoneAccountRegistrar.getSimPhoneAccounts(userHandle)) {
int subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(handle);
PhoneAccountHandle connectionMgrHandle = mPhoneAccountRegistrar.getSimCallManager(subId,
userHandle);
if (connectionMgrHandle == null
|| !connectionMgrHandle.getComponentName().getPackageName().equals(
callingPackage)) {
Log.v(this, "queryRemoteConnectionServices: callingPackage=%s skipped; "
+ "doesn't match mgr %s for tfa %s",
callingPackage, connectionMgrHandle, handle);
} else {
isCallerConnectionManager = true;
}
ConnectionServiceWrapper service = mConnectionServiceRepository.getService(
handle.getComponentName(), handle.getUserHandle());
if (service != null) {
simServices.add(service);
}
}
// Bail early if the caller isn't the sim connection mgr.
if (!isCallerConnectionManager) {
Log.d(this, "queryRemoteConnectionServices: none; not sim call mgr.");
noRemoteServices(callback);
return;
}
final List<ComponentName> simServiceComponentNames = new ArrayList<>();
final List<IBinder> simServiceBinders = new ArrayList<>();
Log.i(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, "queryRemoteConnectionServices: Adding simService %s",
currentSimService.getComponentName());
if (currentSimService.mServiceInterface == null) {
// The remote ConnectionService died, so do not add it.
// We will still perform maybeComplete() and notify the caller with an empty
// list of sim services via maybeComplete().
Log.w(this, "queryRemoteConnectionServices: simService %s died - Skipping.",
currentSimService.getComponentName());
} else {
simServiceComponentNames.add(currentSimService.getComponentName());
simServiceBinders.add(currentSimService.mServiceInterface.asBinder());
}
maybeComplete();
}
@Override
public void onFailure() {
Log.d(this, "queryRemoteConnectionServices: 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);
}
}
}, null);
}
}
private void setRemoteServices(
RemoteServiceCallback callback,
List<ComponentName> componentNames,
List<IBinder> binders) {
try {
callback.onResult(componentNames, binders);
} catch (RemoteException e) {
Log.e(this, e, "setRemoteServices: Contacting ConnectionService %s",
ConnectionServiceWrapper.this.getComponentName());
}
}
private void noRemoteServices(RemoteServiceCallback callback) {
setRemoteServices(callback, Collections.EMPTY_LIST, Collections.EMPTY_LIST);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[ConnectionServiceWrapper componentName=");
sb.append(mComponentName);
sb.append("]");
return sb.toString();
}
}