Merge "telecom register-sim-phone-account for secondary users."
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 122c2de..be9b1dd 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -299,6 +299,7 @@
new ConcurrentHashMap<CallsManagerListener, Boolean>(16, 0.9f, 1));
private final HeadsetMediaButton mHeadsetMediaButton;
private final WiredHeadsetManager mWiredHeadsetManager;
+ private final SystemStateHelper mSystemStateHelper;
private final BluetoothRouteManager mBluetoothRouteManager;
private final DockManager mDockManager;
private final TtyManager mTtyManager;
@@ -436,6 +437,7 @@
mMissedCallNotifier = missedCallNotifier;
StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this);
mWiredHeadsetManager = wiredHeadsetManager;
+ mSystemStateHelper = systemStateHelper;
mDefaultDialerCache = defaultDialerCache;
mBluetoothRouteManager = bluetoothManager;
mDockManager = new DockManager(context);
@@ -1605,6 +1607,57 @@
return targetPhoneAccount != null && targetPhoneAccount.isSelfManaged();
}
+ public void onCallRedirectionComplete(Call call, Uri handle,
+ PhoneAccountHandle phoneAccountHandle,
+ GatewayInfo gatewayInfo, boolean speakerphoneOn,
+ int videoState, boolean shouldCancelCall,
+ String uiAction) {
+ Log.i(this, "onCallRedirectionComplete for Call %s with handle %s" +
+ " and phoneAccountHandle %s", call, handle, phoneAccountHandle);
+
+ boolean endEarly = false;
+ String disconnectReason = "";
+
+ if (shouldCancelCall) {
+ Log.w(this, "onCallRedirectionComplete: call is canceled");
+ endEarly = true;
+ disconnectReason = "Canceled from Call Redirection Service";
+ // TODO show UI uiAction is CallRedirectionProcessor#UI_TYPE_USER_DEFINED_TIMEOUT
+ } else if (handle == null) {
+ Log.w(this, "onCallRedirectionComplete: handle is null");
+ endEarly = true;
+ disconnectReason = "Null handle from Call Redirection Service";
+ } else if (phoneAccountHandle == null) {
+ Log.w(this, "onCallRedirectionComplete: phoneAccountHandle is null");
+ endEarly = true;
+ disconnectReason = "Null phoneAccountHandle from Call Redirection Service";
+ } else if (mPhoneNumberUtilsAdapter.isPotentialLocalEmergencyNumber(mContext,
+ handle.getSchemeSpecificPart())) {
+ Log.w(this, "onCallRedirectionComplete: emergency number %s is redirected from Call"
+ + " Redirection Service", handle.getSchemeSpecificPart());
+ endEarly = true;
+ disconnectReason = "Emergency number is redirected from Call Redirection Service";
+ }
+ if (endEarly) {
+ if (call != null) {
+ call.disconnect(disconnectReason);
+ }
+ return;
+ }
+
+ // If this call is already disconnected then we have nothing more to do.
+ if (call.isDisconnected()) {
+ Log.w(this, "onCallRedirectionComplete: Call has already been disconnected,"
+ + " ignore the call redirection %s", call);
+ return;
+ }
+
+ // TODO show UI uiAction is CallRedirectionProcessor#UI_TYPE_USER_DEFINED_ASK_FOR_CONFIRM
+
+ call.setTargetPhoneAccount(phoneAccountHandle);
+ placeOutgoingCall(call, handle, gatewayInfo, speakerphoneOn, videoState);
+ }
+
/**
* Attempts to issue/connect the specified call.
*
@@ -2589,7 +2642,8 @@
*
* @return The {@link PhoneAccountRegistrar}.
*/
- PhoneAccountRegistrar getPhoneAccountRegistrar() {
+ @VisibleForTesting
+ public PhoneAccountRegistrar getPhoneAccountRegistrar() {
return mPhoneAccountRegistrar;
}
@@ -3386,6 +3440,14 @@
return mLock;
}
+ public Timeouts.Adapter getTimeoutsAdapter() {
+ return mTimeoutsAdapter;
+ }
+
+ public SystemStateHelper getSystemStateHelper() {
+ return mSystemStateHelper;
+ }
+
private void reloadMissedCallsOfUser(UserHandle userHandle) {
mMissedCallNotifier.reloadFromDatabase(mCallerInfoLookupHelper,
new MissedCallNotifier.CallInfoFactory(), userHandle);
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 5571fcf..ee48f77 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -1226,7 +1226,8 @@
serviceInfo.metaData.getBoolean(
TelecomManager.METADATA_INCLUDE_SELF_MANAGED_CALLS, false);
- int currentType = getInCallServiceType(entry.serviceInfo, packageManager);
+ int currentType = getInCallServiceType(entry.serviceInfo, packageManager,
+ packageName);
if (requestedType == 0 || requestedType == currentType) {
if (requestedType == IN_CALL_SERVICE_TYPE_NON_UI) {
// We enforce the rule that self-managed calls are not supported by non-ui
@@ -1250,7 +1251,8 @@
/**
* Returns the type of InCallService described by the specified serviceInfo.
*/
- private int getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager) {
+ private int getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager,
+ String packageName) {
// Verify that the InCallService requires the BIND_INCALL_SERVICE permission which
// enforces that only Telecom can bind to it.
boolean hasServiceBindPermission = serviceInfo.permission != null &&
@@ -1268,12 +1270,12 @@
}
// Check to see if the service holds permissions or metadata for third party apps.
- boolean hasInCallServiceUIMetadata = serviceInfo.metaData != null &&
- serviceInfo.metaData.containsKey(TelecomManager.METADATA_IN_CALL_SERVICE_UI);
+ boolean isUIService = serviceInfo.metaData != null &&
+ serviceInfo.metaData.getBoolean(TelecomManager.METADATA_IN_CALL_SERVICE_UI);
boolean isThirdPartyCompanionApp = packageManager.checkPermission(
Manifest.permission.CALL_COMPANION_APP,
serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED &&
- !hasInCallServiceUIMetadata;
+ !isUIService;
// Check to see if the service is a car-mode UI type by checking that it has the
// CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the
@@ -1286,16 +1288,17 @@
TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false) &&
(hasControlInCallPermission || isThirdPartyCompanionApp);
if (isCarModeUIService) {
- return IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
+ // ThirdPartyInCallService shouldn't be used when role manager hasn't assigned any car
+ // mode role holders, i.e. packageName is null.
+ if (isUIService || (isThirdPartyCompanionApp && packageName != null)) {
+ return IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
+ }
}
// Check to see that it is the default dialer package
boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName,
mDefaultDialerCache.getDefaultDialerApplication(
mCallsManager.getCurrentUserHandle().getIdentifier()));
- boolean isUIService = serviceInfo.metaData != null &&
- serviceInfo.metaData.getBoolean(
- TelecomManager.METADATA_IN_CALL_SERVICE_UI, false);
if (isDefaultDialerPackage && isUIService) {
return IN_CALL_SERVICE_TYPE_DIALER_UI;
}
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index d0afe28..5864ce0 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -16,10 +16,13 @@
package com.android.server.telecom;
+import android.annotation.Nullable;
+import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.ToneGenerator;
+import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.telecom.Log;
@@ -87,33 +90,51 @@
public static class MediaPlayerAdapterImpl implements MediaPlayerAdapter {
private MediaPlayer mMediaPlayer;
- public MediaPlayerAdapterImpl(MediaPlayer mediaPlayer) {
+ /**
+ * Create new media player adapter backed by a real mediaplayer.
+ * Note: Its possible for the mediaplayer to be null if
+ * {@link MediaPlayer#create(Context, Uri)} fails for some reason; in this case we can
+ * continue but not bother playing the audio.
+ * @param mediaPlayer The media player.
+ */
+ public MediaPlayerAdapterImpl(@Nullable MediaPlayer mediaPlayer) {
mMediaPlayer = mediaPlayer;
}
@Override
public void setLooping(boolean isLooping) {
- mMediaPlayer.setLooping(isLooping);
+ if (mMediaPlayer != null) {
+ mMediaPlayer.setLooping(isLooping);
+ }
}
@Override
public void setOnCompletionListener(MediaPlayer.OnCompletionListener listener) {
- mMediaPlayer.setOnCompletionListener(listener);
+ if (mMediaPlayer != null) {
+ mMediaPlayer.setOnCompletionListener(listener);
+ }
}
@Override
public void start() {
- mMediaPlayer.start();
+ if (mMediaPlayer != null) {
+ mMediaPlayer.start();
+ }
}
@Override
public void release() {
- mMediaPlayer.release();
+ if (mMediaPlayer != null) {
+ mMediaPlayer.release();
+ }
}
@Override
public int getDuration() {
- return mMediaPlayer.getDuration();
+ if (mMediaPlayer != null) {
+ return mMediaPlayer.getDuration();
+ }
+ return 0;
}
}
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index 3797c68..440bb10 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -37,6 +37,7 @@
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.callredirection.CallRedirectionProcessor;
// TODO: Needed for move to system service: import com.android.internal.R;
@@ -240,6 +241,8 @@
boolean callImmediately = false;
// True for all managed calls, false for self-managed calls.
boolean sendNewOutgoingCallBroadcast = true;
+ // True for requesting call redirection, false for not requesting it.
+ boolean requestCallRedirection = true;
Uri callingAddress = handle;
if (!isSelfManaged) {
@@ -294,6 +297,7 @@
// Self-managed call.
callImmediately = true;
sendNewOutgoingCallBroadcast = false;
+ requestCallRedirection = false;
Log.i(this, "Skipping NewOutgoingCallBroadcast for self-managed call.");
}
@@ -312,10 +316,33 @@
// initiate the call again because of the presence of the EXTRA_ALREADY_CALLED extra.
}
+ boolean callRedirectionWithService = false;
+ if (requestCallRedirection) {
+ CallRedirectionProcessor callRedirectionProcessor = new CallRedirectionProcessor(
+ mContext, mCallsManager, mCall, callingAddress,
+ mCallsManager.getPhoneAccountRegistrar(),
+ getGateWayInfoFromIntent(intent, handle),
+ intent.getBooleanExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE,
+ false),
+ intent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+ VideoProfile.STATE_AUDIO_ONLY));
+ /**
+ * If there is an available {@link android.telecom.CallRedirectionService}, use the
+ * {@link CallRedirectionProcessor} to perform call redirection instead of using
+ * broadcasting.
+ */
+ callRedirectionWithService = callRedirectionProcessor
+ .canMakeCallRedirectionWithService();
+ if (callRedirectionWithService) {
+ callRedirectionProcessor.performCallRedirection();
+ }
+ }
+
if (sendNewOutgoingCallBroadcast) {
UserHandle targetUser = mCall.getInitiatingUser();
Log.i(this, "Sending NewOutgoingCallBroadcast for %s to %s", mCall, targetUser);
- broadcastIntent(intent, number, !callImmediately, targetUser);
+ broadcastIntent(intent, number,
+ !callImmediately && !callRedirectionWithService, targetUser);
}
return DisconnectCause.NOT_DISCONNECTED;
}
diff --git a/src/com/android/server/telecom/RoleManagerAdapterImpl.java b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
index 873a8d1..33055a8 100644
--- a/src/com/android/server/telecom/RoleManagerAdapterImpl.java
+++ b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
@@ -26,15 +26,11 @@
import java.util.stream.Collectors;
public class RoleManagerAdapterImpl implements RoleManagerAdapter {
- // TODO: replace with actual role manager const.
private static final String ROLE_CALL_REDIRECTION_APP = "android.app.role.PROXY_CALLING_APP";
- // TODO: replace with actual role manager const.
- private static final String ROLE_CAR_MODE_DIALER = "android.app.role.ROLE_CAR_MODE_DIALER";
- // TODO: replace with actual role manager const.
+ private static final String ROLE_CAR_MODE_DIALER = "android.app.role.CAR_MODE_DIALER_APP";
private static final String ROLE_CALL_SCREENING = "android.app.role.CALL_SCREENING_APP";
- // TODO: replace with actual role manager const.
private static final String ROLE_CALL_COMPANION_APP =
- "android.app.role.ROLE_CALL_COMPANION_APP";
+ "android.app.role.CALL_COMPANION_APP";
private String mOverrideDefaultCallRedirectionApp = null;
private String mOverrideDefaultCallScreeningApp = null;
@@ -165,7 +161,7 @@
pw.println();
pw.print("DefaultCarModeDialerApp: ");
- if (mOverrideDefaultCallScreeningApp != null) {
+ if (mOverrideDefaultCarModeApp != null) {
pw.print("(override ");
pw.print(mOverrideDefaultCarModeApp);
pw.print(") ");
@@ -174,7 +170,7 @@
pw.println();
pw.print("DefaultCallCompanionApps: ");
- if (mOverrideDefaultCallScreeningApp != null) {
+ if (mOverrideCallCompanionApps != null) {
pw.print("(override ");
pw.print(mOverrideCallCompanionApps.stream().collect(Collectors.joining(", ")));
pw.print(") ");
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index e927694..37f9363 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -187,7 +187,7 @@
*/
public static long getUserDefinedCallRedirectionTimeoutMillis(ContentResolver contentResolver) {
return get(contentResolver, "user_defined_call_redirection_timeout",
- 3000L /* 3 seconds */);
+ 5000L /* 5 seconds */);
}
/**
@@ -196,6 +196,6 @@
* @param contentResolver The content resolved.
*/
public static long getCarrierCallRedirectionTimeoutMillis(ContentResolver contentResolver) {
- return get(contentResolver, "carrier_call_redirection_timeout", 3000L /* 3 seconds */);
+ return get(contentResolver, "carrier_call_redirection_timeout", 5000L /* 5 seconds */);
}
}
diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
index abb87e1..28ecbc7 100644
--- a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
+++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
@@ -16,26 +16,22 @@
package com.android.server.telecom.callredirection;
-import android.Manifest;
-import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
-import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.PersistableBundle;
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 android.telephony.CarrierConfigManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telecom.ICallRedirectionAdapter;
import com.android.internal.telecom.ICallRedirectionService;
@@ -46,8 +42,6 @@
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.Timeouts;
-import java.util.List;
-
/**
* A single instance of call redirection processor that handles the call redirection with
* user-defined {@link CallRedirectionService} and carrier {@link CallRedirectionService} for a
@@ -98,10 +92,14 @@
private void onServiceBound(ICallRedirectionService service) {
mService = service;
try {
- mService.placeCall(new CallRedirectionAdapter(), mHandle, mPhoneAccountHandle);
+ mHandle = mCallRedirectionProcessorHelper.formatNumberForRedirection(mHandle);
+ // Telecom does not perform user interactions for carrier call redirection.
+ mService.placeCall(new CallRedirectionAdapter(), mHandle, 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);
+ : LogUtils.Events.REDIRECTION_SENT_CARRIER, mComponentName);
Log.d(this, "Requested placeCall with [handle]" + Log.pii(mHandle)
+ " [phoneAccountHandle]" + mPhoneAccountHandle);
} catch (RemoteException e) {
@@ -189,16 +187,20 @@
}
@Override
- public void redirectCall(Uri handle, PhoneAccountHandle targetPhoneAccount) {
+ public void redirectCall(Uri handle, PhoneAccountHandle targetPhoneAccount,
+ boolean confirmFirst) {
Log.startSession("CRA.rC");
long token = Binder.clearCallingIdentity();
try {
synchronized (mTelecomLock) {
mHandle = handle;
mPhoneAccountHandle = targetPhoneAccount;
+ mUiAction = (confirmFirst && mServiceType.equals(SERVICE_TYPE_USER_DEFINED)
+ && mAllowInteractiveResponse)
+ ? UI_TYPE_USER_DEFINED_ASK_FOR_CONFIRM : mUiAction;
Log.d(this, "Received redirectCall with [handle]" + Log.pii(mHandle)
+ " [phoneAccountHandle]" + mPhoneAccountHandle + " from "
- + mServiceType + " call" + " redirection service");
+ + mServiceType + " call redirection service");
finishCallRedirection();
}
} finally {
@@ -212,14 +214,23 @@
private final Context mContext;
private final CallsManager mCallsManager;
private final Call mCall;
- private final PhoneAccountRegistrar mPhoneAccountRegistrar;
+ private final boolean mAllowInteractiveResponse;
+ private final GatewayInfo mGatewayInfo;
+ 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 mHandle;
@@ -229,6 +240,10 @@
*/
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}.
*/
@@ -243,19 +258,28 @@
Context context,
CallsManager callsManager,
Call call,
- PhoneAccountRegistrar phoneAccountRegistrar,
Uri handle,
- PhoneAccountHandle phoneAccountHandle,
- Timeouts.Adapter timeoutsAdapter,
- TelecomSystem.SyncRoot lock) {
+ PhoneAccountRegistrar phoneAccountRegistrar,
+ GatewayInfo gatewayInfo,
+ boolean speakerphoneOn,
+ int videoState) {
mContext = context;
mCallsManager = callsManager;
mCall = call;
- mPhoneAccountRegistrar = phoneAccountRegistrar;
mHandle = handle;
- mPhoneAccountHandle = phoneAccountHandle;
- mTimeoutsAdapter = timeoutsAdapter;
- mTelecomLock = lock;
+ mPhoneAccountHandle = call.getTargetPhoneAccount();
+ mGatewayInfo = 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().isCarMode();
+ mCallRedirectionProcessorHelper = new CallRedirectionProcessorHelper(
+ context, callsManager, phoneAccountRegistrar);
}
@Override
@@ -264,11 +288,15 @@
mHandler.post(new Runnable("CRP.oCRC", mTelecomLock) {
@Override
public void loggedRun() {
+ mHandle = mCallRedirectionProcessorHelper.processNumberWhenRedirectionComplete(
+ mHandle);
if (mIsUserDefinedRedirectionPending) {
Log.addEvent(mCall, LogUtils.Events.REDIRECTION_COMPLETED_USER);
mIsUserDefinedRedirectionPending = false;
if (mShouldCancelCall) {
- // TODO mCallsManager.onCallRedirectionComplete
+ mCallsManager.onCallRedirectionComplete(mCall, mHandle,
+ mPhoneAccountHandle, mGatewayInfo, mSpeakerphoneOn, mVideoState,
+ mShouldCancelCall, mUiAction);
} else {
performCarrierCallRedirection();
}
@@ -276,23 +304,33 @@
if (mIsCarrierRedirectionPending) {
Log.addEvent(mCall, LogUtils.Events.REDIRECTION_COMPLETED_CARRIER);
mIsCarrierRedirectionPending = false;
- // TODO mCallsManager.onCallRedirectionComplete
+ mCallsManager.onCallRedirectionComplete(mCall, mHandle,
+ mPhoneAccountHandle, mGatewayInfo, mSpeakerphoneOn, mVideoState,
+ mShouldCancelCall, mUiAction);
}
}
}.prepare());
}
- /*
+ /**
* The entry to perform call redirection of the call from (@link CallsManager)
*/
public void performCallRedirection() {
- performUserDefinedCallRedirection();
+ // If the Gateway Info is set with intent, do not perform call redirection.
+ if (mGatewayInfo != null) {
+ mCallsManager.onCallRedirectionComplete(mCall, mHandle, mPhoneAccountHandle,
+ mGatewayInfo, mSpeakerphoneOn, mVideoState, mShouldCancelCall, mUiAction);
+ } else {
+ mCallRedirectionProcessorHelper.storePostDialDigits(mHandle);
+ performUserDefinedCallRedirection();
+ }
}
private void performUserDefinedCallRedirection() {
Log.d(this, "performUserDefinedCallRedirection");
- ComponentName componentName = getUserDefinedCallRedirectionService(mContext);
- if (componentName != null && canBindToCallRedirectionService(mContext, componentName)) {
+ ComponentName componentName =
+ mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService();
+ if (componentName != null) {
mAttempt = new CallRedirectionAttempt(componentName, SERVICE_TYPE_USER_DEFINED);
mAttempt.process();
mIsUserDefinedRedirectionPending = true;
@@ -306,9 +344,10 @@
private void performCarrierCallRedirection() {
Log.d(this, "performCarrierCallRedirection");
- ComponentName componentName = getCarrierCallRedirectionService(
- mContext, mPhoneAccountHandle);
- if (componentName != null && canBindToCallRedirectionService(mContext, componentName)) {
+ ComponentName componentName =
+ mCallRedirectionProcessorHelper.getCarrierCallRedirectionService(
+ mPhoneAccountHandle);
+ if (componentName != null) {
mAttempt = new CallRedirectionAttempt(componentName, SERVICE_TYPE_CARRIER);
mAttempt.process();
mIsCarrierRedirectionPending = true;
@@ -316,7 +355,9 @@
} else {
Log.i(this, "There are no carrier call redirection services installed on this"
+ " device.");
- // TODO return to CallsManager.onCallRedirectionComplete
+ mCallsManager.onCallRedirectionComplete(mCall, mHandle,
+ mPhoneAccountHandle, mGatewayInfo, mSpeakerphoneOn, mVideoState,
+ mShouldCancelCall, mUiAction);
}
}
@@ -333,77 +374,49 @@
serviceType.equals(SERVICE_TYPE_USER_DEFINED) ?
mIsUserDefinedRedirectionPending : mIsCarrierRedirectionPending;
if (isCurrentRedirectionPending) {
- Log.i(CallRedirectionProcessor.this,
- serviceType + "call redirection has timed out.");
+ 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);
+ if (serviceType.equals(SERVICE_TYPE_USER_DEFINED)) {
+ mUiAction = UI_TYPE_USER_DEFINED_TIMEOUT;
+ mShouldCancelCall = true;
+ }
onCallRedirectionComplete(mCall);
}
}
}.prepare(), timeout);
}
- private ComponentName getUserDefinedCallRedirectionService(Context context) {
- // TODO get service component name from settings default value:
- // android.provider.Settings#CALL_REDIRECTION_DEFAULT_APPLICATION
- return null;
- }
-
- private ComponentName getCarrierCallRedirectionService(Context context, PhoneAccountHandle
- targetPhoneAccountHandle) {
- CarrierConfigManager configManager = (CarrierConfigManager)
- context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
- if (configManager == null) {
- Log.i(this, "Cannot get CarrierConfigManager.");
- return null;
- }
- PersistableBundle pb = configManager.getConfigForSubId(mPhoneAccountRegistrar
- .getSubscriptionIdForPhoneAccount(targetPhoneAccountHandle));
- if (pb == null) {
- Log.i(this, "Cannot get PersistableBundle.");
- return null;
- }
- String componentNameString = pb.getString(
- CarrierConfigManager.KEY_CALL_REDIRECTION_SERVICE_COMPONENT_NAME_STRING);
- return new ComponentName(context, componentNameString);
- }
-
- private boolean canBindToCallRedirectionService(Context context, ComponentName componentName) {
- Intent intent = new Intent(CallRedirectionService.SERVICE_INTERFACE);
- intent.setComponent(componentName);
- List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
- intent, 0, mCallsManager.getCurrentUserHandle().getIdentifier());
- if (entries.isEmpty()) {
- Log.i(this, "There are no call redirection services installed on this device.");
- return false;
- } else if (entries.size() != 1) {
- Log.i(this, "There are multiple call redirection services installed on this device.");
- return false;
- } else {
- ResolveInfo entry = entries.get(0);
- if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals(
- Manifest.permission.BIND_CALL_REDIRECTION_SERVICE)) {
- Log.w(this, "CallRedirectionService must require BIND_CALL_REDIRECTION_SERVICE"
- + " permission: " + entry.serviceInfo.packageName);
- return false;
- }
- AppOpsManager appOps = (AppOpsManager) context.getSystemService(
- Context.APP_OPS_SERVICE);
- if (appOps.noteOp(AppOpsManager.OP_PROCESS_OUTGOING_CALLS, Binder.getCallingUid(),
- entry.serviceInfo.packageName) != AppOpsManager.MODE_ALLOWED) {
- Log.w(this, "App Ops does not allow " + entry.serviceInfo.packageName);
- return false;
- }
- }
- return true;
+ /**
+ * 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.w(this, "Can make call redirection with any available service: "
+ + canMakeCallRedirectionWithService);
+ return canMakeCallRedirectionWithService;
}
/**
- * Returns the handler for testing purposes.
+ * 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;
+ }
}
diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java
new file mode 100644
index 0000000..5e3f0da
--- /dev/null
+++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java
@@ -0,0 +1,199 @@
+/*
+ * 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.Manifest;
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.PersistableBundle;
+import android.provider.Settings;
+import android.telecom.CallRedirectionService;
+import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.PhoneAccountRegistrar;
+
+import java.util.List;
+
+public class CallRedirectionProcessorHelper {
+
+ private final Context mContext;
+ private final CallsManager mCallsManager;
+ private final PhoneAccountRegistrar mPhoneAccountRegistrar;
+ private String mOriginalPostDialDigits = null;
+
+ public CallRedirectionProcessorHelper(
+ Context context,
+ CallsManager callsManager,
+ PhoneAccountRegistrar phoneAccountRegistrar) {
+ mContext = context;
+ mCallsManager = callsManager;
+ mPhoneAccountRegistrar = phoneAccountRegistrar;
+ }
+
+ @VisibleForTesting
+ // TODO integarte with RoleManager functions
+ public ComponentName getUserDefinedCallRedirectionService() {
+ String componentNameString = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.CALL_REDIRECTION_DEFAULT_APPLICATION,
+ mCallsManager.getCurrentUserHandle().getIdentifier());
+ if (TextUtils.isEmpty(componentNameString)) {
+ Log.i(this, "Default user-defined call redirection is empty. Not performing call"
+ + " redirection.");
+ return null;
+ }
+ return getComponentName(componentNameString,
+ CallRedirectionProcessor.SERVICE_TYPE_USER_DEFINED);
+ }
+
+ @VisibleForTesting
+ public ComponentName getCarrierCallRedirectionService(
+ PhoneAccountHandle targetPhoneAccountHandle) {
+ CarrierConfigManager configManager = (CarrierConfigManager)
+ mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (configManager == null) {
+ Log.i(this, "Cannot get CarrierConfigManager.");
+ return null;
+ }
+ PersistableBundle pb = configManager.getConfigForSubId(mPhoneAccountRegistrar
+ .getSubscriptionIdForPhoneAccount(targetPhoneAccountHandle));
+ if (pb == null) {
+ Log.i(this, "Cannot get PersistableBundle.");
+ return null;
+ }
+ String componentNameString = pb.getString(
+ CarrierConfigManager.KEY_CALL_REDIRECTION_SERVICE_COMPONENT_NAME_STRING);
+ if (componentNameString == null) {
+ Log.i(this, "Cannot get carrier componentNameString.");
+ return null;
+ }
+ return getComponentName(componentNameString,
+ CallRedirectionProcessor.SERVICE_TYPE_CARRIER);
+ }
+
+ protected ComponentName getComponentName(String componentNameString, String serviceType) {
+ ComponentName componentName = ComponentName.unflattenFromString(componentNameString);
+ if (componentName == null) {
+ Log.w(this, "ComponentName is null from string: " + componentNameString);
+ return null;
+ }
+ Intent intent = new Intent(CallRedirectionService.SERVICE_INTERFACE);
+ intent.setComponent(componentName);
+ List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
+ intent, 0, mCallsManager.getCurrentUserHandle().getIdentifier());
+ if (entries.isEmpty()) {
+ Log.i(this, "There are no " + serviceType + " call redirection services installed" +
+ " on this device.");
+ return null;
+ } else if (entries.size() != 1) {
+ Log.i(this, "There are multiple " + serviceType + " call redirection services" +
+ " installed on this device.");
+ return null;
+ }
+ ResolveInfo entry = entries.get(0);
+ if (entry.serviceInfo == null) {
+ Log.w(this, "The " + serviceType + " call redirection service has invalid" +
+ " service info");
+ return null;
+ }
+ if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals(
+ Manifest.permission.BIND_CALL_REDIRECTION_SERVICE)) {
+ Log.w(this, "CallRedirectionService must require BIND_CALL_REDIRECTION_SERVICE"
+ + " permission: " + entry.serviceInfo.packageName);
+ return null;
+ }
+ AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(
+ Context.APP_OPS_SERVICE);
+ if (appOps.noteOpNoThrow(AppOpsManager.OP_PROCESS_OUTGOING_CALLS, Binder.getCallingUid(),
+ entry.serviceInfo.packageName) != AppOpsManager.MODE_ALLOWED) {
+ Log.w(this, "App Ops does not allow " + entry.serviceInfo.packageName);
+ return null;
+ }
+ return componentName;
+ }
+
+ /**
+ * Format Number to E164, and remove post dial digits.
+ */
+ protected Uri formatNumberForRedirection(Uri handle) {
+ return removePostDialDigits(formatNumberToE164(handle));
+ }
+
+ protected Uri processNumberWhenRedirectionComplete(Uri handle) {
+ return appendStoredPostDialDigits(formatNumberForRedirection(handle));
+ }
+
+ protected void storePostDialDigits(Uri handle) {
+ String number = handle.getSchemeSpecificPart();
+ mOriginalPostDialDigits += PhoneNumberUtils.extractPostDialPortion(number);
+ Log.i(this, "storePostDialDigits, stored post dial digits: "
+ + Log.pii(mOriginalPostDialDigits));
+ }
+
+ protected Uri appendStoredPostDialDigits(Uri handle) {
+ String number = handle.getSchemeSpecificPart();
+ number += mOriginalPostDialDigits;
+ Log.i(this, "appendStoredPostDialDigits, appended number: " + Log.pii(number));
+ return Uri.fromParts(handle.getScheme(), number, null);
+ }
+
+ protected Uri formatNumberToE164(Uri handle) {
+ String number = handle.getSchemeSpecificPart();
+
+ // Format number to E164
+ TelephonyManager tm = (TelephonyManager) mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ Log.i(this, "formatNumberToE164, original number: " + Log.pii(number));
+ number = PhoneNumberUtils.formatNumberToE164(number, tm.getNetworkCountryIso());
+ Log.i(this, "formatNumberToE164, formatted E164 number: " + Log.pii(number));
+ // if there is a problem with parsing the phone number, formatNumberToE164 will return null;
+ // and should just use the original number in that case.
+ if (number == null) {
+ return handle;
+ } else {
+ return Uri.fromParts(handle.getScheme(), number, null);
+ }
+ }
+
+ protected Uri removePostDialDigits(Uri handle) {
+ String number = handle.getSchemeSpecificPart();
+
+ // Extract the post dial portion
+ number = PhoneNumberUtils.extractNetworkPortionAlt(number);
+ Log.i(this, "removePostDialDigits, number after being extracted post dial digits: "
+ + Log.pii(number));
+ // if there is a problem with parsing the phone number, removePostDialDigits will return
+ // null; and should just use the original number in that case.
+ if (number == null) {
+ return handle;
+ } else {
+ return Uri.fromParts(handle.getScheme(), number, null);
+ }
+ }
+
+}
diff --git a/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
new file mode 100644
index 0000000..fa78383
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
@@ -0,0 +1,274 @@
+/*
+ * 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.tests;
+
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.telecom.GatewayInfo;
+import android.telecom.PhoneAccountHandle;
+import com.android.internal.telecom.ICallRedirectionService;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.SystemStateHelper;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
+
+import com.android.server.telecom.callredirection.CallRedirectionProcessor;
+import com.android.server.telecom.callredirection.CallRedirectionProcessorHelper;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+import org.junit.Before;
+
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(JUnit4.class)
+public class CallRedirectionProcessorTest extends TelecomTestCase {
+ @Mock private Context mContext;
+ @Mock private CallsManager mCallsManager;
+ @Mock private PhoneAccountRegistrar mPhoneAccountRegistrar;
+ @Mock private PhoneAccountHandle mPhoneAccountHandle;
+ private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
+
+ @Mock private Call mCall;
+
+ @Mock private PackageManager mPackageManager;
+ @Mock private IBinder mBinder;
+ @Mock private ICallRedirectionService mCallRedirectionService;
+
+ @Mock private SystemStateHelper mSystemStateHelper;
+ @Mock private CallRedirectionProcessorHelper mCallRedirectionProcessorHelper;
+
+ @Mock private Uri mHandle;
+ @Mock private GatewayInfo mGatewayInfo;
+ @Mock private UserHandle mUserHandle;
+ @Mock private ContentResolver mContentResolver;
+
+ @Mock private Timeouts.Adapter mTimeoutsAdapter;
+
+ private static final String USER_DEFINED_PKG_NAME = "user_defined_pkg";
+ private static final String USER_DEFINED_CLS_NAME = "user_defined_cls";
+ private static final String CARRIER_PKG_NAME = "carrier_pkg";
+ private static final String CARRIER_CLS_NAME = "carrier_cls";
+
+ private static final long HANDLER_TIMEOUT_DELAY = 5000;
+ private static final long USER_DEFINED_SHORT_TIMEOUT_MS = 1200;
+ private static final long CARRIER_SHORT_TIMEOUT_MS = 400;
+ private static final long CODE_EXECUTION_DELAY = 500;
+
+ // TODO integerate with a test user-defined service
+ private static final ComponentName USER_DEFINED_SERVICE_TEST_COMPONENT_NAME =
+ new ComponentName(USER_DEFINED_PKG_NAME, USER_DEFINED_CLS_NAME);
+ // TODO integerate with a test carrier service
+ private static final ComponentName CARRIER_SERVICE_TEST_COMPONENT_NAME =
+ new ComponentName(CARRIER_PKG_NAME, CARRIER_CLS_NAME);
+
+ private static final boolean SPEAKER_PHONE_ON = true;
+ private static final int VIDEO_STATE = 0;
+
+ private CallRedirectionProcessor mProcessor;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(mCall.getTargetPhoneAccount()).thenReturn(mPhoneAccountHandle);
+ when(mCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ doReturn(mCallRedirectionService).when(mBinder).queryLocalInterface(anyString());
+ when(mCallsManager.getSystemStateHelper()).thenReturn(mSystemStateHelper);
+ when(mCallsManager.getTimeoutsAdapter()).thenReturn(mTimeoutsAdapter);
+ when(mTimeoutsAdapter.getUserDefinedCallRedirectionTimeoutMillis(mContentResolver))
+ .thenReturn(USER_DEFINED_SHORT_TIMEOUT_MS);
+ when(mTimeoutsAdapter.getCarrierCallRedirectionTimeoutMillis(mContentResolver))
+ .thenReturn(CARRIER_SHORT_TIMEOUT_MS);
+ when(mCallsManager.getLock()).thenReturn(mLock);
+ when(mCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+ when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
+ anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
+ }
+
+ private void setIsInCarMode(boolean isInCarMode) {
+ when(mSystemStateHelper.isCarMode()).thenReturn(isInCarMode);
+ }
+
+ private void enableUserDefinedCallRedirectionService() {
+ when(mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService()).thenReturn(
+ USER_DEFINED_SERVICE_TEST_COMPONENT_NAME);
+ }
+
+ private void enableCarrierCallRedirectionService() {
+ when(mCallRedirectionProcessorHelper.getCarrierCallRedirectionService(
+ any(PhoneAccountHandle.class))).thenReturn(CARRIER_SERVICE_TEST_COMPONENT_NAME);
+ }
+
+ private void disableUserDefinedCallRedirectionService() {
+ when(mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService()).thenReturn(
+ null);
+ }
+
+ private void disableCarrierCallRedirectionService() {
+ when(mCallRedirectionProcessorHelper.getCarrierCallRedirectionService(any())).thenReturn(
+ null);
+ }
+
+ private void startProcessWithNoGateWayInfo() {
+ mProcessor = new CallRedirectionProcessor(mContext, mCallsManager, mCall, mHandle,
+ mPhoneAccountRegistrar, null, SPEAKER_PHONE_ON, VIDEO_STATE);
+ mProcessor.setCallRedirectionServiceHelper(mCallRedirectionProcessorHelper);
+ }
+
+ private void startProcessWithGateWayInfo() {
+ mProcessor = new CallRedirectionProcessor(mContext, mCallsManager, mCall, mHandle,
+ mPhoneAccountRegistrar, mGatewayInfo, SPEAKER_PHONE_ON, VIDEO_STATE);
+ mProcessor.setCallRedirectionServiceHelper(mCallRedirectionProcessorHelper);
+ }
+
+ @Test
+ public void testNoUserDefinedServiceNoCarrierSerivce() {
+ startProcessWithNoGateWayInfo();
+ disableUserDefinedCallRedirectionService();
+ disableCarrierCallRedirectionService();
+ mProcessor.performCallRedirection();
+ verify(mContext, times(0)).bindServiceAsUser(any(Intent.class),
+ any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+ verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), eq(mHandle),
+ eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+ eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
+ }
+
+ @Test
+ public void testCarrierServiceTimeoutNoUserDefinedService() throws Exception {
+ startProcessWithNoGateWayInfo();
+ // To make sure tests are not flaky, clean all the previous handler messages
+ waitForHandlerAction(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY);
+ disableUserDefinedCallRedirectionService();
+ enableCarrierCallRedirectionService();
+ mProcessor.performCallRedirection();
+ verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
+ any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+ verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
+ eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+ eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
+ waitForHandlerActionDelayed(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY,
+ CARRIER_SHORT_TIMEOUT_MS + CODE_EXECUTION_DELAY);
+ verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), any(),
+ eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+ eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
+ }
+
+ @Test
+ public void testUserDefinedServiceTimeoutNoCarrierService() throws Exception {
+ startProcessWithNoGateWayInfo();
+ // To make sure tests are not flaky, clean all the previous handler messages
+ waitForHandlerAction(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY);
+ enableUserDefinedCallRedirectionService();
+ disableCarrierCallRedirectionService();
+ mProcessor.performCallRedirection();
+ verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
+ any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+ verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
+ eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+ eq(false), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+
+ // Test it is waiting for a User-defined timeout, not a Carrier timeout
+ Thread.sleep(CARRIER_SHORT_TIMEOUT_MS + CODE_EXECUTION_DELAY);
+ verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
+ eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+ eq(false), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+
+ // Wait for the rest of user-defined timeout time.
+ waitForHandlerActionDelayed(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY,
+ USER_DEFINED_SHORT_TIMEOUT_MS - CARRIER_SHORT_TIMEOUT_MS + CODE_EXECUTION_DELAY);
+ verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), any(),
+ eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+ eq(true), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+ }
+
+ @Test
+ public void testUserDefinedServiceTimeoutAndCarrierServiceTimeout() throws Exception {
+ startProcessWithNoGateWayInfo();
+ // To make sure tests are not flaky, clean all the previous handler messages
+ waitForHandlerAction(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY);
+ enableUserDefinedCallRedirectionService();
+ enableCarrierCallRedirectionService();
+ mProcessor.performCallRedirection();
+
+ verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
+ any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+ verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
+ eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+ eq(false), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+
+ // Test it is waiting for a User-defined timeout, not a Carrier timeout
+ Thread.sleep(CARRIER_SHORT_TIMEOUT_MS + CODE_EXECUTION_DELAY);
+ verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
+ any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+ verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
+ eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+ eq(false), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+
+ // Wait for the rest of user-defined timeout time.
+ waitForHandlerActionDelayed(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY,
+ USER_DEFINED_SHORT_TIMEOUT_MS - CARRIER_SHORT_TIMEOUT_MS + CODE_EXECUTION_DELAY);
+ verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), any(),
+ eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+ eq(true), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+
+ // Wait for another carrier timeout time, but should not expect any carrier service request
+ // is triggered.
+ Thread.sleep(CARRIER_SHORT_TIMEOUT_MS + CODE_EXECUTION_DELAY);
+ verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
+ any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+ verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), any(),
+ eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+ eq(true), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+ }
+
+ @Test
+ public void testProcessGatewayCall() {
+ startProcessWithGateWayInfo();
+ enableUserDefinedCallRedirectionService();
+ enableCarrierCallRedirectionService();
+ mProcessor.performCallRedirection();
+ verify(mContext, times(0)).bindServiceAsUser(any(Intent.class),
+ any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+ verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), eq(mHandle),
+ eq(mPhoneAccountHandle), eq(mGatewayInfo), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+ eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
index 4c0e76a..cfbcfcf 100644
--- a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
+++ b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
@@ -28,6 +28,7 @@
import android.os.Handler;
import android.os.UserHandle;
import android.telecom.GatewayInfo;
+import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telephony.DisconnectCause;
@@ -36,8 +37,10 @@
import com.android.server.telecom.Call;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.NewOutgoingCallIntentBroadcaster;
+import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
+import com.android.server.telecom.SystemStateHelper;
import com.android.server.telecom.TelecomSystem;
import org.junit.Before;
@@ -78,6 +81,9 @@
@Mock private CallsManager mCallsManager;
@Mock private Call mCall;
+ @Mock private SystemStateHelper mSystemStateHelper;
+ @Mock private UserHandle mUserHandle;
+ @Mock private PhoneAccountRegistrar mPhoneAccountRegistrar;
private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapterSpy;
@@ -89,6 +95,12 @@
mPhoneNumberUtilsAdapterSpy = spy(new PhoneNumberUtilsAdapterImpl());
when(mCall.getInitiatingUser()).thenReturn(UserHandle.CURRENT);
when(mCallsManager.getLock()).thenReturn(new TelecomSystem.SyncRoot() { });
+ when(mCallsManager.getSystemStateHelper()).thenReturn(mSystemStateHelper);
+ when(mCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+ when(mCallsManager.getPhoneAccountRegistrar()).thenReturn(mPhoneAccountRegistrar);
+ when(mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(
+ any(PhoneAccountHandle.class))).thenReturn(-1);
+ when(mSystemStateHelper.isCarMode()).thenReturn(false);
}
@SmallTest