Refactor startOutgoingCall in CallsManager
Modify startOutgoingCall to use futures to manage the asynchronous
elements of starting an outgoing call (e.g. phone account selection,
prompting for user input, etc). This is done in preparation for adding
the phone account suggestion feature.
Bug: 111455117
Test: unit, manual
Change-Id: Ia0074738b7c4b146b3c9dbb6e425fe43bb0ac122
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 1d5a24b..6784d2b 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -531,7 +531,7 @@
/**
* Persists the specified parameters and initializes the new instance.
- * @param context The context.
+ * @param context The context.
* @param repository The connection service repository.
* @param handle The handle to dial.
* @param gatewayInfo Gateway information to use for the call.
@@ -551,8 +551,6 @@
CallsManager callsManager,
TelecomSystem.SyncRoot lock,
ConnectionServiceRepository repository,
- ContactsAsyncHelper contactsAsyncHelper,
- CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
Uri handle,
GatewayInfo gatewayInfo,
@@ -587,7 +585,7 @@
/**
* Persists the specified parameters and initializes the new instance.
- * @param context The context.
+ * @param context The context.
* @param repository The connection service repository.
* @param handle The handle to dial.
* @param gatewayInfo Gateway information to use for the call.
@@ -609,8 +607,6 @@
CallsManager callsManager,
TelecomSystem.SyncRoot lock,
ConnectionServiceRepository repository,
- ContactsAsyncHelper contactsAsyncHelper,
- CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
Uri handle,
GatewayInfo gatewayInfo,
@@ -622,8 +618,8 @@
long connectTimeMillis,
long connectElapsedTimeMillis,
ClockProxy clockProxy) {
- this(callId, context, callsManager, lock, repository, contactsAsyncHelper,
- callerInfoAsyncQueryFactory, phoneNumberUtilsAdapter, handle, gatewayInfo,
+ this(callId, context, callsManager, lock, repository,
+ phoneNumberUtilsAdapter, handle, gatewayInfo,
connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, callDirection,
shouldAttachToExistingConnection, isConference, clockProxy);
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index 5213364..cea18dc 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -11,6 +11,7 @@
import android.os.UserManager;
import android.telecom.DefaultDialerManager;
import android.telecom.Log;
+import android.telecom.Logging.Session;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
@@ -19,6 +20,8 @@
import android.telephony.PhoneNumberUtils;
import android.widget.Toast;
+import java.util.concurrent.CompletableFuture;
+
/**
* Single point of entry for all outgoing and incoming calls.
* {@link com.android.server.telecom.components.UserCallIntentProcessor} serves as a trampoline that
@@ -146,13 +149,21 @@
UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
// Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
- Call call = callsManager
+ CompletableFuture<Call> callFuture = callsManager
.startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser,
intent, callingPackage);
- if (call != null) {
- sendNewOutgoingCallIntent(context, call, callsManager, intent);
- }
+ final Session logSubsession = Log.createSubsession();
+ callFuture.thenAccept((call) -> {
+ if (call != null) {
+ Log.continueSession(logSubsession, "CIP.sNOCI");
+ try {
+ sendNewOutgoingCallIntent(context, call, callsManager, intent);
+ } finally {
+ Log.endSession();
+ }
+ }
+ });
}
static void sendNewOutgoingCallIntent(Context context, Call call, CallsManager callsManager,
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 993c40e..6130bb5 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -16,6 +16,7 @@
package com.android.server.telecom;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.content.BroadcastReceiver;
@@ -54,6 +55,7 @@
import android.telecom.ParcelableConnection;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
+import android.telecom.PhoneAccountSuggestion;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telephony.CarrierConfigManager;
@@ -64,6 +66,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.AsyncEmergencyContactNotifier;
+import com.android.internal.telephony.CallerInfo;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.util.IndentingPrintWriter;
@@ -93,8 +96,10 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -141,6 +146,26 @@
void performAction();
}
+ private class LoggedHandlerExecutor implements Executor {
+ private Handler mHandler;
+ private String mSessionName;
+
+ public LoggedHandlerExecutor(Handler handler, String sessionName) {
+ mHandler = handler;
+ mSessionName = sessionName;
+ }
+
+ @Override
+ public void execute(java.lang.Runnable command) {
+ mHandler.post(new Runnable(mSessionName, mLock) {
+ @Override
+ public void loggedRun() {
+ command.run();
+ }
+ }.prepare());
+ }
+ }
+
private static final String TAG = "CallsManager";
/**
@@ -232,9 +257,15 @@
/**
* A pending call is one which requires user-intervention in order to be placed.
- * Used by {@link #startCallConfirmation(Call)}.
+ * Used by {@link #startCallConfirmation}.
*/
private Call mPendingCall;
+ private CompletableFuture<Call> mPendingCallConfirm;
+ private CompletableFuture<Pair<Call, PhoneAccountHandle>> mPendingAccountSelection;
+
+ // Instance variables for testing -- we keep the latest copy of the outgoing call futures
+ // here so that we can wait on them in tests
+ private CompletableFuture<Call> mLatestPostSelectionProcessingFuture;
/**
* The current telecom call ID. Used when creating new instances of {@link Call}. Should
@@ -271,8 +302,6 @@
private final CallLogManager mCallLogManager;
private final Context mContext;
private final TelecomSystem.SyncRoot mLock;
- private final ContactsAsyncHelper mContactsAsyncHelper;
- private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final MissedCallNotifier mMissedCallNotifier;
private IncomingCallNotifier mIncomingCallNotifier;
@@ -370,8 +399,7 @@
public CallsManager(
Context context,
TelecomSystem.SyncRoot lock,
- ContactsAsyncHelper contactsAsyncHelper,
- CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
+ CallerInfoLookupHelper callerInfoLookupHelper,
MissedCallNotifier missedCallNotifier,
PhoneAccountRegistrar phoneAccountRegistrar,
HeadsetMediaButtonFactory headsetMediaButtonFactory,
@@ -398,8 +426,6 @@
mContext = context;
mLock = lock;
mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
- mContactsAsyncHelper = contactsAsyncHelper;
- mCallerInfoAsyncQueryFactory = callerInfoAsyncQueryFactory;
mPhoneAccountRegistrar = phoneAccountRegistrar;
mPhoneAccountRegistrar.addListener(mPhoneAccountListener);
mMissedCallNotifier = missedCallNotifier;
@@ -410,8 +436,7 @@
mDockManager = new DockManager(context);
mTimeoutsAdapter = timeoutsAdapter;
mEmergencyCallHelper = emergencyCallHelper;
- mCallerInfoLookupHelper = new CallerInfoLookupHelper(context, mCallerInfoAsyncQueryFactory,
- mContactsAsyncHelper, mLock);
+ mCallerInfoLookupHelper = callerInfoLookupHelper;
mDtmfLocalTonePlayer =
new DtmfLocalTonePlayer(new DtmfLocalTonePlayer.ToneGeneratorProxy());
@@ -973,8 +998,6 @@
this,
mLock,
mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
handle,
null /* gatewayInfo */,
@@ -1108,8 +1131,6 @@
this,
mLock,
mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
handle,
null /* gatewayInfo */,
@@ -1169,7 +1190,7 @@
* For self-managed connections, we don't expect the Incall UI to launch, but this is still a
* first step in getting the self-managed ConnectionService to create the connection.
* @param handle Handle to connect the call with.
- * @param phoneAccountHandle The phone account which contains the component name of the
+ * @param requestedAccountHandle The phone account which contains the component name of the
* connection service to use for this call.
* @param extras The optional extras Bundle passed with the intent used for the incoming call.
* @param initiatingUser {@link UserHandle} of user that place the outgoing call.
@@ -1177,13 +1198,16 @@
* @param callingPackage the package name of the app which initiated the outgoing call.
*/
@VisibleForTesting
- public Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras,
- UserHandle initiatingUser, Intent originalIntent, String callingPackage) {
- boolean isReusedCall = true;
+ public @NonNull
+ CompletableFuture<Call> startOutgoingCall(Uri handle,
+ PhoneAccountHandle requestedAccountHandle,
+ Bundle extras, UserHandle initiatingUser, Intent originalIntent,
+ String callingPackage) {
+ boolean isReusedCall;
Call call = reuseOutgoingCall(handle);
PhoneAccount account =
- mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle, initiatingUser);
+ mPhoneAccountRegistrar.getPhoneAccount(requestedAccountHandle, initiatingUser);
boolean isSelfManaged = account != null && account.isSelfManaged();
// Create a call with original handle. The handle may be changed when the call is attached
@@ -1193,13 +1217,11 @@
this,
mLock,
mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
handle,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
- null /* phoneAccountHandle */,
+ null /* requestedAccountHandle */,
Call.CALL_DIRECTION_OUTGOING /* callDirection */,
false /* forceAttachToExistingConnection */,
false, /* isConference */
@@ -1217,6 +1239,8 @@
}
call.setInitiatingUser(initiatingUser);
isReusedCall = false;
+ } else {
+ isReusedCall = true;
}
int videoState = VideoProfile.STATE_AUDIO_ONLY;
@@ -1251,96 +1275,209 @@
call.setVideoState(videoState);
}
- List<PhoneAccountHandle> potentialPhoneAccounts = findOutgoingCallPhoneAccount(
- phoneAccountHandle, handle, VideoProfile.isVideo(videoState), initiatingUser);
- if (potentialPhoneAccounts.size() == 1) {
- phoneAccountHandle = potentialPhoneAccounts.get(0);
- } else {
- phoneAccountHandle = null;
- }
- call.setTargetPhoneAccount(phoneAccountHandle);
+ final int finalVideoState = videoState;
+ final Call finalCall = call;
+ Handler outgoingCallHandler = new Handler(Looper.getMainLooper());
+ // Create a empty CompletableFuture and compose it with findOutgoingPhoneAccount to get
+ // a first guess at the list of suitable outgoing PhoneAccounts.
+ // findOutgoingPhoneAccount returns a CompletableFuture which is either already complete
+ // (in the case where we don't need to do the per-contact lookup) or a CompletableFuture
+ // that completes once the contact lookup via CallerInfoLookupHelper is complete.
+ CompletableFuture<List<PhoneAccountHandle>> accountsForCall =
+ CompletableFuture.completedFuture((Void) null).thenComposeAsync((x) ->
+ findOutgoingCallPhoneAccount(requestedAccountHandle, handle,
+ VideoProfile.isVideo(finalVideoState), initiatingUser),
+ new LoggedHandlerExecutor(outgoingCallHandler, "CM.fOCP"));
- boolean isPotentialInCallMMICode = isPotentialInCallMMICode(handle) && !isSelfManaged;
+ // This is a block of code that executes after the list of potential phone accts has been
+ // retrieved.
+ CompletableFuture<List<PhoneAccountHandle>> setAccountHandle =
+ accountsForCall.whenCompleteAsync((potentialPhoneAccounts, exception) -> {
+ Log.i(CallsManager.this, "set outgoing call phone acct stage");
+ PhoneAccountHandle phoneAccountHandle;
+ if (potentialPhoneAccounts.size() == 1) {
+ phoneAccountHandle = potentialPhoneAccounts.get(0);
+ } else {
+ phoneAccountHandle = null;
+ }
+ finalCall.setTargetPhoneAccount(phoneAccountHandle);
+ }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.sOCPA"));
- // Do not support any more live calls. Our options are to move a call to hold, disconnect
- // a call, or cancel this call altogether. If a call is being reused, then it has already
- // passed the makeRoomForOutgoingCall check once and will fail the second time due to the
- // call transitioning into the CONNECTING state.
- if (!isPotentialInCallMMICode && (!isReusedCall
- && !makeRoomForOutgoingCall(call, call.isEmergencyCall()))) {
- Call foregroundCall = getForegroundCall();
- Log.d(this, "No more room for outgoing call %s ", call);
- if (foregroundCall.isSelfManaged()) {
- // If the ongoing call is a self-managed call, then prompt the user to ask if they'd
- // like to disconnect their ongoing call and place the outgoing call.
- call.setOriginalCallIntent(originalIntent);
- startCallConfirmation(call);
- } else {
- // If the ongoing call is a managed call, we will prevent the outgoing call from
- // dialing.
- notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call);
- }
- return null;
- }
- // The outgoing call can be placed, go forward.
+ // This composes the future containing the potential phone accounts with code that queries
+ // the suggestion service if necessary (i.e. if the list is longer than 1).
+ // If the suggestion service is queried, the inner lambda will return a future that
+ // completes when the suggestion service calls the callback.
+ CompletableFuture<List<PhoneAccountSuggestion>> suggestionFuture = accountsForCall.
+ thenComposeAsync(potentialPhoneAccounts -> {
+ Log.i(CallsManager.this, "call outgoing call suggestion service stage");
+ if (potentialPhoneAccounts.size() == 1) {
+ PhoneAccountSuggestion suggestion =
+ new PhoneAccountSuggestion(potentialPhoneAccounts.get(0),
+ PhoneAccountSuggestion.REASON_NONE, true);
+ return CompletableFuture.completedFuture(
+ Collections.singletonList(suggestion));
+ }
+ // todo: call onsuggestphoneaccount and bring back the list of suggestions
+ // from there. For now just map all the accounts to suggest_none
+ List<PhoneAccountSuggestion> suggestions =
+ potentialPhoneAccounts.stream().map(phoneAccountHandle ->
+ new PhoneAccountSuggestion(phoneAccountHandle,
+ PhoneAccountSuggestion.REASON_NONE, false)
+ ).collect(Collectors.toList());
- boolean needsAccountSelection =
- phoneAccountHandle == null && potentialPhoneAccounts.size() > 1
- && !call.isEmergencyCall() && !isSelfManaged;
- if (needsAccountSelection) {
- // This is the state where the user is expected to select an account
- call.setState(CallState.SELECT_PHONE_ACCOUNT, "needs account selection");
- // Create our own instance to modify (since extras may be Bundle.EMPTY)
- extras = new Bundle(extras);
- extras.putParcelableList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS,
- potentialPhoneAccounts);
- } else {
- PhoneAccount accountToUse =
- mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle, initiatingUser);
- if (accountToUse != null && accountToUse.getExtras() != null) {
- if (accountToUse.getExtras()
- .getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
- Log.d(this, "startOutgoingCall: defaulting to voip mode for call %s",
- call.getId());
- call.setIsVoipAudioMode(true);
- }
- }
+ return CompletableFuture.completedFuture(suggestions);
+ }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.cOCSS"));
- call.setState(
- CallState.CONNECTING,
- phoneAccountHandle == null ? "no-handle" : phoneAccountHandle.toString());
- boolean isVoicemail = (call.getHandle() != null)
- && (PhoneAccount.SCHEME_VOICEMAIL.equals(call.getHandle().getScheme())
- || (accountToUse != null && mPhoneAccountRegistrar.isVoiceMailNumber(
- accountToUse.getAccountHandle(), call.getHandle().getSchemeSpecificPart())));
+ // This future checks the status of existing calls and attempts to make room for the
+ // outgoing call. The future returned by the inner method will usually be pre-completed --
+ // we only pause here if user interaction is required to disconnect a self-managed call.
+ // It runs after the account handle is set, independently of the phone account suggestion
+ // future.
+ CompletableFuture<Call> makeRoomForCall = setAccountHandle.thenComposeAsync(
+ potentialPhoneAccounts -> {
+ Log.i(CallsManager.this, "make room for outgoing call stage");
+ boolean isPotentialInCallMMICode =
+ isPotentialInCallMMICode(handle) && !isSelfManaged;
+ // Do not support any more live calls. Our options are to move a call to hold,
+ // disconnect a call, or cancel this call altogether. If a call is being reused,
+ // then it has already passed the makeRoomForOutgoingCall check once and will
+ // fail the second time due to the call transitioning into the CONNECTING state.
+ if (!isPotentialInCallMMICode && (!isReusedCall
+ && !makeRoomForOutgoingCall(finalCall, finalCall.isEmergencyCall()))) {
+ Call foregroundCall = getForegroundCall();
+ Log.d(CallsManager.this, "No more room for outgoing call %s ", finalCall);
+ if (foregroundCall.isSelfManaged()) {
+ // If the ongoing call is a self-managed call, then prompt the user to
+ // ask if they'd like to disconnect their ongoing call and place the
+ // outgoing call.
+ Log.i(CallsManager.this, "Prompting user to disconnect "
+ + "self-managed call");
+ finalCall.setOriginalCallIntent(originalIntent);
+ CompletableFuture<Call> completionFuture = new CompletableFuture<>();
+ startCallConfirmation(finalCall, completionFuture);
+ return completionFuture;
+ } else {
+ // If the ongoing call is a managed call, we will prevent the outgoing
+ // call from dialing.
+ notifyCreateConnectionFailed(
+ finalCall.getTargetPhoneAccount(), finalCall);
+ }
+ Log.i(CallsManager.this, "Aborting call since there's no room");
+ return CompletableFuture.completedFuture(null);
+ }
+ return CompletableFuture.completedFuture(finalCall);
+ }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.dSMCP"));
- if (!isVoicemail && (isRttSettingOn() || (extras != null
- && extras.getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, false)))) {
- Log.d(this, "Outgoing call requesting RTT, rtt setting is %b", isRttSettingOn());
- if (accountToUse != null
- && accountToUse.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
- call.createRttStreams();
- }
- // Even if the phone account doesn't support RTT yet, the connection manager might
- // change that. Set this to check it later.
- call.setRequestedToStartWithRtt();
- }
- }
- setIntentExtrasAndStartTime(call, extras);
- setCallSourceToAnalytics(call, originalIntent);
+ // The outgoing call can be placed, go forward. This future glues together the results of
+ // the account suggestion stage and the make room for call stage.
+ CompletableFuture<Pair<Call, List<PhoneAccountSuggestion>>> preSelectStage =
+ makeRoomForCall.thenCombine(suggestionFuture, Pair::create);
- if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) {
- // Do not add the call if it is a potential MMI code.
- call.addListener(this);
- } else if (!mCalls.contains(call)) {
- // We check if mCalls already contains the call because we could potentially be reusing
- // a call which was previously added (See {@link #reuseOutgoingCall}).
- addCall(call);
- }
+ // This future takes the list of suggested accounts and the call and determines if more
+ // user interaction in the form of a phone account selection screen is needed. If so, it
+ // will set the call to SELECT_PHONE_ACCOUNT, add it to our internal list/send it to dialer,
+ // and then execution will pause pending the dialer calling phoneAccountSelected.
+ CompletableFuture<Pair<Call, PhoneAccountHandle>> dialerSelectPhoneAccountFuture =
+ preSelectStage.thenComposeAsync(
+ (args) -> {
+ Log.i(CallsManager.this, "dialer phone acct select stage");
+ Call callToPlace = args.first;
+ List<PhoneAccountSuggestion> accountSuggestions = args.second;
+ if (callToPlace == null) {
+ return CompletableFuture.completedFuture(null);
+ }
+ if (accountSuggestions == null || accountSuggestions.isEmpty()) {
+ Log.i(CallsManager.this, "Aborting call since there are no"
+ + " available accounts.");
+ return CompletableFuture.completedFuture(null);
+ }
+ boolean needsAccountSelection = accountSuggestions.size() > 1
+ && !callToPlace.isEmergencyCall() && !isSelfManaged;
+ if (!needsAccountSelection) {
+ return CompletableFuture.completedFuture(Pair.create(callToPlace,
+ accountSuggestions.get(0).getPhoneAccountHandle()));
+ }
+ // This is the state where the user is expected to select an account
+ callToPlace.setState(CallState.SELECT_PHONE_ACCOUNT,
+ "needs account selection");
+ // Create our own instance to modify (since extras may be Bundle.EMPTY)
+ Bundle newExtras = new Bundle(extras);
+ List<PhoneAccountHandle> accountsFromSuggestions = accountSuggestions
+ .stream()
+ .map(PhoneAccountSuggestion::getPhoneAccountHandle)
+ .collect(Collectors.toList());
+ newExtras.putParcelableList(
+ android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS,
+ accountsFromSuggestions);
+ // Set a future in place so that we can proceed once the dialer replies.
+ mPendingAccountSelection = new CompletableFuture<>();
+ callToPlace.setIntentExtras(newExtras);
- return call;
+ addCall(callToPlace);
+ return mPendingAccountSelection;
+ }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.dSPA"));
+
+ // Finally, after all user interaction is complete, we execute this code to finish setting
+ // up the outgoing call. The inner method always returns a completed future containing the
+ // call that we've finished setting up.
+ mLatestPostSelectionProcessingFuture = dialerSelectPhoneAccountFuture
+ .thenComposeAsync(args -> {
+ if (args == null) {
+ return CompletableFuture.completedFuture(null);
+ }
+ Log.i(CallsManager.this, "post acct selection stage");
+ Call callToUse = args.first;
+ PhoneAccountHandle phoneAccountHandle = args.second;
+ PhoneAccount accountToUse = mPhoneAccountRegistrar
+ .getPhoneAccount(phoneAccountHandle, initiatingUser);
+ callToUse.setTargetPhoneAccount(phoneAccountHandle);
+ if (accountToUse != null && accountToUse.getExtras() != null) {
+ if (accountToUse.getExtras()
+ .getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
+ Log.d(this, "startOutgoingCall: defaulting to voip mode for call %s",
+ callToUse.getId());
+ callToUse.setIsVoipAudioMode(true);
+ }
+ }
+
+ callToUse.setState(
+ CallState.CONNECTING,
+ phoneAccountHandle == null ? "no-handle"
+ : phoneAccountHandle.toString());
+
+ boolean isVoicemail = isVoicemail(callToUse.getHandle(), accountToUse);
+
+ if (!isVoicemail && (isRttSettingOn() || (extras != null
+ && extras.getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT,
+ false)))) {
+ Log.d(this, "Outgoing call requesting RTT, rtt setting is %b",
+ isRttSettingOn());
+ if (accountToUse != null
+ && accountToUse.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
+ callToUse.createRttStreams();
+ }
+ // Even if the phone account doesn't support RTT yet,
+ // the connection manager might change that. Set this to check it later.
+ callToUse.setRequestedToStartWithRtt();
+ }
+
+ setIntentExtrasAndStartTime(callToUse, extras);
+ setCallSourceToAnalytics(callToUse, originalIntent);
+
+ if (isPotentialMMICode(handle) && !isSelfManaged) {
+ // Do not add the call if it is a potential MMI code.
+ callToUse.addListener(this);
+ } else if (!mCalls.contains(callToUse)) {
+ // We check if mCalls already contains the call because we could
+ // potentially be reusing
+ // a call which was previously added (See {@link #reuseOutgoingCall}).
+ addCall(callToUse);
+ }
+ return CompletableFuture.completedFuture(callToUse);
+ }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.pASP"));
+ return mLatestPostSelectionProcessingFuture;
}
/**
@@ -1364,55 +1501,71 @@
* @return
*/
@VisibleForTesting
- public List<PhoneAccountHandle> findOutgoingCallPhoneAccount(
+ public CompletableFuture<List<PhoneAccountHandle>> findOutgoingCallPhoneAccount(
PhoneAccountHandle targetPhoneAccountHandle, Uri handle, boolean isVideo,
UserHandle initiatingUser) {
- boolean isSelfManaged = isSelfManaged(targetPhoneAccountHandle, initiatingUser);
+ if (isSelfManaged(targetPhoneAccountHandle, initiatingUser)) {
+ return CompletableFuture.completedFuture(Arrays.asList(targetPhoneAccountHandle));
+ }
List<PhoneAccountHandle> accounts;
- if (!isSelfManaged) {
- // Try to find a potential phone account, taking into account whether this is a video
- // call.
- accounts = constructPossiblePhoneAccounts(handle, initiatingUser, isVideo);
- if (isVideo && accounts.size() == 0) {
- // Placing a video call but no video capable accounts were found, so consider any
- // call capable accounts (we can fallback to audio).
- accounts = constructPossiblePhoneAccounts(handle, initiatingUser,
- false /* isVideo */);
- }
- Log.v(this, "findOutgoingCallPhoneAccount: accounts = " + accounts);
-
- // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this
- // call as if a phoneAccount was not specified (does the default behavior instead).
- // Note: We will not attempt to dial with a requested phoneAccount if it is disabled.
- if (targetPhoneAccountHandle != null) {
- if (!accounts.contains(targetPhoneAccountHandle)) {
- targetPhoneAccountHandle = null;
- } else {
- // The target phone account is valid and was found.
- return Arrays.asList(targetPhoneAccountHandle);
- }
- }
-
- if (targetPhoneAccountHandle == null && accounts.size() > 0) {
- // No preset account, check if default exists that supports the URI scheme for the
- // handle and verify it can be used.
- if (accounts.size() > 1) {
- PhoneAccountHandle defaultPhoneAccountHandle =
- mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(
- handle.getScheme(), initiatingUser);
- if (defaultPhoneAccountHandle != null &&
- accounts.contains(defaultPhoneAccountHandle)) {
- accounts.clear();
- accounts.add(defaultPhoneAccountHandle);
- }
- }
- }
- } else {
- // Self-managed ConnectionServices can only have a single potential account.
- accounts = Arrays.asList(targetPhoneAccountHandle);
+ // Try to find a potential phone account, taking into account whether this is a video
+ // call.
+ accounts = constructPossiblePhoneAccounts(handle, initiatingUser, isVideo);
+ if (isVideo && accounts.size() == 0) {
+ // Placing a video call but no video capable accounts were found, so consider any
+ // call capable accounts (we can fallback to audio).
+ accounts = constructPossiblePhoneAccounts(handle, initiatingUser,
+ false /* isVideo */);
}
- return accounts;
+ Log.v(this, "findOutgoingCallPhoneAccount: accounts = " + accounts);
+
+ // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this
+ // call as if a phoneAccount was not specified (does the default behavior instead).
+ // Note: We will not attempt to dial with a requested phoneAccount if it is disabled.
+ if (targetPhoneAccountHandle != null) {
+ if (accounts.contains(targetPhoneAccountHandle)) {
+ // The target phone account is valid and was found.
+ return CompletableFuture.completedFuture(Arrays.asList(targetPhoneAccountHandle));
+ }
+ }
+
+ // Do the query for whether there's a preferred contact
+ final CompletableFuture<PhoneAccountHandle> userPreferredAccountForContact =
+ new CompletableFuture<>();
+ final List<PhoneAccountHandle> possibleAccounts = accounts;
+ mCallerInfoLookupHelper.startLookup(handle,
+ new CallerInfoLookupHelper.OnQueryCompleteListener() {
+ @Override
+ public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) {
+ // TODO: construct the acct handle from caller info
+ userPreferredAccountForContact.complete(null);
+ }
+
+ @Override
+ public void onContactPhotoQueryComplete(Uri handle, CallerInfo info) {
+ // ignore this
+ }
+ });
+
+ return userPreferredAccountForContact.thenApply(phoneAccountHandle -> {
+ if (phoneAccountHandle != null) {
+ return Collections.singletonList(phoneAccountHandle);
+ }
+ if (possibleAccounts.isEmpty() || possibleAccounts.size() == 1) {
+ return possibleAccounts;
+ }
+ // No preset account, check if default exists that supports the URI scheme for the
+ // handle and verify it can be used.
+ PhoneAccountHandle defaultPhoneAccountHandle =
+ mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(
+ handle.getScheme(), initiatingUser);
+ if (defaultPhoneAccountHandle != null &&
+ possibleAccounts.contains(defaultPhoneAccountHandle)) {
+ return Collections.singletonList(defaultPhoneAccountHandle);
+ }
+ return possibleAccounts;
+ });
}
/**
@@ -1671,6 +1824,15 @@
} else {
mLocallyDisconnectingCalls.add(call);
call.disconnect();
+ // Cancel any of the outgoing call futures if they're still around.
+ if (mPendingCallConfirm != null && !mPendingCallConfirm.isDone()) {
+ mPendingCallConfirm.complete(null);
+ mPendingCallConfirm = null;
+ }
+ if (mPendingAccountSelection != null && !mPendingAccountSelection.isDone()) {
+ mPendingAccountSelection.complete(null);
+ mPendingAccountSelection = null;
+ }
}
}
@@ -1892,51 +2054,15 @@
if (!mCalls.contains(call)) {
Log.i(this, "Attempted to add account to unknown call %s", call);
} else {
- call.setTargetPhoneAccount(account);
- PhoneAccount realPhoneAccount =
- mPhoneAccountRegistrar.getPhoneAccountUnchecked(account);
- if (realPhoneAccount != null && realPhoneAccount.getExtras() != null
- && realPhoneAccount.getExtras()
- .getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
- Log.d("phoneAccountSelected: default to voip mode for call %s", call.getId());
- call.setIsVoipAudioMode(true);
- }
-
- boolean isVoicemail = (call.getHandle() != null)
- && (PhoneAccount.SCHEME_VOICEMAIL.equals(call.getHandle().getScheme())
- || (realPhoneAccount != null && mPhoneAccountRegistrar.isVoiceMailNumber(
- realPhoneAccount.getAccountHandle(),
- call.getHandle().getSchemeSpecificPart())));
-
- if (!isVoicemail && (isRttSettingOn() || call.getIntentExtras()
- .getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, false))) {
- Log.d(this, "Outgoing call after account selection requesting RTT," +
- " rtt setting is %b", isRttSettingOn());
- if (realPhoneAccount != null
- && realPhoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
- call.createRttStreams();
- }
- // Even if the phone account doesn't support RTT yet, the connection manager might
- // change that. Set this to check it later.
- call.setRequestedToStartWithRtt();
- }
-
- if (!call.isNewOutgoingCallIntentBroadcastDone()) {
- return;
- }
-
- // Note: emergency calls never go through account selection dialog so they never
- // arrive here.
- if (makeRoomForOutgoingCall(call, false /* isEmergencyCall */)) {
- call.startCreateConnection(mPhoneAccountRegistrar);
- } else {
- call.disconnect("no room");
- }
-
if (setDefault) {
mPhoneAccountRegistrar
.setUserSelectedOutgoingPhoneAccount(account, call.getInitiatingUser());
}
+
+ if (mPendingAccountSelection != null) {
+ mPendingAccountSelection.complete(Pair.create(call, account));
+ mPendingAccountSelection = null;
+ }
}
}
@@ -2101,7 +2227,7 @@
* indicate to the user that the call cannot me placed due to an ongoing call in another app.
*
* Used when there are ongoing self-managed calls and the user tries to make an outgoing managed
- * call. Called by {@link #startCallConfirmation(Call)} when the user is already confirming an
+ * call. Called by {@link #startCallConfirmation} when the user is already confirming an
* outgoing call. Realistically this should almost never be called since in practice the user
* won't make multiple outgoing calls at the same time.
*
@@ -2324,6 +2450,11 @@
return mPhoneNumberUtilsAdapter;
}
+ @VisibleForTesting
+ public CompletableFuture<Call> getLatestPostSelectionProcessingFuture() {
+ return mLatestPostSelectionProcessingFuture;
+ }
+
/**
* Returns the first call that it finds with the given states. The states are treated as having
* priority order so that any call with the first state will be returned before any call with
@@ -2383,8 +2514,6 @@
this,
mLock,
mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
null /* handle */,
null /* gatewayInfo */,
@@ -3099,8 +3228,6 @@
this,
mLock,
mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
connection.getHandle() /* handle */,
null /* gatewayInfo */,
@@ -3345,16 +3472,13 @@
Log.i(this, "confirmPendingCall: callId=%s", callId);
if (mPendingCall != null && mPendingCall.getId().equals(callId)) {
Log.addEvent(mPendingCall, LogUtils.Events.USER_CONFIRMED);
- addCall(mPendingCall);
// We are going to place the new outgoing call, so disconnect any ongoing self-managed
// calls which are ongoing at this time.
disconnectSelfManagedCalls("outgoing call " + callId);
- // Kick of the new outgoing call intent from where it left off prior to confirming the
- // call.
- CallIntentProcessor.sendNewOutgoingCallIntent(mContext, mPendingCall, this,
- mPendingCall.getOriginalCallIntent());
+ mPendingCallConfirm.complete(mPendingCall);
+ mPendingCallConfirm = null;
mPendingCall = null;
}
}
@@ -3373,6 +3497,8 @@
markCallAsDisconnected(mPendingCall, new DisconnectCause(DisconnectCause.CANCELED));
markCallAsRemoved(mPendingCall);
mPendingCall = null;
+ mPendingCallConfirm.complete(null);
+ mPendingCallConfirm = null;
}
}
@@ -3383,15 +3509,17 @@
* outgoing call or not.
* @param call The call to confirm.
*/
- private void startCallConfirmation(Call call) {
+ private void startCallConfirmation(Call call, CompletableFuture<Call> confirmationFuture) {
if (mPendingCall != null) {
Log.i(this, "startCallConfirmation: call %s is already pending; disconnecting %s",
mPendingCall.getId(), call.getId());
markCallDisconnectedDueToSelfManagedCall(call);
+ confirmationFuture.complete(null);
return;
}
Log.addEvent(call, LogUtils.Events.USER_CONFIRMATION);
mPendingCall = call;
+ mPendingCallConfirm = confirmationFuture;
// Figure out the name of the app in charge of the self-managed call(s).
Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
@@ -3540,6 +3668,18 @@
call.getAnalytics().setCallSource(callSource);
}
+ private boolean isVoicemail(Uri callHandle, PhoneAccount phoneAccount) {
+ if (callHandle == null) {
+ return false;
+ }
+ if (PhoneAccount.SCHEME_VOICEMAIL.equals(callHandle.getScheme())) {
+ return true;
+ }
+ return phoneAccount != null && mPhoneAccountRegistrar.isVoiceMailNumber(
+ phoneAccount.getAccountHandle(),
+ callHandle.getSchemeSpecificPart());
+ }
+
/**
* Notifies the {@link android.telecom.ConnectionService} associated with a
* {@link PhoneAccountHandle} that the attempt to create a new connection has failed.
@@ -3615,9 +3755,8 @@
}
extras.putParcelable(TelecomManager.EXTRA_CALL_AUDIO_STATE,
mCallAudioManager.getCallAudioState());
- Call handoverToCall = startOutgoingCall(handoverFromCall.getHandle(), handoverToHandle,
- extras, getCurrentUserHandle(), null /* originalIntent */,
- null /* callingPackage */);
+ Call handoverToCall = createHandoverCall(handoverFromCall.getHandle(), handoverToHandle,
+ extras, getCurrentUserHandle());
if (handoverToCall == null) {
handoverFromCall.sendCallEvent(android.telecom.Call.EVENT_HANDOVER_FAILED, null);
return;
@@ -3634,6 +3773,110 @@
videoState);
}
+ public Call createHandoverCall(Uri handle, PhoneAccountHandle handoverToHandle,
+ Bundle extras, UserHandle initiatingUser) {
+ boolean isReusedCall = true;
+ Call call = reuseOutgoingCall(handle);
+
+ PhoneAccount account =
+ mPhoneAccountRegistrar.getPhoneAccount(handoverToHandle, initiatingUser);
+ boolean isSelfManaged = account != null && account.isSelfManaged();
+
+ // Create a call with original handle. The handle may be changed when the call is attached
+ // to a connection service, but in most cases will remain the same.
+ if (call == null) {
+ call = new Call(getNextCallId(), mContext,
+ this,
+ mLock,
+ mConnectionServiceRepository,
+ mPhoneNumberUtilsAdapter,
+ handle,
+ null /* gatewayInfo */,
+ null /* connectionManagerPhoneAccount */,
+ null /* handoverToHandle */,
+ Call.CALL_DIRECTION_OUTGOING /* callDirection */,
+ false /* forceAttachToExistingConnection */,
+ false, /* isConference */
+ mClockProxy);
+ call.initAnalytics(null);
+
+ // Ensure new calls related to self-managed calls/connections are set as such. This
+ // will be overridden when the actual connection is returned in startCreateConnection,
+ // however doing this now ensures the logs and any other logic will treat this call as
+ // self-managed from the moment it is created.
+ call.setIsSelfManaged(isSelfManaged);
+ if (isSelfManaged) {
+ // Self-managed calls will ALWAYS use voip audio mode.
+ call.setIsVoipAudioMode(true);
+ }
+ call.setInitiatingUser(initiatingUser);
+ isReusedCall = false;
+ }
+
+ int videoState = VideoProfile.STATE_AUDIO_ONLY;
+ if (extras != null) {
+ // Set the video state on the call early so that when it is added to the InCall UI the
+ // UI knows to configure itself as a video call immediately.
+ videoState = extras.getInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+ VideoProfile.STATE_AUDIO_ONLY);
+
+ // If this is an emergency video call, we need to check if the phone account supports
+ // emergency video calling.
+ // Also, ensure we don't try to place an outgoing call with video if video is not
+ // supported.
+ if (VideoProfile.isVideo(videoState)) {
+ if (call.isEmergencyCall() && account != null &&
+ !account.hasCapabilities(PhoneAccount.CAPABILITY_EMERGENCY_VIDEO_CALLING)) {
+ // Phone account doesn't support emergency video calling, so fallback to
+ // audio-only now to prevent the InCall UI from setting up video surfaces
+ // needlessly.
+ Log.i(this, "startOutgoingCall - emergency video calls not supported; " +
+ "falling back to audio-only");
+ videoState = VideoProfile.STATE_AUDIO_ONLY;
+ } else if (account != null &&
+ !account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING)) {
+ // Phone account doesn't support video calling, so fallback to audio-only.
+ Log.i(this, "startOutgoingCall - video calls not supported; fallback to " +
+ "audio-only.");
+ videoState = VideoProfile.STATE_AUDIO_ONLY;
+ }
+ }
+
+ call.setVideoState(videoState);
+ }
+
+ call.setTargetPhoneAccount(handoverToHandle);
+
+ // If there's no more room for a handover, just fail.
+ if ((!isReusedCall && !makeRoomForOutgoingCall(call, call.isEmergencyCall()))) {
+ return null;
+ }
+
+ PhoneAccount accountToUse =
+ mPhoneAccountRegistrar.getPhoneAccount(handoverToHandle, initiatingUser);
+ if (accountToUse != null && accountToUse.getExtras() != null) {
+ if (accountToUse.getExtras()
+ .getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
+ Log.d(this, "startOutgoingCall: defaulting to voip mode for call %s",
+ call.getId());
+ call.setIsVoipAudioMode(true);
+ }
+ }
+
+ call.setState(
+ CallState.CONNECTING,
+ handoverToHandle == null ? "no-handle" : handoverToHandle.toString());
+
+ setIntentExtrasAndStartTime(call, extras);
+
+ if (!mCalls.contains(call)) {
+ // We check if mCalls already contains the call because we could potentially be reusing
+ // a call which was previously added (See {@link #reuseOutgoingCall}).
+ addCall(call);
+ }
+
+ return call;
+ }
/**
* Called in response to a {@link Call} receiving a {@link Call#handoverTo(PhoneAccountHandle,
* int, Bundle)} indicating the {@link android.telecom.InCallService} has requested a
@@ -3676,7 +3919,7 @@
Call call = new Call(getNextCallId(), mContext,
this, mLock, mConnectionServiceRepository,
- mContactsAsyncHelper, mCallerInfoAsyncQueryFactory, mPhoneNumberUtilsAdapter,
+ mPhoneNumberUtilsAdapter,
handoverFromCall.getHandle(), null,
null, null,
Call.CALL_DIRECTION_OUTGOING, false,
@@ -3876,8 +4119,6 @@
this,
mLock,
mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
srcAddr,
null /* gatewayInfo */,
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 9153414..ffa6035 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -243,6 +243,10 @@
mMissedCallNotifier = missedCallNotifierImplFactory
.makeMissedCallNotifierImpl(mContext, mPhoneAccountRegistrar, defaultDialerCache);
+ CallerInfoLookupHelper callerInfoLookupHelper =
+ new CallerInfoLookupHelper(context, callerInfoAsyncQueryFactory,
+ mContactsAsyncHelper, mLock);
+
EmergencyCallHelper emergencyCallHelper = new EmergencyCallHelper(mContext,
mContext.getResources().getString(R.string.ui_default_package), timeoutsAdapter);
@@ -262,8 +266,7 @@
mCallsManager = new CallsManager(
mContext,
mLock,
- mContactsAsyncHelper,
- callerInfoAsyncQueryFactory,
+ callerInfoLookupHelper,
mMissedCallNotifier,
mPhoneAccountRegistrar,
headsetMediaButtonFactory,
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index e863d27..51bfed7 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -338,6 +338,9 @@
mInCallServiceFixtureY.getCall(callId).getState());
mInCallServiceFixtureX.mInCallAdapter.phoneAccountSelected(callId,
mPhoneAccountA0.getAccountHandle(), false);
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ verifyAndProcessOutgoingCallBroadcast(mPhoneAccountA0.getAccountHandle());
IdPair ids = outgoingCallPhoneAccountSelected(mPhoneAccountA0.getAccountHandle(),
startingNumConnections, startingNumCalls, mConnectionServiceFixtureA);
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index dff89c9..fe4213f 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -25,6 +25,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
@@ -50,6 +51,7 @@
import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallerInfoAsyncQueryFactory;
+import com.android.server.telecom.CallerInfoLookupHelper;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ClockProxy;
import com.android.server.telecom.ConnectionServiceFocusManager;
@@ -87,6 +89,8 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.Arrays;
@@ -120,8 +124,7 @@
private static final Uri TEST_ADDRESS = Uri.parse("tel:555-1212");
private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
- @Mock private ContactsAsyncHelper mContactsAsyncHelper;
- @Mock private CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
+ @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
@Mock private MissedCallNotifier mMissedCallNotifier;
@Mock private PhoneAccountRegistrar mPhoneAccountRegistrar;
@Mock private HeadsetMediaButton mHeadsetMediaButton;
@@ -178,8 +181,7 @@
mCallsManager = new CallsManager(
mComponentContextFixture.getTestDouble().getApplicationContext(),
mLock,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
+ mCallerInfoLookupHelper,
mMissedCallNotifier,
mPhoneAccountRegistrar,
mHeadsetMediaButtonFactory,
@@ -234,8 +236,6 @@
mCallsManager,
mLock,
null /* ConnectionServiceRepository */,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
TEST_ADDRESS,
null /* GatewayInfo */,
@@ -268,6 +268,15 @@
assertEquals(2, phoneAccountHandles.size());
}
+ private void setupCallerInfoLookupHelper() {
+ doAnswer(invocation -> {
+ Uri handle = invocation.getArgument(0);
+ CallerInfoLookupHelper.OnQueryCompleteListener listener = invocation.getArgument(1);
+ listener.onCallerInfoQueryComplete(handle, null);
+ return null;
+ }).when(mCallerInfoLookupHelper).startLookup(any(Uri.class),
+ any(CallerInfoLookupHelper.OnQueryCompleteListener.class));
+ }
/**
* Tests finding the outgoing call phone account where the call is being placed on a
* self-managed ConnectionService.
@@ -276,8 +285,10 @@
@MediumTest
@Test
public void testFindOutgoingCallPhoneAccountSelfManaged() throws Exception {
+ setupCallerInfoLookupHelper();
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- SELF_MANAGED_HANDLE, TEST_ADDRESS, false /* isVideo */, null /* userHandle */);
+ SELF_MANAGED_HANDLE, TEST_ADDRESS, false /* isVideo */, null /* userHandle */)
+ .get();
assertEquals(1, accounts.size());
assertEquals(SELF_MANAGED_HANDLE, accounts.get(0));
}
@@ -290,6 +301,7 @@
@MediumTest
@Test
public void testFindOutgoingCallAccountDefault() throws Exception {
+ setupCallerInfoLookupHelper();
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
SIM_1_HANDLE);
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
@@ -297,7 +309,8 @@
new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- null /* phoneAcct */, TEST_ADDRESS, false /* isVideo */, null /* userHandle */);
+ null /* phoneAcct */, TEST_ADDRESS, false /* isVideo */, null /* userHandle */)
+ .get();
// Should have found just the default.
assertEquals(1, accounts.size());
@@ -312,6 +325,7 @@
@MediumTest
@Test
public void testFindOutgoingCallAccountNoDefault() throws Exception {
+ setupCallerInfoLookupHelper();
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
null);
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
@@ -319,7 +333,8 @@
new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- null /* phoneAcct */, TEST_ADDRESS, false /* isVideo */, null /* userHandle */);
+ null /* phoneAcct */, TEST_ADDRESS, false /* isVideo */, null /* userHandle */)
+ .get();
assertEquals(2, accounts.size());
assertTrue(accounts.contains(SIM_1_HANDLE));
@@ -334,6 +349,7 @@
@MediumTest
@Test
public void testFindOutgoingCallAccountVideo() throws Exception {
+ setupCallerInfoLookupHelper();
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
null);
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
@@ -341,7 +357,8 @@
new ArrayList<>(Arrays.asList(SIM_2_HANDLE)));
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, null /* userHandle */);
+ null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, null /* userHandle */)
+ .get();
assertEquals(1, accounts.size());
assertTrue(accounts.contains(SIM_2_HANDLE));
@@ -355,6 +372,7 @@
@MediumTest
@Test
public void testFindOutgoingCallAccountVideoNotAvailable() throws Exception {
+ setupCallerInfoLookupHelper();
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
null);
// When querying for video capable accounts, return nothing.
@@ -366,7 +384,8 @@
any(), eq(0 /* none specified */))).thenReturn(
new ArrayList<>(Arrays.asList(SIM_1_HANDLE)));
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, null /* userHandle */);
+ null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, null /* userHandle */)
+ .get();
// Should have found one.
assertEquals(1, accounts.size());
@@ -380,6 +399,7 @@
@MediumTest
@Test
public void testUseSpecifiedAccount() throws Exception {
+ setupCallerInfoLookupHelper();
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
null);
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
@@ -387,7 +407,7 @@
new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- SIM_2_HANDLE, TEST_ADDRESS, false /* isVideo */, null /* userHandle */);
+ SIM_2_HANDLE, TEST_ADDRESS, false /* isVideo */, null /* userHandle */).get();
assertEquals(1, accounts.size());
assertTrue(accounts.contains(SIM_2_HANDLE));
@@ -912,8 +932,6 @@
mCallsManager,
mLock, /* ConnectionServiceRepository */
null,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
TEST_ADDRESS,
null /* GatewayInfo */,
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 282a74e..c4c30d1 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -561,8 +561,11 @@
ConnectionServiceFixture connectionServiceFixture)
throws Exception {
- return startOutgoingPhoneCallPendingCreateConnection(number, null,
- connectionServiceFixture, Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY);
+ startOutgoingPhoneCallWaitForBroadcaster(number, null,
+ connectionServiceFixture, Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY,
+ false /*isEmergency*/);
+
+ return mInCallServiceFixtureX.mLatestCallId;
}
protected IdPair outgoingCallPhoneAccountSelected(PhoneAccountHandle phoneAccountHandle,
@@ -687,13 +690,17 @@
// Send the CallerInfo lookup reply.
mCallerInfoAsyncQueryFactoryFixture.mRequests.forEach(
CallerInfoAsyncQueryFactoryFixture.Request::reply);
+ if (phoneAccountHandle != null) {
+ mTelecomSystem.getCallsManager().getLatestPostSelectionProcessingFuture().join();
+ }
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
boolean isSelfManaged = phoneAccountHandle == mPhoneAccountSelfManaged.getAccountHandle();
if (!hasInCallAdapter && !isSelfManaged) {
- verify(mInCallServiceFixtureX.getTestDouble())
+ verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
.setInCallAdapter(
any(IInCallAdapter.class));
- verify(mInCallServiceFixtureY.getTestDouble())
+ verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT))
.setInCallAdapter(
any(IInCallAdapter.class));
}
@@ -705,7 +712,13 @@
int videoState) throws Exception {
startOutgoingPhoneCallWaitForBroadcaster(number,phoneAccountHandle,
connectionServiceFixture, initiatingUser, videoState, false /*isEmergency*/);
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ verifyAndProcessOutgoingCallBroadcast(phoneAccountHandle);
+ return mInCallServiceFixtureX.mLatestCallId;
+ }
+
+ protected void verifyAndProcessOutgoingCallBroadcast(PhoneAccountHandle phoneAccountHandle) {
ArgumentCaptor<Intent> newOutgoingCallIntent =
ArgumentCaptor.forClass(Intent.class);
ArgumentCaptor<BroadcastReceiver> newOutgoingCallReceiver =
@@ -734,7 +747,6 @@
newOutgoingCallIntent.getValue());
}
- return mInCallServiceFixtureX.mLatestCallId;
}
// When Telecom is redialing due to an error, we need to make sure the number of connections