blob: adeb3113d3272e6cc1e503ebb4f7407597542e14 [file] [log] [blame]
/*
* Copyright (C) 2018 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.callredirection;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.telecom.CallRedirectionService;
import android.telecom.GatewayInfo;
import android.telecom.Log;
import android.telecom.Logging.Runnable;
import android.telecom.PhoneAccountHandle;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telecom.ICallRedirectionAdapter;
import com.android.internal.telecom.ICallRedirectionService;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.LogUtils;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.Timeouts;
/**
* A single instance of call redirection processor that handles the call redirection with
* user-defined {@link CallRedirectionService} and carrier {@link CallRedirectionService} for a
* single call.
*
* A user-defined call redirection will be performed firstly and a carrier call redirection will be
* performed after that; there will be a total of two call redirection cycles.
*
* A call redirection cycle is a cycle:
* 1) Telecom requests a call redirection of a call with a specific {@link CallRedirectionService},
* 2) Telecom receives the response either from a specific {@link CallRedirectionService} or from
* the timeout.
*
* Telecom should return to {@link CallsManager} at the end of current call redirection
* cycle, if
* 1) {@link CallRedirectionService} sends {@link CallRedirectionService#cancelCall()} response
* before timeout;
* or 2) Telecom finishes call redirection with carrier {@link CallRedirectionService}.
*/
public class CallRedirectionProcessor implements CallRedirectionCallback {
private class CallRedirectionAttempt {
private final ComponentName mComponentName;
private final String mServiceType;
private ServiceConnection mConnection;
private ICallRedirectionService mService;
private CallRedirectionAttempt(ComponentName componentName, String serviceType) {
mComponentName = componentName;
mServiceType = serviceType;
}
private void process() {
Intent intent = new Intent(CallRedirectionService.SERVICE_INTERFACE)
.setComponent(mComponentName);
ServiceConnection connection = new CallRedirectionServiceConnection();
if (mContext.bindServiceAsUser(
intent,
connection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
| Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
UserHandle.CURRENT)) {
Log.d(this, "bindService, found " + mServiceType + " call redirection service,"
+ " waiting for it to connect");
mConnection = connection;
}
}
private void onServiceBound(ICallRedirectionService service) {
mService = service;
try {
// Telecom does not perform user interactions for carrier call redirection.
mService.placeCall(new CallRedirectionAdapter(), mProcessedDestinationUri,
mPhoneAccountHandle, mAllowInteractiveResponse
&& mServiceType.equals(SERVICE_TYPE_USER_DEFINED));
Log.addEvent(mCall, mServiceType.equals(SERVICE_TYPE_USER_DEFINED)
? LogUtils.Events.REDIRECTION_SENT_USER
: LogUtils.Events.REDIRECTION_SENT_CARRIER, mComponentName);
Log.d(this, "Requested placeCall with [Destination Uri] "
+ Log.pii(mProcessedDestinationUri)
+ " [phoneAccountHandle]" + mPhoneAccountHandle);
} catch (RemoteException e) {
Log.e(this, e, "Failed to request with the found " + mServiceType + " call"
+ " redirection service");
finishCallRedirection();
}
}
private void finishCallRedirection() {
if (((mServiceType.equals(SERVICE_TYPE_CARRIER)) && mIsCarrierRedirectionPending)
|| ((mServiceType.equals(SERVICE_TYPE_USER_DEFINED))
&& mIsUserDefinedRedirectionPending)) {
if (mConnection != null) {
// We still need to call unbind even if the service disconnected.
mContext.unbindService(mConnection);
mConnection = null;
}
mService = null;
onCallRedirectionComplete(mCall);
}
}
public void notifyTimeout() {
if (mService != null) {
try {
mService.notifyTimeout();
} catch (RemoteException e) {
Log.e(this, e, "Failed to notify call redirection timed out to "
+ mServiceType + " call redirection service");
}
}
}
private class CallRedirectionServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
Log.startSession("CRSC.oSC");
try {
synchronized (mTelecomLock) {
Log.addEvent(mCall, mServiceType.equals(SERVICE_TYPE_USER_DEFINED)
? LogUtils.Events.REDIRECTION_BOUND_USER
: LogUtils.Events.REDIRECTION_BOUND_CARRIER, componentName);
onServiceBound(ICallRedirectionService.Stub.asInterface(service));
}
} finally {
Log.endSession();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.startSession("CRSC.oSD");
try {
synchronized (mTelecomLock) {
finishCallRedirection();
}
} finally {
Log.endSession();
}
}
}
private class CallRedirectionAdapter extends ICallRedirectionAdapter.Stub {
@Override
public void cancelCall() {
Log.startSession("CRA.cC");
long token = Binder.clearCallingIdentity();
try {
synchronized (mTelecomLock) {
Log.d(this, "Received cancelCall from " + mServiceType + " call"
+ " redirection service");
mShouldCancelCall = true;
finishCallRedirection();
}
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void placeCallUnmodified() {
Log.startSession("CRA.pCU");
long token = Binder.clearCallingIdentity();
try {
synchronized (mTelecomLock) {
Log.d(this, "Received placeCallUnmodified from " + mServiceType + " call"
+ " redirection service");
finishCallRedirection();
}
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
public void redirectCall(Uri gatewayUri, PhoneAccountHandle targetPhoneAccount,
boolean confirmFirst) {
Log.startSession("CRA.rC");
long token = Binder.clearCallingIdentity();
try {
synchronized (mTelecomLock) {
mRedirectionGatewayInfo = mCallRedirectionProcessorHelper
.getGatewayInfoFromGatewayUri(mComponentName.getPackageName(),
gatewayUri, mDestinationUri, mPostDialDigits);
mPhoneAccountHandle = targetPhoneAccount;
// If carrier redirects call, we should skip to notify users about
// the user-defined call redirection service.
mUiAction = (confirmFirst && mServiceType.equals(SERVICE_TYPE_USER_DEFINED)
&& mAllowInteractiveResponse)
? UI_TYPE_USER_DEFINED_ASK_FOR_CONFIRM : UI_TYPE_NO_ACTION;
Log.d(this, "Received redirectCall with [gatewayUri]"
+ Log.pii(gatewayUri) + " [phoneAccountHandle]"
+ mPhoneAccountHandle + "[confirmFirst]" + confirmFirst + " from "
+ mServiceType + " call redirection service");
finishCallRedirection();
}
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
}
}
private final Context mContext;
private final CallsManager mCallsManager;
private final Call mCall;
private final boolean mAllowInteractiveResponse;
private GatewayInfo mRedirectionGatewayInfo;
private final boolean mSpeakerphoneOn;
private final int mVideoState;
private final Timeouts.Adapter mTimeoutsAdapter;
private final TelecomSystem.SyncRoot mTelecomLock;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private CallRedirectionAttempt mAttempt;
private CallRedirectionProcessorHelper mCallRedirectionProcessorHelper;
public static final String SERVICE_TYPE_CARRIER = "carrier";
public static final String SERVICE_TYPE_USER_DEFINED = "user_defined";
public static final String UI_TYPE_NO_ACTION = "no_action";
public static final String UI_TYPE_USER_DEFINED_TIMEOUT = "user_defined_timeout";
public static final String UI_TYPE_USER_DEFINED_ASK_FOR_CONFIRM
= "user_defined_ask_for_confirm";
private PhoneAccountHandle mPhoneAccountHandle;
private Uri mDestinationUri;
/**
* Try to send the implemented service with processed destination uri by formatting it to E.164
* and removing post dial digits.
*/
private Uri mProcessedDestinationUri;
/**
* The post dial digits which were removed from {@link #mDestinationUri} when determining
* {@link #mProcessedDestinationUri}.
*/
private String mPostDialDigits;
/**
* Indicates if Telecom should cancel the call when the whole call redirection finishes.
*/
private boolean mShouldCancelCall = false;
/**
* Indicates Telecom should handle different types of UI if need.
*/
private String mUiAction = UI_TYPE_NO_ACTION;
/**
* Indicates if Telecom is waiting for a callback from a user-defined
* {@link CallRedirectionService}.
*/
private boolean mIsUserDefinedRedirectionPending = false;
/**
* Indicates if Telecom is waiting for a callback from a carrier
* {@link CallRedirectionService}.
*/
private boolean mIsCarrierRedirectionPending = false;
public CallRedirectionProcessor(
Context context,
CallsManager callsManager,
Call call,
Uri handle,
PhoneAccountRegistrar phoneAccountRegistrar,
GatewayInfo gatewayInfo,
boolean speakerphoneOn,
int videoState) {
mContext = context;
mCallsManager = callsManager;
mCall = call;
mDestinationUri = handle;
mPhoneAccountHandle = call.getTargetPhoneAccount();
mRedirectionGatewayInfo = gatewayInfo;
mSpeakerphoneOn = speakerphoneOn;
mVideoState = videoState;
mTimeoutsAdapter = callsManager.getTimeoutsAdapter();
mTelecomLock = callsManager.getLock();
/**
* The current rule to decide whether the implemented {@link CallRedirectionService} should
* allow interactive responses with users is only based on whether it is in car mode.
*/
mAllowInteractiveResponse = !callsManager.getSystemStateHelper().isCarModeOrProjectionActive();
mCallRedirectionProcessorHelper = new CallRedirectionProcessorHelper(
context, callsManager, phoneAccountRegistrar);
mProcessedDestinationUri = mCallRedirectionProcessorHelper.formatNumberForRedirection(
mDestinationUri);
mPostDialDigits = mCallRedirectionProcessorHelper.getPostDialDigits(mDestinationUri);
}
@Override
public void onCallRedirectionComplete(Call call) {
// synchronized on mTelecomLock to enter into Telecom.
mHandler.post(new Runnable("CRP.oCRC", mTelecomLock) {
@Override
public void loggedRun() {
if (mIsUserDefinedRedirectionPending) {
Log.addEvent(mCall, LogUtils.Events.REDIRECTION_COMPLETED_USER);
mIsUserDefinedRedirectionPending = false;
if (mShouldCancelCall) {
mCallsManager.onCallRedirectionComplete(mCall, mDestinationUri,
mPhoneAccountHandle, mRedirectionGatewayInfo, mSpeakerphoneOn,
mVideoState, mShouldCancelCall, mUiAction);
} else {
performCarrierCallRedirection();
}
}
if (mIsCarrierRedirectionPending) {
Log.addEvent(mCall, LogUtils.Events.REDIRECTION_COMPLETED_CARRIER);
mIsCarrierRedirectionPending = false;
mCallsManager.onCallRedirectionComplete(mCall, mDestinationUri,
mPhoneAccountHandle, mRedirectionGatewayInfo, mSpeakerphoneOn,
mVideoState, mShouldCancelCall, mUiAction);
}
}
}.prepare());
}
/**
* The entry to perform call redirection of the call from (@link CallsManager)
*/
public void performCallRedirection() {
// If the Gateway Info is set with intent, only request with carrier call redirection.
if (mRedirectionGatewayInfo != null) {
performCarrierCallRedirection();
} else {
performUserDefinedCallRedirection();
}
}
private void performUserDefinedCallRedirection() {
Log.d(this, "performUserDefinedCallRedirection");
ComponentName componentName =
mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService();
if (componentName != null) {
mAttempt = new CallRedirectionAttempt(componentName, SERVICE_TYPE_USER_DEFINED);
mAttempt.process();
mIsUserDefinedRedirectionPending = true;
processTimeoutForCallRedirection(SERVICE_TYPE_USER_DEFINED);
} else {
Log.i(this, "There are no user-defined call redirection services installed on this"
+ " device.");
performCarrierCallRedirection();
}
}
private void performCarrierCallRedirection() {
Log.d(this, "performCarrierCallRedirection");
ComponentName componentName =
mCallRedirectionProcessorHelper.getCarrierCallRedirectionService(
mPhoneAccountHandle);
if (componentName != null) {
mAttempt = new CallRedirectionAttempt(componentName, SERVICE_TYPE_CARRIER);
mAttempt.process();
mIsCarrierRedirectionPending = true;
processTimeoutForCallRedirection(SERVICE_TYPE_CARRIER);
} else {
Log.i(this, "There are no carrier call redirection services installed on this"
+ " device.");
mCallsManager.onCallRedirectionComplete(mCall, mDestinationUri,
mPhoneAccountHandle, mRedirectionGatewayInfo, mSpeakerphoneOn, mVideoState,
mShouldCancelCall, mUiAction);
}
}
private void processTimeoutForCallRedirection(String serviceType) {
long timeout = serviceType.equals(SERVICE_TYPE_USER_DEFINED) ?
mTimeoutsAdapter.getUserDefinedCallRedirectionTimeoutMillis(
mContext.getContentResolver()) : mTimeoutsAdapter
.getCarrierCallRedirectionTimeoutMillis(mContext.getContentResolver());
mHandler.postDelayed(new Runnable("CRP.pTFCR", null) {
@Override
public void loggedRun() {
boolean isCurrentRedirectionPending =
serviceType.equals(SERVICE_TYPE_USER_DEFINED) ?
mIsUserDefinedRedirectionPending : mIsCarrierRedirectionPending;
if (isCurrentRedirectionPending) {
Log.i(this, serviceType + " call redirection has timed out.");
Log.addEvent(mCall, serviceType.equals(SERVICE_TYPE_USER_DEFINED)
? LogUtils.Events.REDIRECTION_TIMED_OUT_USER
: LogUtils.Events.REDIRECTION_TIMED_OUT_CARRIER);
mAttempt.notifyTimeout();
if (serviceType.equals(SERVICE_TYPE_USER_DEFINED)) {
mUiAction = UI_TYPE_USER_DEFINED_TIMEOUT;
mShouldCancelCall = true;
}
onCallRedirectionComplete(mCall);
}
}
}.prepare(), timeout);
}
/**
* Checks if Telecom can make call redirection with any available call redirection service.
*
* @return {@code true} if it can; {@code false} otherwise.
*/
public boolean canMakeCallRedirectionWithService() {
boolean canMakeCallRedirectionWithService =
mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService() != null
|| mCallRedirectionProcessorHelper.getCarrierCallRedirectionService(
mPhoneAccountHandle) != null;
Log.i(this, "Can make call redirection with any available service: "
+ canMakeCallRedirectionWithService);
return canMakeCallRedirectionWithService;
}
/**
* Returns the handler, for testing purposes.
*/
@VisibleForTesting
public Handler getHandler() {
return mHandler;
}
/**
* Set CallRedirectionProcessorHelper for testing purposes.
*/
@VisibleForTesting
public void setCallRedirectionServiceHelper(
CallRedirectionProcessorHelper callRedirectionProcessorHelper) {
mCallRedirectionProcessorHelper = callRedirectionProcessorHelper;
}
}