Merge "Resolve separate binding for BT ICS." into main
diff --git a/res/values/config.xml b/res/values/config.xml
index 9375798..ae5d88e 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -80,12 +80,16 @@
      callers are combined into a single toggle. -->
     <bool name="combine_options_to_block_unavailable_and_unknown_callers">true</bool>
 
-    <!-- System bluetooth stack package name -->
-    <string name="system_bluetooth_stack">com.android.bluetooth</string>
-
     <!-- When true, skip fetching quick reply response -->
     <bool name="skip_loading_canned_text_response">false</bool>
 
     <!-- When true, skip fetching incoming caller info -->
     <bool name="skip_incoming_caller_info_query">false</bool>
+
+    <string-array name="system_bluetooth_stack_package_name" translatable="false">
+        <!-- AOSP -->
+        <item>com.android.bluetooth</item>
+        <!-- Used for internal targets -->
+        <item>com.google.android.bluetooth</item>
+    </string-array>
 </resources>
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 7e5f6de..002ba11 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -4816,12 +4816,20 @@
     }
 
     /**
+     * @return The binding {@link CompletableFuture} for the BT ICS.
+     */
+    public CompletableFuture<Boolean> getBtIcsFuture() {
+        return mBtIcsFuture;
+    }
+
+    /**
      * Wait for bluetooth {@link android.telecom.InCallService} binding completion or timeout. Used
      * for audio routing operations for a ringing call.
      */
     public void waitForBtIcs() {
         if (mBtIcsFuture != null) {
             try {
+                Log.i(this, "waitForBtIcs: waiting for BT service to bind");
                 mBtIcsFuture.get();
             } catch (InterruptedException | ExecutionException e) {
                 // ignore
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index e5678a0..1f1ca9d 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -20,6 +20,8 @@
 import android.content.Context;
 import android.media.IAudioService;
 import android.media.ToneGenerator;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.UserHandle;
 import android.telecom.CallAudioState;
 import android.telecom.Log;
@@ -36,6 +38,7 @@
 import java.util.HashSet;
 import java.util.Set;
 import java.util.LinkedHashSet;
+import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 
 
@@ -66,9 +69,13 @@
 
     private Call mStreamingCall;
     private Call mForegroundCall;
+    private CompletableFuture<Boolean> mCallRingingFuture;
+    private Thread mBtIcsBindingThread;
     private boolean mIsTonePlaying = false;
     private boolean mIsDisconnectedTonePlaying = false;
     private InCallTonePlayer mHoldTonePlayer;
+    private final HandlerThread mHandlerThread;
+    private final Handler mHandler;
 
     public CallAudioManager(CallAudioRouteAdapter callAudioRouteAdapter,
             CallsManager callsManager,
@@ -105,6 +112,9 @@
         mBluetoothStateReceiver = bluetoothStateReceiver;
         mDtmfLocalTonePlayer = dtmfLocalTonePlayer;
         mFeatureFlags = featureFlags;
+        mHandlerThread = new HandlerThread(this.getClass().getSimpleName());
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
 
         mPlayerFactory.setCallAudioManager(this);
         mCallAudioModeStateMachine.setCallAudioManager(this);
@@ -750,14 +760,42 @@
 
     private void onCallEnteringRinging() {
         if (mRingingCalls.size() == 1) {
-            // Wait until the BT ICS binding completed to request further audio route change
-            for (Call ringingCall: mRingingCalls) {
-                ringingCall.waitForBtIcs();
+            Log.i(this, "onCallEnteringRinging: mFeatureFlags.separatelyBindToBtIncallService() ? %s",
+                    mFeatureFlags.separatelyBindToBtIncallService());
+            Log.i(this, "onCallEnteringRinging: mRingingCalls.getFirst().getBtIcsFuture() = %s",
+                    mRingingCalls.getFirst().getBtIcsFuture());
+            if (mFeatureFlags.separatelyBindToBtIncallService()
+                    && mRingingCalls.getFirst().getBtIcsFuture() != null) {
+                mCallRingingFuture  = mRingingCalls.getFirst().getBtIcsFuture()
+                        .thenComposeAsync((completed) -> {
+                            mCallAudioModeStateMachine.sendMessageWithArgs(
+                                    CallAudioModeStateMachine.NEW_RINGING_CALL,
+                                    makeArgsForModeStateMachine());
+                            return CompletableFuture.completedFuture(completed);
+                        }, new LoggedHandlerExecutor(mHandler, "CAM.oCER", mCallsManager.getLock()))
+                        .exceptionally((throwable) -> {
+                            Log.e(this, throwable, "Error while executing BT ICS future");
+                            // Fallback on performing computation on a separate thread.
+                            handleBtBindingWaitFallback();
+                            return null;
+                        });
+            } else {
+                mCallAudioModeStateMachine.sendMessageWithArgs(
+                        CallAudioModeStateMachine.NEW_RINGING_CALL,
+                        makeArgsForModeStateMachine());
             }
+        }
+    }
+
+    private void handleBtBindingWaitFallback() {
+        // Wait until the BT ICS binding completed to request further audio route change
+        mBtIcsBindingThread = new Thread(() -> {
+            mRingingCalls.getFirst().waitForBtIcs();
             mCallAudioModeStateMachine.sendMessageWithArgs(
                     CallAudioModeStateMachine.NEW_RINGING_CALL,
                     makeArgsForModeStateMachine());
-        }
+        });
+        mBtIcsBindingThread.start();
     }
 
     private void onCallEnteringHold() {
@@ -889,12 +927,14 @@
         // we will not play a disconnect tone.
         if (call.isHandoverInProgress()) {
             Log.i(LOG_TAG, "Omitting tone because %s is being handed over.", call);
+            completeDisconnectToneFuture(call);
             return;
         }
 
         if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) {
             Log.v(LOG_TAG, "Omitting tone because we are not foreground" +
                     " and there is another call.");
+            completeDisconnectToneFuture(call);
             return;
         }
 
@@ -935,6 +975,8 @@
                     mCallsManager.onDisconnectedTonePlaying(call, true);
                     mIsDisconnectedTonePlaying = true;
                 }
+            } else {
+                completeDisconnectToneFuture(call);
             }
         }
     }
@@ -1022,6 +1064,14 @@
                 oldState == CallState.ON_HOLD;
     }
 
+    private void completeDisconnectToneFuture(Call call) {
+        CompletableFuture<Void> disconnectedToneFuture = mCallsManager.getInCallController()
+                .getDisconnectedToneBtFutures().get(call.getId());
+        if (disconnectedToneFuture != null) {
+            disconnectedToneFuture.complete(null);
+        }
+    }
+
     @VisibleForTesting
     public Set<Call> getTrackedCalls() {
         return mCalls;
@@ -1031,4 +1081,9 @@
     public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() {
         return mCallStateToCalls;
     }
+
+    @VisibleForTesting
+    public CompletableFuture<Boolean> getCallRingingFuture() {
+        return mCallRingingFuture;
+    }
 }
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 0744502..dcf3e55 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -1035,7 +1035,8 @@
 
         if (result.shouldAllowCall) {
             if (mFeatureFlags.separatelyBindToBtIncallService()) {
-                incomingCall.setBtIcsFuture(mInCallController.bindToBTService(incomingCall));
+                mInCallController.bindToBTService(incomingCall, null);
+                incomingCall.setBtIcsFuture(mInCallController.getBtBindingFuture(incomingCall));
                 setCallState(incomingCall, CallState.RINGING, "successful incoming call");
             }
             incomingCall.setPostCallPackageName(
diff --git a/src/com/android/server/telecom/DefaultDialerCache.java b/src/com/android/server/telecom/DefaultDialerCache.java
index d819780..44b426a 100644
--- a/src/com/android/server/telecom/DefaultDialerCache.java
+++ b/src/com/android/server/telecom/DefaultDialerCache.java
@@ -176,7 +176,7 @@
                         UserHandle.USER_ALL);
     }
 
-    public String getBTInCallServicePackage() {
+    public String[] getBTInCallServicePackages() {
         return mRoleManagerAdapter.getBTInCallService();
     }
 
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index e625bbe..f3c84ba 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -370,7 +370,8 @@
             // not be running (handled in getUserFromCall).
             UserHandle userToBind = isManagedProfile ? userFromCall : UserHandle.CURRENT;
             if ((mInCallServiceInfo.mType == IN_CALL_SERVICE_TYPE_NON_UI
-                    || mInCallServiceInfo.mType == IN_CALL_SERVICE_TYPE_CAR_MODE_UI) && (
+                    || mInCallServiceInfo.mType == IN_CALL_SERVICE_TYPE_CAR_MODE_UI
+                    || mInCallServiceInfo.mType == IN_CALL_SERVICE_TYPE_BLUETOOTH) && (
                     mUserHandleToUseForBinding != null)) {
                 //guarding change for non-UI/carmode-UI services which may not be present for
                 // work profile.
@@ -1080,6 +1081,7 @@
                 if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())) {
                     synchronized (mLock) {
                         int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
+                        String changedPackage = intent.getData().getSchemeSpecificPart();
                         UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
                         boolean isManagedProfile = um.isManagedProfile(userHandle.getIdentifier());
 
@@ -1109,12 +1111,36 @@
                                         childManagedProfileUser);
                         List<InCallServiceBindingConnection> componentsToBindForUser = null;
                         List<InCallServiceBindingConnection> componentsToBindForChild = null;
+                        // Separate binding for BT logic.
+                        boolean isBluetoothPkg = isBluetoothPackage(changedPackage);
+                        Call callToConnectWith = mCallIdMapper.getCalls().isEmpty()
+                                ? null
+                                : mCallIdMapper.getCalls().iterator().next();
+
+                        // Bind to BT service if there's an available call. When the flag isn't
+                        // enabled, the service will be included as part of
+                        // getNonUiInCallServiceBindingConnectionList.
+                        if (mFeatureFlags.separatelyBindToBtIncallService()
+                                && isBluetoothPkg && callToConnectWith != null) {
+                            // mNonUIInCallServiceConnections will always contain a key for
+                            // userHandle and/or the child user if there is an ongoing call with
+                            // that user, regardless if there aren't any non-UI ICS bound.
+                            if (isUserKeyPresent) {
+                                bindToBTService(callToConnectWith, userHandle);
+                            }
+                            if (isChildUserKeyPresent) {
+                                // This will try to use the ICS found in the parent if one isn't
+                                // available for the child.
+                                bindToBTService(callToConnectWith, childManagedProfileUser);
+                            }
+                        }
 
                         if(isUserKeyPresent) {
                             componentsToBindForUser =
                                     getNonUiInCallServiceBindingConnectionList(intent,
                                             userHandle, null);
                         }
+
                         if (isChildUserKeyPresent) {
                             componentsToBindForChild =
                                     getNonUiInCallServiceBindingConnectionList(intent,
@@ -1127,11 +1153,11 @@
                                 isUserKeyPresent, isChildUserKeyPresent, isManagedProfile,
                                 userHandle.getIdentifier());
 
-                        if (isUserKeyPresent && componentsToBindForUser != null) {
+                        if (isUserKeyPresent && !componentsToBindForUser.isEmpty()) {
                             mNonUIInCallServiceConnections.get(userHandle).
                                     addConnections(componentsToBindForUser);
                         }
-                        if (isChildUserKeyPresent && componentsToBindForChild != null) {
+                        if (isChildUserKeyPresent && !componentsToBindForChild.isEmpty()) {
                             mNonUIInCallServiceConnections.get(childManagedProfileUser).
                                     addConnections(componentsToBindForChild);
                         }
@@ -1186,6 +1212,10 @@
     private static final int IN_CALL_SERVICE_TYPE_COMPANION = 5;
     private static final int IN_CALL_SERVICE_TYPE_BLUETOOTH = 6;
 
+    // Timeout value to be used to ensure future completion for mDisconnectedToneBtFutures. This is
+    // set to 4 seconds to account for the exceptional case (TONE_CONGESTION).
+    private static final int DISCONNECTED_TONE_TIMEOUT = 4000;
+
     private static final int[] LIVE_CALL_STATES = { CallState.ACTIVE, CallState.PULLING,
             CallState.DISCONNECTING };
 
@@ -1233,7 +1263,10 @@
     // in-call service.
     // The future will complete with true if bluetooth in-call service succeeds, false if it timed
     // out.
-    private CompletableFuture<Boolean> mBtBindingFuture = CompletableFuture.completedFuture(true);
+    private Map<UserHandle, CompletableFuture<Boolean>> mBtBindingFuture = new ArrayMap<>();
+    // Future used to delay terminating the BT InCallService before the call disconnect tone
+    // finishes playing.
+    private Map<String, CompletableFuture<Void>> mDisconnectedToneBtFutures = new ArrayMap<>();
 
     private final CarModeTracker mCarModeTracker;
 
@@ -1256,6 +1289,8 @@
 
     private boolean mIsStartCallDelayScheduled = false;
 
+    private boolean mDisconnectedToneStartedPlaying = false;
+
     /**
      * A list of call IDs which are currently using the camera.
      */
@@ -1375,27 +1410,29 @@
         addCall(call);
 
         if (mFeatureFlags.separatelyBindToBtIncallService()) {
-            boolean bindBTService = false;
-            boolean bindOtherServices = false;
+            boolean bindingToBtRequired = false;
+            boolean bindingToOtherServicesRequired = false;
             if (!isBoundAndConnectedToBTService(userFromCall)) {
                 Log.i(this, "onCallAdded: %s; not bound or connected to BT ICS.", call);
-                bindBTService = true;
-                bindToBTService(call);
+                bindingToBtRequired = true;
+                bindToBTService(call, null);
             }
             if (!isBoundAndConnectedToServices(userFromCall)) {
                 Log.i(this, "onCallAdded: %s; not bound or connected to other ICS.", call);
                 // We are not bound, or we're not connected.
-                bindOtherServices = true;
-                bindToOtherServices(call);
+                bindingToOtherServicesRequired = true;
+                bindToServices(call);
             }
-            if (!bindBTService || !bindOtherServices) {
+            // If either BT service are already bound or other services are already bound, attempt
+            // to add the new call to the connected incall services.
+            if (!bindingToBtRequired || !bindingToOtherServicesRequired) {
                 addCallToConnectedServices(call, userFromCall);
             }
         } else {
             if (!isBoundAndConnectedToServices(userFromCall)) {
                 Log.i(this, "onCallAdded: %s; not bound or connected.", call);
                 // We are not bound, or we're not connected.
-                bindToServices(call, false);
+                bindToServices(call);
             } else {
                 addCallToConnectedServices(call, userFromCall);
             }
@@ -1507,17 +1544,31 @@
     @Override
     public void onDisconnectedTonePlaying(Call call, boolean isTonePlaying) {
         Log.i(this, "onDisconnectedTonePlaying: %s -> %b", call, isTonePlaying);
-
         if (mFeatureFlags.separatelyBindToBtIncallService()) {
             synchronized (mLock) {
-                mPendingEndToneCall.remove(call);
-                if (!mPendingEndToneCall.isEmpty()) {
-                    return;
-                }
-                UserHandle userHandle = getUserFromCall(call);
-                if (mBTInCallServiceConnections.containsKey(userHandle)) {
-                    mBTInCallServiceConnections.get(userHandle).disconnect();
-                    mBTInCallServiceConnections.remove(userHandle);
+                if (isTonePlaying) {
+                    mDisconnectedToneStartedPlaying = true;
+                } else if (mDisconnectedToneStartedPlaying) {
+                    mDisconnectedToneStartedPlaying = false;
+                    if (mDisconnectedToneBtFutures.containsKey(call.getId())) {
+                        Log.i(this, "onDisconnectedTonePlaying: completing BT "
+                                + "disconnected tone future");
+                        mDisconnectedToneBtFutures.get(call.getId()).complete(null);
+                    }
+                    mPendingEndToneCall.remove(call);
+                    if (!mPendingEndToneCall.isEmpty()) {
+                        return;
+                    }
+                    UserHandle userHandle = getUserFromCall(call);
+                    if (mBTInCallServiceConnections.containsKey(userHandle)) {
+                        Log.i(this, "onDisconnectedTonePlaying: Unbinding BT service");
+                        mBTInCallServiceConnections.get(userHandle).disconnect();
+                        mBTInCallServiceConnections.remove(userHandle);
+                    }
+                    // Ensure that BT ICS instance is cleaned up
+                    if (mBTInCallServices.remove(userHandle) != null) {
+                        updateCombinedInCallServiceMap(userHandle);
+                    }
                 }
             }
         }
@@ -1969,6 +2020,8 @@
         }
         getCombinedInCallServiceMap().remove(userHandle);
         if (mFeatureFlags.separatelyBindToBtIncallService()) {
+            // Note that the BT ICS will be repopulated as part of the combined map if the
+            // BT ICS is still bound (disconnected tone hasn't finished playing).
             updateCombinedInCallServiceMap(userHandle);
         }
     }
@@ -1979,34 +2032,49 @@
      *
      * @param call The newly added call that triggered the binding to the in-call services.
      */
-    public CompletableFuture<Boolean> bindToBTService(Call call) {
+    public void bindToBTService(Call call, UserHandle userHandle) {
+        Log.i(this, "bindToBtService");
+        UserHandle userToBind = userHandle == null
+                ? getUserFromCall(call)
+                : userHandle;
+        UserManager um = mContext.getSystemService(UserManager.class);
+        UserHandle parentUser = mFeatureFlags.profileUserSupport()
+                ? um.getProfileParent(userToBind) : null;
+
+        if (!mFeatureFlags.profileUserSupport()
+                && um.isManagedProfile(userToBind.getIdentifier())) {
+            parentUser = um.getProfileParent(userToBind);
+        }
+
         // Track the call if we don't already know about it.
         addCall(call);
-        UserHandle userFromCall = getUserFromCall(call);
-
-        List<InCallServiceInfo> infos = getInCallServiceComponents(userFromCall,
+        List<InCallServiceInfo> infos = getInCallServiceComponents(userToBind,
                 IN_CALL_SERVICE_TYPE_BLUETOOTH);
+        boolean serviceUnavailableForUser = false;
         if (infos.size() == 0 || infos.get(0) == null) {
-            Log.w(this, "No available BT service");
-            mBtBindingFuture = CompletableFuture.completedFuture(false);
-            return mBtBindingFuture;
+            Log.i(this, "No available BT ICS for user (%s). Trying with parent instead.",
+                    userToBind);
+            serviceUnavailableForUser = true;
+            // Check if the service is available under the parent user instead.
+            if (parentUser != null) {
+                infos = getInCallServiceComponents(parentUser, IN_CALL_SERVICE_TYPE_BLUETOOTH);
+            }
+            if (infos.size() == 0 || infos.get(0) == null) {
+                Log.w(this, "No available BT ICS to bind to for user %s or its parent %s.",
+                        userToBind, parentUser);
+                mBtBindingFuture.put(userToBind, CompletableFuture.completedFuture(false));
+                return;
+            }
         }
-        mBtBindingFuture = new CompletableFuture<Boolean>().completeOnTimeout(false,
-                mTimeoutsAdapter.getCallBindBluetoothInCallServicesDelay(
-                        mContext.getContentResolver()), TimeUnit.MILLISECONDS);
-        new InCallServiceBindingConnection(infos.get(0)).connect(call);
-        return mBtBindingFuture;
-    }
 
-    /**
-     * Binds to all the UI-providing InCallService as well as system-implemented non-UI
-     * InCallServices except BT InCallServices. Method-invoker must check
-     * {@link #isBoundAndConnectedToServices(UserHandle)} before invoking.
-     *
-     * @param call The newly added call that triggered the binding to the in-call services.
-     */
-    public void bindToOtherServices(Call call) {
-        bindToServices(call, true);
+        mBtBindingFuture.put(userToBind, new CompletableFuture<Boolean>().completeOnTimeout(false,
+                mTimeoutsAdapter.getCallBindBluetoothInCallServicesDelay(
+                        mContext.getContentResolver()), TimeUnit.MILLISECONDS));
+        InCallServiceBindingConnection btIcsBindingConnection =
+                new InCallServiceBindingConnection(infos.get(0),
+                        serviceUnavailableForUser ? parentUser : userToBind);
+        mBTInCallServiceConnections.put(userToBind, btIcsBindingConnection);
+        btIcsBindingConnection.connect(call);
     }
 
     /**
@@ -2016,11 +2084,9 @@
      *
      * @param call           The newly added call that triggered the binding to the in-call
      *                      services.
-     * @param skipBTServices Boolean variable to specify if the binding to BT InCallService should
-     *                      be skipped
      */
     @VisibleForTesting
-    public void bindToServices(Call call, boolean skipBTServices) {
+    public void bindToServices(Call call) {
         UserHandle userFromCall = getUserFromCall(call);
         UserManager um = mContext.getSystemService(UserManager.class);
         UserHandle parentUser = mFeatureFlags.profileUserSupport()
@@ -2085,7 +2151,7 @@
             // Only connect to the non-ui InCallServices if we actually connected to the main UI
             // one, or if the call is self-managed (in which case we'd still want to keep Wear, BT,
             // etc. informed.
-            connectToNonUiInCallServices(call, skipBTServices);
+            connectToNonUiInCallServices(call);
             mBindingFuture = new CompletableFuture<Boolean>().completeOnTimeout(false,
                     mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
                             mContext.getContentResolver()),
@@ -2100,7 +2166,7 @@
                 packageChangedFilter, null, null);
     }
 
-    private void updateNonUiInCallServices(Call call, boolean skipBTService) {
+    private void updateNonUiInCallServices(Call call) {
         UserHandle userFromCall = getUserFromCall(call);
 
         UserManager um = mContext.getSystemService(UserManager.class);
@@ -2155,10 +2221,10 @@
                 nonUIInCalls));
     }
 
-    private void connectToNonUiInCallServices(Call call, boolean skipBTService) {
+    private void connectToNonUiInCallServices(Call call) {
         UserHandle userFromCall = getUserFromCall(call);
         if (!mNonUIInCallServiceConnections.containsKey(userFromCall)) {
-            updateNonUiInCallServices(call, skipBTService);
+            updateNonUiInCallServices(call);
         }
         mNonUIInCallServiceConnections.get(userFromCall).connect(call);
     }
@@ -2427,10 +2493,8 @@
             return IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI;
         }
 
-        String bluetoothPackage = mDefaultDialerCache.getBTInCallServicePackage();
-        if (mFeatureFlags.separatelyBindToBtIncallService()
-                && serviceInfo.packageName != null
-                && serviceInfo.packageName.equals(bluetoothPackage)
+        boolean processingBluetoothPackage = isBluetoothPackage(serviceInfo.packageName);
+        if (mFeatureFlags.separatelyBindToBtIncallService() && processingBluetoothPackage
                 && (hasControlInCallPermission || hasAppOpsPermittedManageOngoingCalls)) {
             return IN_CALL_SERVICE_TYPE_BLUETOOTH;
         }
@@ -2477,11 +2541,13 @@
         IInCallService inCallService = IInCallService.Stub.asInterface(service);
         if (mFeatureFlags.separatelyBindToBtIncallService()
                 && info.getType() == IN_CALL_SERVICE_TYPE_BLUETOOTH) {
-            if (mBtBindingFuture.isDone()) {
+            if (!mBtBindingFuture.containsKey(userHandle)
+                    || mBtBindingFuture.get(userHandle).isDone()) {
+                Log.i(this, "onConnected: BT binding future timed out.");
                 // Binding completed after the timeout. Clean up this binding
                 return false;
             } else {
-                mBtBindingFuture.complete(true);
+                mBtBindingFuture.get(userHandle).complete(true);
             }
             mBTInCallServices.put(userHandle, new Pair<>(info, inCallService));
         } else {
@@ -2652,12 +2718,20 @@
                 IInCallService inCallService = entry.getValue();
                 componentsUpdated.add(componentName);
 
-                try {
-                    inCallService.updateCall(
-                            sanitizeParcelableCallForService(info, parcelableCall));
-                } catch (RemoteException exception) {
-                    Log.w(this, "Call status update did not send to: "
-                                + componentName +" successfully with error " + exception);
+                if (info.getType() == IN_CALL_SERVICE_TYPE_BLUETOOTH
+                        && call.getState() == CallState.DISCONNECTED
+                        && !mDisconnectedToneBtFutures.containsKey(call.getId())) {
+                    CompletableFuture<Void> disconnectedToneFuture = new CompletableFuture<Void>()
+                            .completeOnTimeout(null, DISCONNECTED_TONE_TIMEOUT,
+                                    TimeUnit.MILLISECONDS);
+                    mDisconnectedToneBtFutures.put(call.getId(), disconnectedToneFuture);
+                    mDisconnectedToneBtFutures.get(call.getId()).thenRunAsync(() -> {
+                        Log.i(this, "updateCall: Sending call disconnected update to BT ICS.");
+                        updateCallToIcs(inCallService, info, parcelableCall, componentName);
+                        mDisconnectedToneBtFutures.remove(call.getId());
+                    }, new LoggedHandlerExecutor(mHandler, "ICC.uC", mLock));
+                } else {
+                    updateCallToIcs(inCallService, info, parcelableCall, componentName);
                 }
             }
             Log.i(this, "Components updated: %s", componentsUpdated);
@@ -2667,12 +2741,27 @@
         }
     }
 
+    private void updateCallToIcs(IInCallService inCallService, InCallServiceInfo info,
+            ParcelableCall parcelableCall, ComponentName componentName) {
+        try {
+            inCallService.updateCall(
+                    sanitizeParcelableCallForService(info, parcelableCall));
+        } catch (RemoteException exception) {
+            Log.w(this, "Call status update did not send to: "
+                    + componentName + " successfully with error " + exception);
+        }
+    }
+
     /**
      * Adds the call to the list of calls tracked by the {@link InCallController}.
      * @param call The call to add.
      */
     @VisibleForTesting
     public void addCall(Call call) {
+        if (call == null) {
+            return;
+        }
+
         if (mCallIdMapper.getCalls().size() == 0) {
             mAppOpsManager.startWatchingActive(new String[] { OPSTR_RECORD_AUDIO },
                     java.lang.Runnable::run, this);
@@ -2683,12 +2772,12 @@
         if (mCallIdMapper.getCallId(call) == null) {
             mCallIdMapper.addCall(call);
             call.addListener(mCallListener);
+            if (mFeatureFlags.separatelyBindToBtIncallService()) {
+                mPendingEndToneCall.add(call);
+            }
         }
 
         maybeTrackMicrophoneUse(isMuted());
-        if (mFeatureFlags.separatelyBindToBtIncallService()) {
-            mPendingEndToneCall.add(call);
-        }
     }
 
     /**
@@ -2718,6 +2807,23 @@
     }
 
     /**
+     * @return A future that is pending whenever we are in the middle of binding to the BT
+     *         incall service.
+     */
+    public CompletableFuture<Boolean> getBtBindingFuture(Call call) {
+        UserHandle userHandle = getUserFromCall(call);
+        return mBtBindingFuture.get(userHandle);
+    }
+
+    /**
+     * @return A future that is pending whenever we are in the process of sending the call
+     *         disconnected state to the BT ICS so that the disconnect tone can finish playing.
+     */
+    public Map<String, CompletableFuture<Void>> getDisconnectedToneBtFutures() {
+        return mDisconnectedToneBtFutures;
+    }
+
+    /**
      * Dumps the state of the {@link InCallController}.
      *
      * @param pw The {@code IndentingPrintWriter} to write the state to.
@@ -3174,7 +3280,10 @@
                 }
             }
         }
-        return false;
+        // If early binding for BT ICS is enabled, ensure that it is included into consideration as
+        // a bound non-UI ICS.
+        return mFeatureFlags.separatelyBindToBtIncallService() && !mBTInCallServices.isEmpty()
+                && isBluetoothPackage(packageName);
     }
 
     private void updateCombinedInCallServiceMap(UserHandle user) {
@@ -3208,4 +3317,13 @@
             }
         }
     }
+
+    private boolean isBluetoothPackage(String packageName) {
+        for (String pkgName : mDefaultDialerCache.getBTInCallServicePackages()) {
+            if (pkgName.equals(packageName)) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/src/com/android/server/telecom/RoleManagerAdapter.java b/src/com/android/server/telecom/RoleManagerAdapter.java
index 9f515e6..1b5c71b 100644
--- a/src/com/android/server/telecom/RoleManagerAdapter.java
+++ b/src/com/android/server/telecom/RoleManagerAdapter.java
@@ -71,7 +71,7 @@
      * bt in-call service role.
      * @return the package name of the package filling the role, {@code null} otherwise.
      */
-    String getBTInCallService();
+    String[] getBTInCallService();
 
     /**
      * Override the {@link android.app.role.RoleManager} bt in-call service package with another
diff --git a/src/com/android/server/telecom/RoleManagerAdapterImpl.java b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
index 33ec466..ded4d9c 100644
--- a/src/com/android/server/telecom/RoleManagerAdapterImpl.java
+++ b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
@@ -78,9 +78,9 @@
     }
 
     @Override
-    public String getBTInCallService() {
+    public String[] getBTInCallService() {
         if (mOverrideBTInCallService != null) {
-            return mOverrideBTInCallService;
+            return new String [] {mOverrideBTInCallService};
         }
         return getBluetoothInCallServicePackageName();
     }
@@ -166,8 +166,8 @@
         return roleHolders.get(0);
     }
 
-    private String getBluetoothInCallServicePackageName() {
-        return mContext.getResources().getString(R.string.system_bluetooth_stack);
+    private String[] getBluetoothInCallServicePackageName() {
+        return mContext.getResources().getStringArray(R.array.system_bluetooth_stack_package_name);
     }
 
     /**
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 230bb09..20320f2 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -1672,7 +1672,13 @@
                                         && accountExtra != null && accountExtra.getBoolean(
                                         PhoneAccount.EXTRA_SKIP_CALL_FILTERING,
                                         false)) {
-                                    mCallsManager.getInCallController().bindToServices(null, false);
+                                    if (mFeatureFlags.separatelyBindToBtIncallService()) {
+                                        mCallsManager.getInCallController().bindToBTService(
+                                                null, null);
+                                    }
+                                    // Should be able to run this as is even if above flag is
+                                    // enabled (BT binding should be skipped automatically).
+                                    mCallsManager.getInCallController().bindToServices(null);
                                 }
                             }
                         } finally {
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
index 97405a3..1d641ba 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom.tests;
 
+import static com.android.server.telecom.tests.TelecomSystemTest.TEST_TIMEOUT;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -34,6 +36,8 @@
 import static org.mockito.Mockito.when;
 
 import android.media.ToneGenerator;
+import android.os.Handler;
+import android.os.Looper;
 import android.telecom.DisconnectCause;
 import android.util.SparseArray;
 
@@ -67,6 +71,7 @@
 import java.util.Arrays;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 
 @RunWith(JUnit4.class)
@@ -423,9 +428,12 @@
         Call call = mock(Call.class);
         ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
         when(call.getState()).thenReturn(CallState.RINGING);
+        handleWaitForBtIcsBinding(call);
 
         // Make sure appropriate messages are sent when we add a RINGING call
         mCallAudioManager.onCallAdded(call);
+        mCallAudioManager.getCallRingingFuture().join();
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
 
         assertEquals(call, mCallAudioManager.getForegroundCall());
         verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
@@ -556,10 +564,14 @@
 
         Call call = createAudioProcessingCall();
 
+
         when(call.getState()).thenReturn(CallState.SIMULATED_RINGING);
+        handleWaitForBtIcsBinding(call);
 
         mCallAudioManager.onCallStateChanged(call, CallState.AUDIO_PROCESSING,
                 CallState.SIMULATED_RINGING);
+        mCallAudioManager.getCallRingingFuture().join();
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         verify(mPlayerFactory, never()).createPlayer(any(Call.class), anyInt());
         CallAudioModeStateMachine.MessageArgs expectedArgs = new Builder()
                 .setHasActiveOrDialingCalls(false)
@@ -810,9 +822,12 @@
     private Call createSimulatedRingingCall() {
         Call call = mock(Call.class);
         when(call.getState()).thenReturn(CallState.SIMULATED_RINGING);
+        handleWaitForBtIcsBinding(call);
         ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
 
         mCallAudioManager.onCallAdded(call);
+        mCallAudioManager.getCallRingingFuture().join();
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
 
         assertEquals(call, mCallAudioManager.getForegroundCall());
 
@@ -838,8 +853,11 @@
     private Call createIncomingCall() {
         Call call = mock(Call.class);
         when(call.getState()).thenReturn(CallState.RINGING);
+        handleWaitForBtIcsBinding(call);
 
         mCallAudioManager.onCallAdded(call);
+        mCallAudioManager.getCallRingingFuture().join();
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         assertEquals(call, mCallAudioManager.getForegroundCall());
         ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
                 ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
@@ -924,4 +942,10 @@
         assertEquals(expected.isTonePlaying, actual.isTonePlaying);
         assertEquals(expected.foregroundCallIsVoip, actual.foregroundCallIsVoip);
     }
+
+    private void handleWaitForBtIcsBinding(Call call) {
+        when(mFlags.separatelyBindToBtIncallService()).thenReturn(true);
+        CompletableFuture<Boolean> btBindingFuture = CompletableFuture.completedFuture(true);
+        when(call.getBtIcsFuture()).thenReturn(btBindingFuture);
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index e7f2d83..6f5b4e7 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -3610,8 +3610,6 @@
                 .setShouldAllowCall(true)
                 .setShouldReject(false)
                 .build();
-        when(mInCallController.bindToBTService(eq(call))).thenReturn(
-                CompletableFuture.completedFuture(true));
         when(mInCallController.isBoundAndConnectedToBTService(any(UserHandle.class)))
                 .thenReturn(false);
 
@@ -3619,7 +3617,7 @@
 
         InOrder inOrder = inOrder(mInCallController, call, mInCallController);
 
-        inOrder.verify(mInCallController).bindToBTService(eq(call));
+        inOrder.verify(mInCallController).bindToBTService(eq(call), eq(null));
         inOrder.verify(call).setState(eq(CallState.RINGING), anyString());
     }
 
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 6c07c79..6af31ae 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -225,7 +225,7 @@
         when(mDefaultDialerCache.getSystemDialerApplication()).thenReturn(SYS_PKG);
         when(mDefaultDialerCache.getSystemDialerComponent()).thenReturn(
                 new ComponentName(SYS_PKG, SYS_CLASS));
-        when(mDefaultDialerCache.getBTInCallServicePackage()).thenReturn(BT_PKG);
+        when(mDefaultDialerCache.getBTInCallServicePackages()).thenReturn(new String[] {BT_PKG});
         mEmergencyCallHelper = new EmergencyCallHelper(mMockContext, mDefaultDialerCache,
                 mTimeoutsAdapter);
         when(mMockCallsManager.getRoleManagerAdapter()).thenReturn(mMockRoleManagerAdapter);
@@ -409,7 +409,7 @@
                 .thenReturn(300_000L);
 
         setupMockPackageManager(false /* default */, true /* system */, false /* external calls */);
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mMockContext).bindServiceAsUser(
@@ -444,7 +444,7 @@
 
         Intent queryIntent = new Intent(InCallService.SERVICE_INTERFACE);
         setupMockPackageManager(false /* default */, true /* system */, false /* external calls */);
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mMockContext).bindServiceAsUser(
@@ -483,7 +483,7 @@
                 anyInt(), eq(mUserHandle))).thenReturn(true);
 
         setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         // Query for the different InCallServices
         ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -546,7 +546,7 @@
         setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
         setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
 
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         // Query for the different InCallServices
         ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -608,7 +608,7 @@
         setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
         setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
 
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mMockContext, times(1)).bindServiceAsUser(
@@ -639,7 +639,7 @@
         setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
         setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
 
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mMockContext, times(1)).bindServiceAsUser(
@@ -672,7 +672,7 @@
         setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
         setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
 
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mMockContext, times(1)).bindServiceAsUser(
@@ -701,7 +701,7 @@
         setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
         setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
 
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mMockContext, times(1)).bindServiceAsUser(
@@ -751,7 +751,7 @@
         setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
         setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
 
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         // Query for the different InCallServices
         ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -833,7 +833,7 @@
                 .thenReturn(true);
 
         setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         // Query for the different InCallServices
         ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -914,7 +914,7 @@
         when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
 
         setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
         ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
@@ -961,7 +961,7 @@
         mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true);
 
         // Now bind; we should only bind to one app.
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         // Bind InCallServices
         ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -1044,7 +1044,7 @@
                 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
 
         mInCallController.addCall(mMockCall);
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         // There will be 4 calls for the various types of ICS.
         verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
@@ -1213,7 +1213,7 @@
     public void testBindToService_IncludeExternal() throws Exception {
         setupMocks(true /* isExternalCall */);
         setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         // Query for the different InCallServices
         ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -1264,7 +1264,7 @@
 
         when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockCall));
         setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
         ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
@@ -1313,7 +1313,7 @@
         mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true);
 
         // Now bind; we should only bind to one app.
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         // Bind InCallServices
         ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -1332,7 +1332,7 @@
     public void testNoBindToInvalidService_CarModeUI() throws Exception {
         setupMocks(true /* isExternalCall */);
         setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         when(mMockPackageManager.checkPermission(
                 matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
@@ -1384,7 +1384,7 @@
                             anyInt(), any(AttributionSource.class), nullable(String.class)));
 
             // Now bind; we should bind to the system dialer and app op non ui app.
-            mInCallController.bindToServices(mMockCall, false);
+            mInCallController.bindToServices(mMockCall);
 
             // Bind InCallServices
             ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -1428,7 +1428,7 @@
         when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(null);
 
         // we should bind to only the non ui app.
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         // Bind InCallServices
         ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -1461,7 +1461,7 @@
                 matches(DEF_PKG))).thenReturn(PackageManager.PERMISSION_DENIED);
         when(mMockCall.getName()).thenReturn("evil");
 
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         // Bind InCallServices
         ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -1498,7 +1498,7 @@
         setupMocks(true /* isExternalCall */);
         setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
         // Bind to default dialer.
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         // Uninstall an unrelated app.
         mSystemStateListener.onPackageUninstalled("com.joe.stuff");
@@ -1522,7 +1522,7 @@
         setupMocks(true /* isExternalCall */);
         setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
         // Bind to default dialer.
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         // Enable car mode and enter car mode at default priority.
         when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true);
@@ -1590,7 +1590,7 @@
         setupMockPackageManager(true /* default */, true /* nonui */, false /* appop_nonui */ ,
                 true /* system */, false /* external calls */,
                 false /* self mgd in default*/, false /* self mgd in car*/);
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
         ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
@@ -1659,7 +1659,7 @@
 
         // Bind; we should not bind to anything right now; the dialer does not support self
         // managed calls.
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         // Bind InCallServices; make sure no binding took place.  InCallController handles not
         // binding initially, but the rebind (see next test case) will always happen.
@@ -1698,7 +1698,7 @@
 
         // Bind; we should not bind to anything right now; the dialer does not support self
         // managed calls.
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         // Bind InCallServices; make sure no binding took place.
         verify(mMockContext, never()).bindServiceAsUser(
@@ -1800,7 +1800,7 @@
         assertFalse(mUserHandle.equals(UserHandle.USER_CURRENT));
         when(mMockCurrentUserManager.isManagedProfile()).thenReturn(false);
 
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
 
         // Bind InCallService on UserHandle.CURRENT and not the user from the call (mUserHandle)
         ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -1821,7 +1821,7 @@
         when(mMockCall.getAssociatedUser()).thenReturn(testUser);
 
         // Bind to ICS. The mapping should've been inserted with the testUser as the key.
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
         assertTrue(mInCallController.getInCallServiceConnections().containsKey(testUser));
 
         // Set the target phone account. Simulates the flow when the user has chosen which sim to
@@ -1849,7 +1849,7 @@
         when(mMockCall.isIncoming()).thenReturn(true);
 
         // Bind to ICS. The mapping should've been inserted with the testUser as the key.
-        mInCallController.bindToServices(mMockCall, false);
+        mInCallController.bindToServices(mMockCall);
         assertTrue(mInCallController.getInCallServiceConnections().containsKey(testUser));
 
         // Remove the call. This invokes getUserFromCall to remove the ICS mapping.
@@ -1951,7 +1951,7 @@
                 true /*includeSelfManagedCallsInNonUi*/);
 
         //pass in call by child/profile user
-        mInCallController.bindToServices(mMockChildUserCall, false);
+        mInCallController.bindToServices(mMockChildUserCall);
         // Verify that queryIntentServicesAsUser is also called with parent handle
         // Query for the different InCallServices
         ArgumentCaptor<Integer> userIdCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -1974,7 +1974,7 @@
         setupMocks(false /* isExternalCall */);
         setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
         Intent expectedIntent = new Intent(InCallService.SERVICE_INTERFACE);
-        expectedIntent.setPackage(mDefaultDialerCache.getBTInCallServicePackage());
+        expectedIntent.setPackage(mDefaultDialerCache.getBTInCallServicePackages()[0]);
         LinkedList<ResolveInfo> resolveInfo = new LinkedList<ResolveInfo>();
         resolveInfo.add(getBluetoothResolveinfo());
         when(mFeatureFlags.separatelyBindToBtIncallService()).thenReturn(true);
@@ -1990,7 +1990,7 @@
         }).when(mMockPackageManager).queryIntentServicesAsUser(any(Intent.class), anyInt(),
                 anyInt());
 
-        mInCallController.bindToBTService(mMockCall);
+        mInCallController.bindToBTService(mMockCall, null);
 
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
         verify(mMockContext).bindServiceAsUser(captor.capture(), any(ServiceConnection.class),
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index d1dd20c..dc5f325 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -1167,7 +1167,7 @@
 
         verify(mFakePhoneAccountRegistrar).getPhoneAccount(
                 TEL_PA_HANDLE_16, TEL_PA_HANDLE_16.getUserHandle());
-        verify(mInCallController, never()).bindToServices(any(), anyBoolean());
+        verify(mInCallController, never()).bindToServices(any());
         addCallTestHelper(TelecomManager.ACTION_INCOMING_CALL,
                 CallIntentProcessor.KEY_IS_INCOMING_CALL, extras,
                 TEL_PA_HANDLE_16, false);
@@ -1189,7 +1189,7 @@
 
         mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, extras, CALLING_PACKAGE);
 
-        verify(mInCallController, never()).bindToServices(eq(null), anyBoolean());
+        verify(mInCallController, never()).bindToServices(eq(null));
     }
 
     @SmallTest
@@ -1207,7 +1207,7 @@
 
         mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, extras, CALLING_PACKAGE);
 
-        verify(mInCallController).bindToServices(eq(null), anyBoolean());
+        verify(mInCallController).bindToServices(eq(null));
     }
 
     @SmallTest
@@ -1225,7 +1225,7 @@
 
         mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, extras, CALLING_PACKAGE);
 
-        verify(mInCallController, never()).bindToServices(eq(null), anyBoolean());
+        verify(mInCallController, never()).bindToServices(eq(null));
     }
 
     @SmallTest
@@ -1244,7 +1244,7 @@
 
         mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, extras, CALLING_PACKAGE);
 
-        verify(mInCallController, never()).bindToServices(eq(null), anyBoolean());
+        verify(mInCallController, never()).bindToServices(eq(null));
     }
 
 
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index a8663d6..4463d65 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -516,6 +516,7 @@
         when(mRoleManagerAdapter.getCallCompanionApps()).thenReturn(Collections.emptyList());
         when(mRoleManagerAdapter.getDefaultCallScreeningApp(any(UserHandle.class)))
                 .thenReturn(null);
+        when(mRoleManagerAdapter.getBTInCallService()).thenReturn(new String[] {"bt_pkg"});
         when(mFeatureFlags.useRefactoredAudioRouteSwitching()).thenReturn(false);
         mTelecomSystem = new TelecomSystem(
                 mComponentContextFixture.getTestDouble(),