Merge "Add updates for the BT route for the UI."
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index dfd7b2c..cc80db2 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -132,6 +132,7 @@
void onConnectionManagerPhoneAccountChanged(Call call);
void onPhoneAccountChanged(Call call);
void onConferenceableCallsChanged(Call call);
+ void onConferenceStateChanged(Call call, boolean isConference);
boolean onCanceledViaNewOutgoingCallBroadcast(Call call, long disconnectionTimeout);
void onHoldToneRequested(Call call);
void onConnectionEvent(Call call, String event, Bundle extras);
@@ -201,6 +202,8 @@
@Override
public void onConferenceableCallsChanged(Call call) {}
@Override
+ public void onConferenceStateChanged(Call call, boolean isConference) {}
+ @Override
public boolean onCanceledViaNewOutgoingCallBroadcast(Call call, long disconnectionTimeout) {
return false;
}
@@ -1472,7 +1475,6 @@
"capable when not supported by the phone account.");
connectionCapabilities = removeVideoCapabilities(connectionCapabilities);
}
-
int previousCapabilities = mConnectionCapabilities;
mConnectionCapabilities = connectionCapabilities;
for (Listener l : mListeners) {
@@ -3108,6 +3110,20 @@
}
/**
+ * Sets whether this {@link Call} is a conference or not.
+ * @param isConference
+ */
+ public void setConferenceState(boolean isConference) {
+ mIsConference = isConference;
+ Log.addEvent(this, LogUtils.Events.CONF_STATE_CHANGED, "isConference=" + isConference);
+ // Ultimately CallsManager needs to know so it can update the "add call" state and inform
+ // the UI to update itself.
+ for (Listener l : mListeners) {
+ l.onConferenceStateChanged(this, isConference);
+ }
+ }
+
+ /**
* Sets the video history based on the state and state transitions of the call. Always add the
* current video state to the video state history during a call transition except for the
* transitions DIALING->ACTIVE and RINGING->ANSWERED. In these cases, clear the history. If a
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index cda6054..e4535d9 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -141,6 +141,7 @@
void onExternalCallChanged(Call call, boolean isExternalCall);
void onDisconnectedTonePlaying(boolean isTonePlaying);
void onConnectionTimeChanged(Call call);
+ void onConferenceStateChanged(Call call, boolean isConference);
}
/** Interface used to define the action which is executed delay under some condition. */
@@ -299,6 +300,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 +438,7 @@
mMissedCallNotifier = missedCallNotifier;
StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this);
mWiredHeadsetManager = wiredHeadsetManager;
+ mSystemStateHelper = systemStateHelper;
mDefaultDialerCache = defaultDialerCache;
mBluetoothRouteManager = bluetoothManager;
mDockManager = new DockManager(context);
@@ -801,6 +804,15 @@
}
@Override
+ public void onConferenceStateChanged(Call call, boolean isConference) {
+ // Conference changed whether it is treated as a conference or not.
+ updateCanAddCall();
+ for (CallsManagerListener listener : mListeners) {
+ listener.onConferenceStateChanged(call, isConference);
+ }
+ }
+
+ @Override
public void onIsVoipAudioModeChanged(Call call) {
for (CallsManagerListener listener : mListeners) {
listener.onIsVoipAudioModeChanged(call);
@@ -1606,6 +1618,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.
*
@@ -2590,7 +2653,8 @@
*
* @return The {@link PhoneAccountRegistrar}.
*/
- PhoneAccountRegistrar getPhoneAccountRegistrar() {
+ @VisibleForTesting
+ public PhoneAccountRegistrar getPhoneAccountRegistrar() {
return mPhoneAccountRegistrar;
}
@@ -3387,6 +3451,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/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index cdc0209..4fa2ee5 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -96,4 +96,8 @@
@Override
public void onConnectionTimeChanged(Call call) {
}
+
+ @Override
+ public void onConferenceStateChanged(Call call, boolean isConference) {
+ }
}
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 6c63ecb..90064cd 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -990,6 +990,27 @@
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();
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index cf5bf89..142cf3a 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -972,6 +972,12 @@
updateCall(call);
}
+ @Override
+ public void onConferenceStateChanged(Call call, boolean isConference) {
+ Log.d(this, "onConferenceStateChanged %s ,isConf=%b", call, isConference);
+ updateCall(call);
+ }
+
void bringToForeground(boolean showDialpad) {
if (!mInCallServices.isEmpty()) {
for (IInCallService inCallService : mInCallServices.values()) {
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index bf8dba7..71ed67f 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -95,6 +95,7 @@
public static final String ADD_CHILD = "ADD_CHILD";
public static final String REMOVE_CHILD = "REMOVE_CHILD";
public static final String SET_PARENT = "SET_PARENT";
+ public static final String CONF_STATE_CHANGED = "CONF_STATE_CHANGED";
public static final String MUTE = "MUTE";
public static final String UNMUTE = "UNMUTE";
public static final String AUDIO_ROUTE = "AUDIO_ROUTE";
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/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index 196b8ad..9010913 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -332,7 +332,7 @@
int subId = getSubscriptionIdForPhoneAccount(accountHandle);
mSubscriptionManager.setDefaultVoiceSubId(subId);
}
-
+ Log.i(this, "setUserSelectedOutgoingPhoneAccount: %s", accountHandle);
mState.defaultOutgoingAccountHandles
.put(userHandle, new DefaultPhoneAccountHandle(userHandle, accountHandle,
account.getGroupId()));
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index f57a0c6..f472421 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -137,10 +137,14 @@
}
@Override
- public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
+ public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount(String callingPackage) {
synchronized (mLock) {
try {
Log.startSession("TSI.gUSOPA");
+ if (!isDialerOrPrivileged(callingPackage, "getDefaultOutgoingPhoneAccount")) {
+ throw new SecurityException("Only the default dialer, or caller with "
+ + "READ_PRIVILEGED_PHONE_STATE can call this method.");
+ }
final UserHandle callingUserHandle = Binder.getCallingUserHandle();
return mPhoneAccountRegistrar.getUserSelectedOutgoingPhoneAccount(
callingUserHandle);
@@ -1909,6 +1913,19 @@
}
}
+ private boolean isDialerOrPrivileged(String callingPackage, String message) {
+ // The system/default dialer can always read phone state - so that emergency calls will
+ // still work.
+ if (isPrivilegedDialerCalling(callingPackage)) {
+ return true;
+ }
+
+ mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, message);
+ // SKIP checking run-time OP_READ_PHONE_STATE since caller or self has PRIVILEGED
+ // permission
+ return true;
+ }
+
private boolean isSelfManagedConnectionService(PhoneAccountHandle phoneAccountHandle) {
if (phoneAccountHandle != null) {
PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
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
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index c11811d..b779ce2 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -22,6 +22,8 @@
import static android.Manifest.permission.READ_PHONE_STATE;
import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
+import android.annotation.MainThread;
+import android.annotation.WorkerThread;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.ComponentName;
@@ -271,7 +273,8 @@
makeMultiUserPhoneAccount(TEL_PA_HANDLE_16).build());
PhoneAccountHandle returnedHandle
- = mTSIBinder.getUserSelectedOutgoingPhoneAccount();
+ = mTSIBinder.getUserSelectedOutgoingPhoneAccount(
+ TEL_PA_HANDLE_16.getComponentName().getPackageName());
assertEquals(TEL_PA_HANDLE_16, returnedHandle);
}