Implement multi-hfp APIs

Plumb Telecom multi-hfp APIs all the way through the stack.
Also delete BluetoothManager (deprecated) and modify tests to fully test
the multi-hfp functionality

Bug: 64767509
Test: unit tests
Change-Id: I02aafe3fca759510a013fd7cd956e3e923dd408a
diff --git a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
index e70a63a..4d86f05 100644
--- a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
+++ b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
@@ -328,7 +328,7 @@
                         .getCallAudioRouteStateMachine().getHandler(),
                 TEST_TIMEOUT);
         audioRoutes.add(mInCallServiceFixtureX.mCallAudioState.getRoute());
-        mInCallServiceFixtureX.getInCallAdapter().setAudioRoute(CallAudioState.ROUTE_SPEAKER);
+        mInCallServiceFixtureX.getInCallAdapter().setAudioRoute(CallAudioState.ROUTE_SPEAKER, null);
         waitForHandlerAction(
                 mTelecomSystem.getCallsManager().getCallAudioManager()
                         .getCallAudioRouteStateMachine().getHandler(),
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 905a15e..2a8dd51 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -555,10 +555,10 @@
         verify(mAudioService, timeout(TEST_TIMEOUT))
                 .setMicrophoneMute(eq(false), any(String.class), any(Integer.class));
 
-        mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
+        mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_SPEAKER, null);
         verify(audioManager, timeout(TEST_TIMEOUT))
                 .setSpeakerphoneOn(true);
-        mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_EARPIECE);
+        mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_EARPIECE, null);
         verify(audioManager, timeout(TEST_TIMEOUT))
                 .setSpeakerphoneOn(false);
 
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothManagerTest.java
deleted file mode 100644
index d9a9e14..0000000
--- a/tests/src/com/android/server/telecom/tests/BluetoothManagerTest.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.telecom.tests;
-
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothProfile;
-import android.content.BroadcastReceiver;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Parcel;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.server.telecom.BluetoothAdapterProxy;
-import com.android.server.telecom.BluetoothHeadsetProxy;
-import com.android.server.telecom.BluetoothManager;
-
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-
-import java.util.Collections;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class BluetoothManagerTest extends TelecomTestCase {
-    @Mock BluetoothManager.BluetoothStateListener mListener;
-    @Mock BluetoothHeadsetProxy mHeadsetProxy;
-    @Mock BluetoothAdapterProxy mAdapterProxy;
-
-    BluetoothManager mBluetoothManager;
-    BluetoothProfile.ServiceListener serviceListenerUnderTest;
-    BroadcastReceiver receiverUnderTest;
-
-    private BluetoothDevice device1;
-
-    public void setUp() throws Exception {
-        super.setUp();
-        initializeDevice();
-        mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
-        mBluetoothManager = new BluetoothManager(mContext, mAdapterProxy);
-
-        ArgumentCaptor<BluetoothProfile.ServiceListener> serviceCaptor =
-                ArgumentCaptor.forClass(BluetoothProfile.ServiceListener.class);
-        verify(mAdapterProxy).getProfileProxy(eq(mContext),
-                serviceCaptor.capture(), eq(BluetoothProfile.HEADSET));
-        serviceListenerUnderTest = serviceCaptor.getValue();
-
-        ArgumentCaptor<BroadcastReceiver> receiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(mContext).registerReceiver(receiverCaptor.capture(), any(IntentFilter.class));
-        receiverUnderTest = receiverCaptor.getValue();
-
-        mBluetoothManager.setBluetoothStateListener(mListener);
-        mBluetoothManager.setBluetoothHeadsetForTesting(mHeadsetProxy);
-    }
-
-    @SmallTest
-    public void testIsBluetoothAvailableWithNoDevices() {
-        when(mHeadsetProxy.getConnectedDevices()).thenReturn(
-                Collections.<BluetoothDevice>emptyList());
-        assertFalse(mBluetoothManager.isBluetoothAvailable());
-    }
-
-    @SmallTest
-    public void testIsBluetoothAvailable() {
-        when(mHeadsetProxy.getConnectedDevices()).thenReturn(
-                Collections.singletonList(device1));
-        assertTrue(mBluetoothManager.isBluetoothAvailable());
-    }
-
-    @SmallTest
-    public void testIsAudioConnectedWithNoDevices() {
-        when(mHeadsetProxy.getConnectedDevices()).thenReturn(
-                Collections.<BluetoothDevice>emptyList());
-        assertFalse(mBluetoothManager.isBluetoothAudioConnected());
-    }
-
-    @SmallTest
-    public void testIsAudioConnectedWhenAudioNotOn() {
-        when(mHeadsetProxy.getConnectedDevices()).thenReturn(
-                Collections.singletonList(device1));
-        when(mHeadsetProxy.isAudioConnected(eq(device1))).thenReturn(false);
-        assertFalse(mBluetoothManager.isBluetoothAudioConnected());
-    }
-
-    @SmallTest
-    public void testIsAudioConnectedWhenAudioOn() {
-        when(mHeadsetProxy.getConnectedDevices()).thenReturn(
-                Collections.singletonList(device1));
-        when(mHeadsetProxy.isAudioConnected(eq(device1))).thenReturn(true);
-        assertTrue(mBluetoothManager.isBluetoothAudioConnected());
-    }
-
-    @SmallTest
-    public void testShouldBePendingAfterConnectAudio() {
-        when(mHeadsetProxy.getConnectedDevices()).thenReturn(
-                Collections.singletonList(device1));
-        when(mHeadsetProxy.isAudioConnected(eq(device1))).thenReturn(false);
-        mBluetoothManager.connectBluetoothAudio();
-        verify(mHeadsetProxy).connectAudio();
-        assertFalse(mBluetoothManager.isBluetoothAudioConnected());
-        assertTrue(mBluetoothManager.isBluetoothAudioConnectedOrPending());
-    }
-
-    @SmallTest
-    public void testDisconnectAudioWhenHeadsetServiceConnected() {
-        mBluetoothManager.disconnectBluetoothAudio();
-        verify(mHeadsetProxy).disconnectAudio();
-    }
-
-    @SmallTest
-    public void testDisconnectAudioWithNoHeadsetService() {
-        mBluetoothManager.setBluetoothHeadsetForTesting(null);
-        mBluetoothManager.disconnectBluetoothAudio();
-        verify(mHeadsetProxy, never()).disconnectAudio();
-    }
-
-    @SmallTest
-    public void testConnectServiceWhenUninitialized1() {
-        when(mHeadsetProxy.getConnectedDevices()).thenReturn(
-                Collections.<BluetoothDevice>emptyList());
-        serviceListenerUnderTest.onServiceConnected(BluetoothProfile.HEALTH, null);
-        verify(mListener).onBluetoothStateChange(BluetoothManager.BLUETOOTH_UNINITIALIZED,
-                BluetoothManager.BLUETOOTH_DISCONNECTED);
-    }
-
-    @SmallTest
-    public void testConnectServiceWhenUninitialized2() {
-        when(mHeadsetProxy.getConnectedDevices()).thenReturn(
-                Collections.singletonList(device1));
-        when(mHeadsetProxy.isAudioConnected(eq(device1))).thenReturn(false);
-        serviceListenerUnderTest.onServiceConnected(BluetoothProfile.HEALTH, null);
-        verify(mListener).onBluetoothStateChange(BluetoothManager.BLUETOOTH_UNINITIALIZED,
-                BluetoothManager.BLUETOOTH_DEVICE_CONNECTED);
-    }
-
-    @SmallTest
-    public void testConnectServiceWhenUninitialized3() {
-        when(mHeadsetProxy.getConnectedDevices()).thenReturn(
-                Collections.singletonList(device1));
-        when(mHeadsetProxy.isAudioConnected(eq(device1))).thenReturn(true);
-        serviceListenerUnderTest.onServiceConnected(BluetoothProfile.HEALTH, null);
-        verify(mListener).onBluetoothStateChange(BluetoothManager.BLUETOOTH_UNINITIALIZED,
-                BluetoothManager.BLUETOOTH_AUDIO_CONNECTED);
-    }
-
-    @SmallTest
-    public void testReceiveAudioDisconnectWhenConnected() {
-        mBluetoothManager.setInternalBluetoothState(BluetoothManager.BLUETOOTH_AUDIO_CONNECTED);
-        when(mHeadsetProxy.getConnectedDevices()).thenReturn(
-                Collections.singletonList(device1));
-        when(mHeadsetProxy.isAudioConnected(eq(device1))).thenReturn(false);
-        receiverUnderTest.onReceive(mContext,
-                buildAudioActionIntent(BluetoothHeadset.STATE_AUDIO_DISCONNECTED));
-        verify(mListener).onBluetoothStateChange(BluetoothManager.BLUETOOTH_AUDIO_CONNECTED,
-                BluetoothManager.BLUETOOTH_DEVICE_CONNECTED);
-    }
-
-    @SmallTest
-    public void testReceiveAudioConnectWhenDisconnected() {
-        mBluetoothManager.setInternalBluetoothState(BluetoothManager.BLUETOOTH_DEVICE_CONNECTED);
-        when(mHeadsetProxy.getConnectedDevices()).thenReturn(
-                Collections.singletonList(device1));
-        when(mHeadsetProxy.isAudioConnected(eq(device1))).thenReturn(true);
-        receiverUnderTest.onReceive(mContext,
-                buildAudioActionIntent(BluetoothHeadset.STATE_AUDIO_CONNECTED));
-        verify(mListener).onBluetoothStateChange(BluetoothManager.BLUETOOTH_DEVICE_CONNECTED,
-                BluetoothManager.BLUETOOTH_AUDIO_CONNECTED);
-    }
-
-    @SmallTest
-    public void testReceiveAudioConnectWhenPending() {
-        mBluetoothManager.setInternalBluetoothState(BluetoothManager.BLUETOOTH_AUDIO_PENDING);
-        when(mHeadsetProxy.getConnectedDevices()).thenReturn(
-                Collections.singletonList(device1));
-        when(mHeadsetProxy.isAudioConnected(eq(device1))).thenReturn(true);
-        receiverUnderTest.onReceive(mContext,
-                buildAudioActionIntent(BluetoothHeadset.STATE_AUDIO_CONNECTED));
-        verify(mListener).onBluetoothStateChange(BluetoothManager.BLUETOOTH_AUDIO_PENDING,
-                BluetoothManager.BLUETOOTH_AUDIO_CONNECTED);
-    }
-
-    @SmallTest
-    public void testReceiveAudioDisconnectWhenPending() {
-        mBluetoothManager.setInternalBluetoothState(BluetoothManager.BLUETOOTH_AUDIO_PENDING);
-        when(mHeadsetProxy.getConnectedDevices()).thenReturn(
-                Collections.singletonList(device1));
-        when(mHeadsetProxy.isAudioConnected(eq(device1))).thenReturn(false);
-        receiverUnderTest.onReceive(mContext,
-                buildAudioActionIntent(BluetoothHeadset.STATE_AUDIO_DISCONNECTED));
-        verify(mListener).onBluetoothStateChange(BluetoothManager.BLUETOOTH_AUDIO_PENDING,
-                BluetoothManager.BLUETOOTH_DEVICE_CONNECTED);
-    }
-
-    @SmallTest
-    public void testReceiveAudioConnectingWhenPending() {
-        mBluetoothManager.setInternalBluetoothState(BluetoothManager.BLUETOOTH_AUDIO_PENDING);
-        when(mHeadsetProxy.getConnectedDevices()).thenReturn(
-                Collections.singletonList(device1));
-        when(mHeadsetProxy.isAudioConnected(eq(device1))).thenReturn(false);
-        receiverUnderTest.onReceive(mContext,
-                buildAudioActionIntent(BluetoothHeadset.STATE_AUDIO_CONNECTING));
-        verify(mListener, never()).onBluetoothStateChange(anyInt(), anyInt());
-    }
-
-    private Intent buildAudioActionIntent(int state) {
-        Intent i = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
-        i.putExtra(BluetoothHeadset.EXTRA_STATE, state);
-        return i;
-    }
-
-    private void initializeDevice() {
-        Parcel p1 = Parcel.obtain();
-        p1.writeString("00:01:02:03:04:05");
-        p1.setDataPosition(0);
-        device1 = BluetoothDevice.CREATOR.createFromParcel(p1);
-        p1.recycle();
-    }
-}
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
index ecdf4b1..2b2caaf 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
@@ -153,7 +153,6 @@
         public String messageDevice; // The device that should be specified in the message.
         public ListenerUpdate[] expectedListenerUpdates; // what the listener should expect.
         public int expectedBluetoothInteraction; // NONE, CONNECT, or DISCONNECT
-        // TODO: this will always be none for now. Change once BT changes their API.
         public String expectedConnectionAddress; // Expected device to connect to.
         public String expectedFinalStateName; // Expected name of the final state.
         public BluetoothDevice[] connectedDevices; // array of connected devices
@@ -230,7 +229,7 @@
         setupConnectedDevices(new BluetoothDevice[]{device1}, null);
         when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
                 nullable(ContentResolver.class))).thenReturn(0L);
-        when(mHeadsetProxy.connectAudio()).thenReturn(false);
+        when(mHeadsetProxy.connectAudio(nullable(String.class))).thenReturn(false);
         executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, null);
         // Wait 3 times: for the first connection attempt, the retry attempt,
         // the second retry, and once more to make sure there are only three attempts.
@@ -238,8 +237,7 @@
         waitForStateMachineActionCompletion(sm, BluetoothRouteManager.RUN_RUNNABLE);
         waitForStateMachineActionCompletion(sm, BluetoothRouteManager.RUN_RUNNABLE);
         waitForStateMachineActionCompletion(sm, BluetoothRouteManager.RUN_RUNNABLE);
-        // TODO: verify address
-        verify(mHeadsetProxy, times(3)).connectAudio();
+        verify(mHeadsetProxy, times(3)).connectAudio(device1.getAddress());
         assertEquals(BluetoothRouteManager.AUDIO_OFF_STATE_NAME, sm.getCurrentState().getName());
         sm.getHandler().removeMessages(BluetoothRouteManager.CONNECTION_TIMEOUT);
         sm.quitNow();
@@ -252,17 +250,16 @@
         setupConnectedDevices(new BluetoothDevice[]{device1, device2}, null);
         when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
                 nullable(ContentResolver.class))).thenReturn(0L);
-        when(mHeadsetProxy.connectAudio()).thenReturn(false);
+        when(mHeadsetProxy.connectAudio(nullable(String.class))).thenReturn(false);
         executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, device2.getAddress());
         // Wait 3 times: the first connection attempt is accounted for in executeRoutingAction,
-        // so wait twice for the retry attempt, again to make sure there are only two attempts, and
-        // once more for good luck.
+        // so wait twice for the retry attempt, again to make sure there are only three attempts,
+        // and once more for good luck.
         waitForStateMachineActionCompletion(sm, BluetoothRouteManager.RUN_RUNNABLE);
         waitForStateMachineActionCompletion(sm, BluetoothRouteManager.RUN_RUNNABLE);
         waitForStateMachineActionCompletion(sm, BluetoothRouteManager.RUN_RUNNABLE);
         waitForStateMachineActionCompletion(sm, BluetoothRouteManager.RUN_RUNNABLE);
-        // TODO: verify address of device2
-        verify(mHeadsetProxy, times(3)).connectAudio();
+        verify(mHeadsetProxy, times(3)).connectAudio(device2.getAddress());
         assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
                         + ":" + device1.getAddress(),
                 sm.getCurrentState().getName());
@@ -279,15 +276,13 @@
                 BluetoothRouteManager.AUDIO_OFF_STATE_NAME, null);
         setupConnectedDevices(new BluetoothDevice[]{device3, device2, device1}, null);
         executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, device1.getAddress());
-        // TODO: verify address
-        verify(mHeadsetProxy, times(1)).connectAudio();
+        verify(mHeadsetProxy, times(1)).connectAudio(device1.getAddress());
 
         setupConnectedDevices(new BluetoothDevice[]{device3, device2, device1}, device1);
         executeRoutingAction(sm, BluetoothRouteManager.HFP_IS_ON, device1.getAddress());
 
         executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, device2.getAddress());
-        // TODO: verify address
-        verify(mHeadsetProxy, times(2)).connectAudio();
+        verify(mHeadsetProxy, times(1)).connectAudio(device2.getAddress());
 
         setupConnectedDevices(new BluetoothDevice[]{device3, device2, device1}, device2);
         executeRoutingAction(sm, BluetoothRouteManager.HFP_IS_ON, device2.getAddress());
@@ -295,7 +290,7 @@
         setupConnectedDevices(new BluetoothDevice[]{device3, device1}, null);
         executeRoutingAction(sm, BluetoothRouteManager.LOST_DEVICE, device2.getAddress());
         // Verify that we've fallen back to device 1
-        verify(mHeadsetProxy, times(3)).connectAudio();
+        verify(mHeadsetProxy, times(2)).connectAudio(device1.getAddress());
         assertEquals(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX
                         + ":" + device1.getAddress(),
                 sm.getCurrentState().getName());
@@ -309,7 +304,7 @@
         setupConnectedDevices(new BluetoothDevice[]{device3}, null);
         executeRoutingAction(sm, BluetoothRouteManager.LOST_DEVICE, device1.getAddress());
         // Verify that we've fallen back to device 3
-        verify(mHeadsetProxy, times(4)).connectAudio();
+        verify(mHeadsetProxy, times(1)).connectAudio(device3.getAddress());
         assertEquals(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX
                         + ":" + device3.getAddress(),
                 sm.getCurrentState().getName());
@@ -332,8 +327,7 @@
                 BluetoothRouteManager.AUDIO_OFF_STATE_NAME, null);
         setupConnectedDevices(new BluetoothDevice[]{device3, device2, device1}, null);
         executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, device3.getAddress());
-        // TODO: verify address
-        verify(mHeadsetProxy, times(1)).connectAudio();
+        verify(mHeadsetProxy, times(1)).connectAudio(device3.getAddress());
 
         setupConnectedDevices(new BluetoothDevice[]{device3, device2, device1}, device3);
         executeRoutingAction(sm, BluetoothRouteManager.HFP_IS_ON, device3.getAddress());
@@ -342,7 +336,7 @@
         setupConnectedDevices(new BluetoothDevice[]{device2, device1}, null);
         executeRoutingAction(sm, BluetoothRouteManager.LOST_DEVICE, device3.getAddress());
         // Verify that we've fallen back to device 2
-        verify(mHeadsetProxy, times(2)).connectAudio();
+        verify(mHeadsetProxy, times(1)).connectAudio(device2.getAddress());
         assertEquals(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX
                         + ":" + device2.getAddress(),
                 sm.getCurrentState().getName());
@@ -356,7 +350,7 @@
         setupConnectedDevices(new BluetoothDevice[]{device1}, null);
         executeRoutingAction(sm, BluetoothRouteManager.LOST_DEVICE, device2.getAddress());
         // Verify that we've fallen back to device 1
-        verify(mHeadsetProxy, times(3)).connectAudio();
+        verify(mHeadsetProxy, times(1)).connectAudio(device1.getAddress());
         assertEquals(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX
                         + ":" + device1.getAddress(),
                 sm.getCurrentState().getName());
@@ -411,18 +405,17 @@
             }
         }
 
-        // TODO: work the address in here
         switch (params.expectedBluetoothInteraction) {
             case NONE:
-                verify(mHeadsetProxy, never()).connectAudio();
+                verify(mHeadsetProxy, never()).connectAudio(nullable(String.class));
                 verify(mHeadsetProxy, never()).disconnectAudio();
                 break;
             case CONNECT:
-                verify(mHeadsetProxy).connectAudio();
+                verify(mHeadsetProxy).connectAudio(params.expectedConnectionAddress);
                 verify(mHeadsetProxy, never()).disconnectAudio();
                 break;
             case DISCONNECT:
-                verify(mHeadsetProxy, never()).connectAudio();
+                verify(mHeadsetProxy, never()).connectAudio(nullable(String.class));
                 verify(mHeadsetProxy).disconnectAudio();
                 break;
         }
@@ -465,7 +458,7 @@
         waitForStateMachineActionCompletion(brm, CallAudioModeStateMachine.RUN_RUNNABLE);
     }
 
-    private BluetoothDevice makeBluetoothDevice(String address) {
+    public static BluetoothDevice makeBluetoothDevice(String address) {
         Parcel p1 = Parcel.obtain();
         p1.writeString(address);
         p1.setDataPosition(0);
@@ -483,7 +476,7 @@
             mTimeoutsAdapter = mock(Timeouts.Adapter.class);
         }
         when(mDeviceManager.getHeadsetService()).thenReturn(mHeadsetProxy);
-        when(mHeadsetProxy.connectAudio()).thenReturn(true);
+        when(mHeadsetProxy.connectAudio(nullable(String.class))).thenReturn(true);
         when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
                 nullable(ContentResolver.class))).thenReturn(100000L);
         when(mTimeoutsAdapter.getBluetoothPendingTimeoutMillis(
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index 9742021..84b75d1 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.telecom.tests;
 
+import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.media.AudioManager;
 import android.media.IAudioService;
@@ -43,6 +44,8 @@
 import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 import static org.mockito.ArgumentMatchers.nullable;
@@ -63,14 +66,24 @@
     private static final int ON = 1;
     private static final int OFF = 2;
 
+    private static final BluetoothDevice bluetoothDevice1 =
+            BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:01");
+    private static final BluetoothDevice bluetoothDevice2 =
+            BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:02");
+    private static final BluetoothDevice bluetoothDevice3 =
+            BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:03");
+
     static class RoutingTestParameters extends TestParameters {
         public String name;
         public int initialRoute;
+        public BluetoothDevice initialBluetoothDevice = null;
         public int availableRoutes; // may excl. speakerphone, because that's always available
+        public List<BluetoothDevice> availableBluetoothDevices = Collections.emptyList();
         public int speakerInteraction; // one of NONE, ON, or OFF
         public int bluetoothInteraction; // one of NONE, ON, or OFF
         public int action;
         public int expectedRoute;
+        public BluetoothDevice expectedBluetoothDevice = null;
         public int expectedAvailableRoutes; // also may exclude the speakerphone.
         public boolean doesDeviceSupportEarpiece; // set to false in the case of Wear devices
         public boolean shouldRunWithFocus;
@@ -99,6 +112,21 @@
             return this;
         }
 
+        public RoutingTestParameters setInitialBluetoothDevice(BluetoothDevice device) {
+            initialBluetoothDevice = device;
+            return this;
+        }
+
+        public RoutingTestParameters setAvailableBluetoothDevices(BluetoothDevice... devices) {
+            availableBluetoothDevices = Arrays.asList(devices);
+            return this;
+        }
+
+        public RoutingTestParameters setExpectedBluetoothDevice(BluetoothDevice device) {
+            expectedBluetoothDevice = device;
+            return this;
+        }
+
         @Override
         public String toString() {
             return "RoutingTestParameters{" +
@@ -262,6 +290,8 @@
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
         when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
+        when(mockBluetoothRouteManager.getConnectedDevices())
+                .thenReturn(Collections.singletonList(bluetoothDevice1));
         when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
 
         CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
@@ -272,12 +302,12 @@
                 CallAudioRouteStateMachine.RINGING_FOCUS);
         waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
 
-        verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(null);
+        verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(nullable(String.class));
 
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
                 CallAudioRouteStateMachine.ACTIVE_FOCUS);
         waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
-        verify(mockBluetoothRouteManager, times(1)).connectBluetoothAudio(null);
+        verify(mockBluetoothRouteManager, times(1)).connectBluetoothAudio(nullable(String.class));
     }
 
     @MediumTest
@@ -318,6 +348,53 @@
     }
 
     @SmallTest
+    public void testConnectSpecificBluetoothDevice() {
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                true);
+        List<BluetoothDevice> availableDevices =
+                Arrays.asList(bluetoothDevice1, bluetoothDevice2, bluetoothDevice3);
+
+        when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
+        when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
+        when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
+        when(mockBluetoothRouteManager.getConnectedDevices()).thenReturn(availableDevices);
+        doAnswer(invocation -> {
+            when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice())
+                    .thenReturn(bluetoothDevice2);
+            stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+            return null;
+        }).when(mockBluetoothRouteManager).connectBluetoothAudio(bluetoothDevice2.getAddress());
+
+        CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, null,
+                availableDevices);
+        stateMachine.initialize(initState);
+
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH,
+                0, bluetoothDevice2.getAddress());
+        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+
+        verify(mockBluetoothRouteManager).connectBluetoothAudio(bluetoothDevice2.getAddress());
+        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        CallAudioState expectedEndState = new CallAudioState(false,
+                CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH,
+                bluetoothDevice2,
+                availableDevices);
+
+        verifyNewSystemCallAudioState(initState, expectedEndState);
+    }
+
+    @SmallTest
     public void testInitializationWithEarpieceNoHeadsetNoBluetooth() {
         CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
@@ -846,15 +923,21 @@
         switch (params.bluetoothInteraction) {
             case NONE:
                 verify(mockBluetoothRouteManager, never()).disconnectBluetoothAudio();
-                verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(null);
+                verify(mockBluetoothRouteManager, never())
+                        .connectBluetoothAudio(nullable(String.class));
                 break;
             case ON:
-                verify(mockBluetoothRouteManager).connectBluetoothAudio(null);
-
+                if (params.expectedBluetoothDevice == null) {
+                    verify(mockBluetoothRouteManager).connectBluetoothAudio(null);
+                } else {
+                    verify(mockBluetoothRouteManager).connectBluetoothAudio(
+                            params.expectedBluetoothDevice.getAddress());
+                }
                 verify(mockBluetoothRouteManager, never()).disconnectBluetoothAudio();
                 break;
             case OFF:
-                verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(null);
+                verify(mockBluetoothRouteManager, never())
+                        .connectBluetoothAudio(nullable(String.class));
                 verify(mockBluetoothRouteManager).disconnectBluetoothAudio();
         }
 
@@ -881,6 +964,14 @@
         when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(
                 (params.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0
                         || (params.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0);
+        when(mockBluetoothRouteManager.getConnectedDevices())
+                .thenReturn(params.availableBluetoothDevices);
+        if (params.initialBluetoothDevice != null) {
+            when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice())
+                    .thenReturn(params.initialBluetoothDevice);
+        }
+
+
         doAnswer(invocation -> {
             sm.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
             return null;
@@ -938,7 +1029,7 @@
 
     private void verifyNoSystemAudioChanges() {
         verify(mockBluetoothRouteManager, never()).disconnectBluetoothAudio();
-        verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(null);
+        verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(nullable(String.class));
         verify(mockAudioManager, never()).setSpeakerphoneOn(any(Boolean.class));
         verify(mockCallsManager, never()).onCallAudioStateChanged(any(CallAudioState.class),
                 any(CallAudioState.class));