Merge "Add Call Redirection app into RoleManagerAdapter in Telecom"
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/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index a8f2bc8..b68a851 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -727,9 +727,11 @@
             Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
 
             if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
-                mPlayerFactory.createPlayer(toneToPlay).startTone();
-                mCallsManager.onDisconnectedTonePlaying(true);
-                mIsDisconnectedTonePlaying = true;
+                boolean didToneStart = mPlayerFactory.createPlayer(toneToPlay).startTone();
+                if (didToneStart) {
+                    mCallsManager.onDisconnectedTonePlaying(true);
+                    mIsDisconnectedTonePlaying = true;
+                }
             }
         }
     }
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/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 0cc7ad2..98d5bba 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -427,11 +427,11 @@
     }
 
     @VisibleForTesting
-    public void startTone() {
+    public boolean startTone() {
         // Skip playing the end call tone if the volume is silenced.
         if (mToneId == TONE_CALL_ENDED && !mAudioManagerAdapter.isVolumeOverZero()) {
             Log.i(this, "startTone: skip end-call tone as device is silenced.");
-            return;
+            return false;
         }
 
         sTonesPlaying++;
@@ -447,6 +447,7 @@
         }
 
         super.start();
+        return true;
     }
 
     @Override
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/CallAudioManagerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
index e4b22ec..01add22 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
@@ -77,7 +77,7 @@
             InCallTonePlayer mockInCallTonePlayer = mock(InCallTonePlayer.class);
             doAnswer((invocation2) -> {
                 mCallAudioManager.setIsTonePlaying(true);
-                return null;
+                return true;
             }).when(mockInCallTonePlayer).startTone();
             return mockInCallTonePlayer;
         }).when(mPlayerFactory).createPlayer(anyInt());
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/InCallTonePlayerTest.java b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
index 7718da8..eba494f 100644
--- a/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
+++ b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom.tests;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -114,7 +116,7 @@
     public void testNoEndCallToneInSilence() {
         when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(false);
         InCallTonePlayer player = mFactory.createPlayer(InCallTonePlayer.TONE_CALL_ENDED);
-        player.startTone();
+        assertFalse(player.startTone());
 
         // Verify we didn't play a tone.
         verify(mCallAudioManager, never()).setIsTonePlaying(eq(true));
@@ -126,7 +128,7 @@
     public void testEndCallToneWhenNotSilenced() {
         when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
         InCallTonePlayer player = mFactory.createPlayer(InCallTonePlayer.TONE_CALL_ENDED);
-        player.startTone();
+        assertTrue(player.startTone());
 
         // Verify we did play a tone.
         verify(mCallAudioManager).setIsTonePlaying(eq(true));
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index ab5ddef..02ee137 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -123,6 +123,7 @@
         when(mockAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         NotificationManager notificationManager =
                 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        when(mockTonePlayer.startTone()).thenReturn(true);
         when(notificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
         mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
                 mockRingtonePlayer, mockRingtoneFactory, mockVibrator, spyVibrationEffectProxy,
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 282a74e..c60a428 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
@@ -852,20 +864,6 @@
         //Wait for/Verify call blocking happened asynchronously
         incomingCallAddedLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
 
-        // Do the blocked number check only for non-self-managed calls
-        PhoneAccount pa = mTelecomSystem.getPhoneAccountRegistrar()
-                .getPhoneAccount(phoneAccountHandle, phoneAccountHandle.getUserHandle());
-        if (!pa.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
-            IContentProvider blockedNumberProvider =
-                    mSpyContext.getContentResolver().acquireProvider(
-                            BlockedNumberContract.AUTHORITY);
-            verify(blockedNumberProvider, timeout(TEST_TIMEOUT)).call(
-                    anyString(),
-                    eq(BlockedNumberContract.SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER),
-                    eq(number),
-                    isNotNull(Bundle.class));
-        }
-
         // For the case of incoming calls, Telecom connecting the InCall services and adding the
         // Call is triggered by the async completion of the CallerInfoAsyncQuery. Once the Call
         // is added, future interactions as triggered by the ConnectionService, through the various