Snap for 4781566 from f44285a11239129ee93dec1ef97d96aa70df1e0a to pi-release

Change-Id: I36ef7ce5659826f3636a8cc91eae9dbfe67f054c
diff --git a/src/com/android/server/telecom/BluetoothHeadsetProxy.java b/src/com/android/server/telecom/BluetoothHeadsetProxy.java
index 7de49fc..f32f454 100644
--- a/src/com/android/server/telecom/BluetoothHeadsetProxy.java
+++ b/src/com/android/server/telecom/BluetoothHeadsetProxy.java
@@ -68,6 +68,10 @@
         return mBluetoothHeadset.setActiveDevice(device);
     }
 
+    public BluetoothDevice getActiveDevice() {
+        return mBluetoothHeadset.getActiveDevice();
+    }
+
     public boolean isAudioOn() {
         return mBluetoothHeadset.isAudioOn();
     }
diff --git a/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java b/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
index 32d737c..a871dfc 100644
--- a/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
+++ b/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
@@ -52,15 +52,15 @@
     }
 
     @Override
-    public void onBluetoothDeviceAvailable() {
+    public void onBluetoothActiveDevicePresent() {
         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
-                CallAudioRouteStateMachine.CONNECT_BLUETOOTH);
+                CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT);
     }
 
     @Override
-    public void onBluetoothDeviceUnavailable() {
+    public void onBluetoothActiveDeviceGone() {
         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
-                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH);
+                CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_GONE);
     }
 
     @Override
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index 8ea35be..d380bfd 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -91,11 +91,11 @@
     /** Valid values for msg.what */
     public static final int CONNECT_WIRED_HEADSET = 1;
     public static final int DISCONNECT_WIRED_HEADSET = 2;
-    public static final int CONNECT_BLUETOOTH = 3;
-    public static final int DISCONNECT_BLUETOOTH = 4;
     public static final int CONNECT_DOCK = 5;
     public static final int DISCONNECT_DOCK = 6;
     public static final int BLUETOOTH_DEVICE_LIST_CHANGED = 7;
+    public static final int BT_ACTIVE_DEVICE_PRESENT = 8;
+    public static final int BT_ACTIVE_DEVICE_GONE = 9;
 
     public static final int SWITCH_EARPIECE = 1001;
     public static final int SWITCH_BLUETOOTH = 1002;
@@ -151,11 +151,11 @@
     private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
         put(CONNECT_WIRED_HEADSET, "CONNECT_WIRED_HEADSET");
         put(DISCONNECT_WIRED_HEADSET, "DISCONNECT_WIRED_HEADSET");
-        put(CONNECT_BLUETOOTH, "CONNECT_BLUETOOTH");
-        put(DISCONNECT_BLUETOOTH, "DISCONNECT_BLUETOOTH");
         put(CONNECT_DOCK, "CONNECT_DOCK");
         put(DISCONNECT_DOCK, "DISCONNECT_DOCK");
         put(BLUETOOTH_DEVICE_LIST_CHANGED, "BLUETOOTH_DEVICE_LIST_CHANGED");
+        put(BT_ACTIVE_DEVICE_PRESENT, "BT_ACTIVE_DEVICE_PRESENT");
+        put(BT_ACTIVE_DEVICE_GONE, "BT_ACTIVE_DEVICE_GONE");
 
         put(SWITCH_EARPIECE, "SWITCH_EARPIECE");
         put(SWITCH_BLUETOOTH, "SWITCH_BLUETOOTH");
@@ -242,6 +242,8 @@
             int removedRoutes = 0;
             boolean isHandled = NOT_HANDLED;
 
+            Log.i(this, "Processing message %s",
+                    MESSAGE_CODE_TO_NAME.get(msg.what, Integer.toString(msg.what)));
             switch (msg.what) {
                 case CONNECT_WIRED_HEADSET:
                     Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE,
@@ -249,11 +251,6 @@
                     removedRoutes |= ROUTE_EARPIECE;
                     addedRoutes |= ROUTE_WIRED_HEADSET;
                     break;
-                case CONNECT_BLUETOOTH:
-                    Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE,
-                            "Bluetooth connected");
-                    addedRoutes |= ROUTE_BLUETOOTH;
-                    break;
                 case DISCONNECT_WIRED_HEADSET:
                     Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE,
                             "Wired headset disconnected");
@@ -262,10 +259,13 @@
                         addedRoutes |= ROUTE_EARPIECE;
                     }
                     break;
-                case DISCONNECT_BLUETOOTH:
+                case BT_ACTIVE_DEVICE_PRESENT:
                     Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE,
-                            "Bluetooth disconnected");
-                    removedRoutes |= ROUTE_BLUETOOTH;
+                            "Bluetooth active device present");
+                    break;
+                case BT_ACTIVE_DEVICE_GONE:
+                    Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE,
+                            "Bluetooth active device gone");
                     break;
                 case BLUETOOTH_DEVICE_LIST_CHANGED:
                     Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE,
@@ -493,7 +493,7 @@
                 case CONNECT_WIRED_HEADSET:
                     sendInternalMessage(SWITCH_HEADSET);
                     return HANDLED;
-                case CONNECT_BLUETOOTH:
+                case BT_ACTIVE_DEVICE_PRESENT:
                     if (!mHasUserExplicitlyLeftBluetooth) {
                         sendInternalMessage(SWITCH_BLUETOOTH);
                     } else {
@@ -501,7 +501,7 @@
                                 "explicitly disconnected.");
                     }
                     return HANDLED;
-                case DISCONNECT_BLUETOOTH:
+                case BT_ACTIVE_DEVICE_GONE:
                     // No change in audio route required
                     return HANDLED;
                 case DISCONNECT_WIRED_HEADSET:
@@ -688,7 +688,7 @@
                     Log.e(this, new IllegalStateException(),
                             "Wired headset should already be connected.");
                     return HANDLED;
-                case CONNECT_BLUETOOTH:
+                case BT_ACTIVE_DEVICE_PRESENT:
                     if (!mHasUserExplicitlyLeftBluetooth) {
                         sendInternalMessage(SWITCH_BLUETOOTH);
                     } else {
@@ -696,7 +696,7 @@
                                 "explicitly disconnected.");
                     }
                     return HANDLED;
-                case DISCONNECT_BLUETOOTH:
+                case BT_ACTIVE_DEVICE_GONE:
                     // No change in audio route required
                     return HANDLED;
                 case DISCONNECT_WIRED_HEADSET:
@@ -1000,12 +1000,11 @@
                 case CONNECT_WIRED_HEADSET:
                     sendInternalMessage(SWITCH_HEADSET);
                     return HANDLED;
-                case CONNECT_BLUETOOTH:
-                    // We can't tell when a change in bluetooth state corresponds to an
-                    // actual connection or disconnection, so we'll just ignore it if we're already
-                    // in the bluetooth route.
+                case BT_ACTIVE_DEVICE_PRESENT:
+                    Log.w(this, "Bluetooth active device should not"
+                            + " have been null while we were in BT route.");
                     return HANDLED;
-                case DISCONNECT_BLUETOOTH:
+                case BT_ACTIVE_DEVICE_GONE:
                     sendInternalMessage(SWITCH_BASELINE_ROUTE, NO_INCLUDE_BLUETOOTH_IN_BASELINE);
                     mWasOnSpeaker = false;
                     return HANDLED;
@@ -1204,7 +1203,7 @@
                 case CONNECT_WIRED_HEADSET:
                     sendInternalMessage(SWITCH_HEADSET);
                     return HANDLED;
-                case CONNECT_BLUETOOTH:
+                case BT_ACTIVE_DEVICE_PRESENT:
                     if (!mHasUserExplicitlyLeftBluetooth) {
                         sendInternalMessage(SWITCH_BLUETOOTH);
                     } else {
@@ -1212,7 +1211,7 @@
                                 "explicitly disconnected.");
                     }
                     return HANDLED;
-                case DISCONNECT_BLUETOOTH:
+                case BT_ACTIVE_DEVICE_GONE:
                     // No change in audio route required
                     return HANDLED;
                 case DISCONNECT_WIRED_HEADSET:
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index d50ef1a..ce35587 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -18,6 +18,7 @@
 
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothManager;
 import android.bluetooth.BluetoothProfile;
 import android.content.BroadcastReceiver;
 import android.content.Context;
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index 8c3a694..298129a 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -72,8 +72,8 @@
 
     public interface BluetoothStateListener {
         void onBluetoothDeviceListChanged();
-        void onBluetoothDeviceAvailable();
-        void onBluetoothDeviceUnavailable();
+        void onBluetoothActiveDevicePresent();
+        void onBluetoothActiveDeviceGone();
         void onBluetoothAudioConnected();
         void onBluetoothAudioDisconnected();
     }
@@ -243,15 +243,8 @@
                         break;
                     case LOST_DEVICE:
                         removeDevice((String) args.arg2);
-
                         if (Objects.equals(address, mDeviceAddress)) {
-                            String newAddress = connectHfpAudio(null);
-                            if (newAddress != null) {
-                                transitionTo(getConnectingStateForAddress(newAddress,
-                                        "AudioConnecting/LOST_DEVICE"));
-                            } else {
-                                transitionTo(mAudioOffState);
-                            }
+                            transitionToActualState();
                         }
                         break;
                     case CONNECT_HFP:
@@ -364,13 +357,7 @@
                     case LOST_DEVICE:
                         removeDevice((String) args.arg2);
                         if (Objects.equals(address, mDeviceAddress)) {
-                            String newAddress = connectHfpAudio(null);
-                            if (newAddress != null) {
-                                transitionTo(getConnectingStateForAddress(newAddress,
-                                        "AudioConnected/LOST_DEVICE"));
-                            } else {
-                                transitionTo(mAudioOffState);
-                            }
+                            transitionToActualState();
                         }
                         break;
                     case CONNECT_HFP:
@@ -421,14 +408,7 @@
                     case HFP_LOST:
                         if (Objects.equals(mDeviceAddress, address)) {
                             Log.i(LOG_TAG, "HFP connection with device %s lost.", mDeviceAddress);
-                            String nextAddress = connectHfpAudio(null, mDeviceAddress);
-                            if (nextAddress == null) {
-                                Log.i(LOG_TAG, "No suitable fallback device. Going to AUDIO_OFF.");
-                                transitionToActualState();
-                            } else {
-                                transitionTo(getConnectingStateForAddress(nextAddress,
-                                        "AudioConnected/HFP_LOST"));
-                            }
+                            transitionToActualState();
                         } else {
                             Log.w(LOG_TAG, "Got HFP lost message for device %s while" +
                                     " connected to %s.", address, mDeviceAddress);
@@ -458,6 +438,8 @@
 
     private BluetoothStateListener mListener;
     private BluetoothDeviceManager mDeviceManager;
+    // Tracks the active device in the BT stack.
+    private BluetoothDevice mActiveDeviceCache = null;
 
     public BluetoothRouteManager(Context context, TelecomSystem.SyncRoot lock,
             BluetoothDeviceManager deviceManager, Timeouts.Adapter timeoutsAdapter) {
@@ -565,9 +547,6 @@
         sendMessage(NEW_DEVICE_CONNECTED, args);
 
         mListener.onBluetoothDeviceListChanged();
-        if (mDeviceManager.getConnectedDevices().size() == 1) {
-            mListener.onBluetoothDeviceAvailable();
-        }
     }
 
     public void onDeviceLost(String lostDeviceAddress) {
@@ -577,8 +556,17 @@
         sendMessage(LOST_DEVICE, args);
 
         mListener.onBluetoothDeviceListChanged();
-        if (mDeviceManager.getConnectedDevices().size() == 0) {
-            mListener.onBluetoothDeviceUnavailable();
+    }
+
+    public void onActiveDeviceChanged(BluetoothDevice device) {
+        BluetoothDevice oldActiveDevice = mActiveDeviceCache;
+        mActiveDeviceCache = device;
+        if ((oldActiveDevice == null) ^ (device == null)) {
+            if (device == null) {
+                mListener.onBluetoothActiveDeviceGone();
+            } else {
+                mListener.onBluetoothActiveDevicePresent();
+            }
         }
     }
 
@@ -588,15 +576,7 @@
     }
 
     private String connectHfpAudio(String address) {
-        return connectHfpAudio(address, 0, null);
-    }
-
-    private String connectHfpAudio(String address, int retryCount) {
-        return connectHfpAudio(address, retryCount, null);
-    }
-
-    private String connectHfpAudio(String address, String excludeAddress) {
-        return connectHfpAudio(address, 0, excludeAddress);
+        return connectHfpAudio(address, 0);
     }
 
     /**
@@ -605,23 +585,26 @@
      * Telecom from within it.
      * @param address The address that should be tried first. May be null.
      * @param retryCount The number of times this connection attempt has been retried.
-     * @param excludeAddress Don't connect to this address.
      * @return The address of the device that's actually being connected to, or null if no
      * connection was successful.
      */
-    private String connectHfpAudio(String address, int retryCount, String excludeAddress) {
+    private String connectHfpAudio(String address, int retryCount) {
         Collection<BluetoothDevice> deviceList = getConnectedDevices();
         Optional<BluetoothDevice> matchingDevice = deviceList.stream()
                 .filter(d -> Objects.equals(d.getAddress(), address))
                 .findAny();
 
-        String actualAddress = matchingDevice.isPresent() ?
-                address : getPreferredDevice(excludeAddress);
+        String actualAddress = matchingDevice.isPresent()
+                ? address : getActiveDeviceAddress();
         if (!matchingDevice.isPresent()) {
             Log.i(this, "No device with address %s available. Using %s instead.",
                     address, actualAddress);
         }
-        if (actualAddress != null && !connectAudio(actualAddress)) {
+        if (actualAddress == null) {
+            Log.i(this, "No device specified and BT stack has no active device. Not connecting.");
+            return null;
+        }
+        if (!connectAudio(actualAddress)) {
             boolean shouldRetry = retryCount < MAX_CONNECTION_RETRIES;
             Log.w(LOG_TAG, "Could not connect to %s. Will %s", actualAddress,
                     shouldRetry ? "retry" : "not retry");
@@ -640,17 +623,8 @@
         return actualAddress;
     }
 
-    private String getPreferredDevice(String excludeAddress) {
-        String preferredDevice = null;
-        for (String address : mMostRecentlyUsedDevices) {
-            if (!Objects.equals(excludeAddress, address)) {
-                preferredDevice = address;
-            }
-        }
-        if (preferredDevice == null) {
-            return mDeviceManager.getMostRecentlyConnectedDevice(excludeAddress);
-        }
-        return preferredDevice;
+    private String getActiveDeviceAddress() {
+        return mActiveDeviceCache == null ? null : mActiveDeviceCache.getAddress();
     }
 
     private void transitionToActualState() {
@@ -808,4 +782,9 @@
                 break;
         }
     }
+
+    @VisibleForTesting
+    public void setActiveDeviceCacheForTesting(BluetoothDevice device) {
+        mActiveDeviceCache = device;
+    }
 }
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index 802fba0..f9c6437 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -38,6 +38,7 @@
         INTENT_FILTER = new IntentFilter();
         INTENT_FILTER.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
         INTENT_FILTER.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
+        INTENT_FILTER.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
     }
 
     // If not in a call, BSR won't listen to the Bluetooth stack's HFP on/off messages, since
@@ -50,66 +51,84 @@
         Log.startSession("BSR.oR");
         try {
             String action = intent.getAction();
-
-            if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
-                if (!mIsInCall) {
-                    Log.i(LOG_TAG, "Ignoring BT audio state change since we're not in a call");
-                    return;
-                }
-                int bluetoothHeadsetAudioState =
-                        intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
-                                BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
-                BluetoothDevice device =
-                        intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                if (device == null) {
-                    Log.w(LOG_TAG, "Got null device from broadcast. " +
-                            "Ignoring.");
-                    return;
-                }
-
-                Log.i(LOG_TAG, "Device %s transitioned to audio state %d",
-                        device.getAddress(), bluetoothHeadsetAudioState);
-                Session session = Log.createSubsession();
-                SomeArgs args = SomeArgs.obtain();
-                args.arg1 = session;
-                args.arg2 = device.getAddress();
-                switch (bluetoothHeadsetAudioState) {
-                    case BluetoothHeadset.STATE_AUDIO_CONNECTED:
-                        mBluetoothRouteManager.sendMessage(HFP_IS_ON, args);
-                        break;
-                    case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
-                        mBluetoothRouteManager.sendMessage(HFP_LOST, args);
-                        break;
-                }
-            }
-
-            else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
-                int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
-                        BluetoothHeadset.STATE_DISCONNECTED);
-                BluetoothDevice device =
-                        intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-
-                if (device == null) {
-                    Log.w(LOG_TAG, "Got null device from broadcast. " +
-                            "Ignoring.");
-                    return;
-                }
-
-                Log.i(LOG_TAG, "Device %s changed state to %d",
-                        device.getAddress(), bluetoothHeadsetState);
-
-                if (bluetoothHeadsetState == BluetoothHeadset.STATE_CONNECTED) {
-                    mBluetoothDeviceManager.onDeviceConnected(device);
-                } else if (bluetoothHeadsetState == BluetoothHeadset.STATE_DISCONNECTED
-                        || bluetoothHeadsetState == BluetoothHeadset.STATE_DISCONNECTING) {
-                    mBluetoothDeviceManager.onDeviceDisconnected(device);
-                }
+            switch (action) {
+                case BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED:
+                    handleAudioStateChanged(intent);
+                    break;
+                case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED:
+                    handleConnectionStateChanged(intent);
+                    break;
+                case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED:
+                    handleActiveDeviceChanged(intent);
+                    break;
             }
         } finally {
             Log.endSession();
         }
     }
 
+    private void handleAudioStateChanged(Intent intent) {
+        if (!mIsInCall) {
+            Log.i(LOG_TAG, "Ignoring BT audio state change since we're not in a call");
+            return;
+        }
+        int bluetoothHeadsetAudioState =
+                intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
+                        BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+        BluetoothDevice device =
+                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+        if (device == null) {
+            Log.w(LOG_TAG, "Got null device from broadcast. " +
+                    "Ignoring.");
+            return;
+        }
+
+        Log.i(LOG_TAG, "Device %s transitioned to audio state %d",
+                device.getAddress(), bluetoothHeadsetAudioState);
+        Session session = Log.createSubsession();
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = session;
+        args.arg2 = device.getAddress();
+        switch (bluetoothHeadsetAudioState) {
+            case BluetoothHeadset.STATE_AUDIO_CONNECTED:
+                mBluetoothRouteManager.sendMessage(HFP_IS_ON, args);
+                break;
+            case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
+                mBluetoothRouteManager.sendMessage(HFP_LOST, args);
+                break;
+        }
+    }
+
+    private void handleConnectionStateChanged(Intent intent) {
+        int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
+                BluetoothHeadset.STATE_DISCONNECTED);
+        BluetoothDevice device =
+                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+        if (device == null) {
+            Log.w(LOG_TAG, "Got null device from broadcast. " +
+                    "Ignoring.");
+            return;
+        }
+
+        Log.i(LOG_TAG, "Device %s changed state to %d",
+                device.getAddress(), bluetoothHeadsetState);
+
+        if (bluetoothHeadsetState == BluetoothHeadset.STATE_CONNECTED) {
+            mBluetoothDeviceManager.onDeviceConnected(device);
+        } else if (bluetoothHeadsetState == BluetoothHeadset.STATE_DISCONNECTED
+                || bluetoothHeadsetState == BluetoothHeadset.STATE_DISCONNECTING) {
+            mBluetoothDeviceManager.onDeviceDisconnected(device);
+        }
+    }
+
+    private void handleActiveDeviceChanged(Intent intent) {
+        BluetoothDevice device =
+                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+        Log.i(LOG_TAG, "Device %s is now the preferred HFP device", device);
+        mBluetoothRouteManager.onActiveDeviceChanged(device);
+    }
+
     public BluetoothStateReceiver(BluetoothDeviceManager deviceManager,
             BluetoothRouteManager routeManager) {
         mBluetoothDeviceManager = deviceManager;
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
index 5cf4c1d..30848f8 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
@@ -75,7 +75,7 @@
         when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
                 nullable(ContentResolver.class))).thenReturn(0L);
         when(mHeadsetProxy.connectAudio()).thenReturn(false);
-        executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, null);
+        executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, DEVICE1.getAddress());
         // 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.
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -113,105 +113,6 @@
         sm.quitNow();
     }
 
-    @SmallTest
-    @Test
-    public void testProperFallbackOrder1() {
-        // Device 1, 2, 3 are connected in that order. Device 1 is activated, then device 2.
-        // Disconnect device 2, verify fallback to device 1. Disconnect device 1, fallback to
-        // device 3.
-        BluetoothRouteManager sm = setupStateMachine(
-                BluetoothRouteManager.AUDIO_OFF_STATE_NAME, null);
-        setupConnectedDevices(new BluetoothDevice[]{DEVICE3, DEVICE2, DEVICE1}, null);
-        executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, DEVICE1.getAddress());
-        verifyConnectionAttempt(DEVICE1, 1);
-
-        setupConnectedDevices(new BluetoothDevice[]{DEVICE3, DEVICE2, DEVICE1}, DEVICE1);
-        executeRoutingAction(sm, BluetoothRouteManager.HFP_IS_ON, DEVICE1.getAddress());
-
-        executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, DEVICE2.getAddress());
-        verifyConnectionAttempt(DEVICE2, 1);
-
-        setupConnectedDevices(new BluetoothDevice[]{DEVICE3, DEVICE2, DEVICE1}, DEVICE2);
-        executeRoutingAction(sm, BluetoothRouteManager.HFP_IS_ON, DEVICE2.getAddress());
-        // Disconnect device 2
-        setupConnectedDevices(new BluetoothDevice[]{DEVICE3, DEVICE1}, null);
-        executeRoutingAction(sm, BluetoothRouteManager.LOST_DEVICE, DEVICE2.getAddress());
-        // Verify that we've fallen back to device 1
-        verifyConnectionAttempt(DEVICE1, 2);
-        assertEquals(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX
-                        + ":" + DEVICE1.getAddress(),
-                sm.getCurrentState().getName());
-        setupConnectedDevices(new BluetoothDevice[]{DEVICE3, DEVICE1}, DEVICE1);
-        executeRoutingAction(sm, BluetoothRouteManager.HFP_IS_ON, DEVICE1.getAddress());
-        assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
-                        + ":" + DEVICE1.getAddress(),
-                sm.getCurrentState().getName());
-
-        // Disconnect device 1
-        setupConnectedDevices(new BluetoothDevice[]{DEVICE3}, null);
-        executeRoutingAction(sm, BluetoothRouteManager.LOST_DEVICE, DEVICE1.getAddress());
-        // Verify that we've fallen back to device 3
-        verifyConnectionAttempt(DEVICE3, 1);
-        assertEquals(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX
-                        + ":" + DEVICE3.getAddress(),
-                sm.getCurrentState().getName());
-        setupConnectedDevices(new BluetoothDevice[]{DEVICE3}, DEVICE3);
-        executeRoutingAction(sm, BluetoothRouteManager.HFP_IS_ON, DEVICE3.getAddress());
-        assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
-                        + ":" + DEVICE3.getAddress(),
-                sm.getCurrentState().getName());
-
-        sm.getHandler().removeMessages(BluetoothRouteManager.CONNECTION_TIMEOUT);
-        sm.quitNow();
-    }
-
-    @SmallTest
-    @Test
-    public void testProperFallbackOrder2() {
-        // Device 1, 2, 3 are connected in that order. Device 3 is activated.
-        // Disconnect device 3, verify fallback to device 2. Disconnect device 2, fallback to
-        // device 1.
-        BluetoothRouteManager sm = setupStateMachine(
-                BluetoothRouteManager.AUDIO_OFF_STATE_NAME, null);
-        setupConnectedDevices(new BluetoothDevice[]{DEVICE3, DEVICE2, DEVICE1}, null);
-        executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, DEVICE3.getAddress());
-        verifyConnectionAttempt(DEVICE3, 1);
-
-        setupConnectedDevices(new BluetoothDevice[]{DEVICE3, DEVICE2, DEVICE1}, DEVICE3);
-        executeRoutingAction(sm, BluetoothRouteManager.HFP_IS_ON, DEVICE3.getAddress());
-
-        // Disconnect device 2
-        setupConnectedDevices(new BluetoothDevice[]{DEVICE2, DEVICE1}, null);
-        executeRoutingAction(sm, BluetoothRouteManager.LOST_DEVICE, DEVICE3.getAddress());
-        // Verify that we've fallen back to device 2
-        verifyConnectionAttempt(DEVICE2, 1);
-        assertEquals(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX
-                        + ":" + DEVICE2.getAddress(),
-                sm.getCurrentState().getName());
-        setupConnectedDevices(new BluetoothDevice[]{DEVICE2, DEVICE1}, DEVICE2);
-        executeRoutingAction(sm, BluetoothRouteManager.HFP_IS_ON, DEVICE2.getAddress());
-        assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
-                        + ":" + DEVICE2.getAddress(),
-                sm.getCurrentState().getName());
-
-        // Disconnect device 2
-        setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, null);
-        executeRoutingAction(sm, BluetoothRouteManager.LOST_DEVICE, DEVICE2.getAddress());
-        // Verify that we've fallen back to device 1
-        verifyConnectionAttempt(DEVICE1, 1);
-        assertEquals(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX
-                        + ":" + DEVICE1.getAddress(),
-                sm.getCurrentState().getName());
-        setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, DEVICE1);
-        executeRoutingAction(sm, BluetoothRouteManager.HFP_IS_ON, DEVICE1.getAddress());
-        assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
-                        + ":" + DEVICE1.getAddress(),
-                sm.getCurrentState().getName());
-
-        sm.getHandler().removeMessages(BluetoothRouteManager.CONNECTION_TIMEOUT);
-        sm.quitNow();
-    }
-
     private BluetoothRouteManager setupStateMachine(String initialState,
             BluetoothDevice initialDevice) {
         resetMocks();
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
index bf35506..b9c1427 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
@@ -54,7 +54,7 @@
 @RunWith(Parameterized.class)
 public class BluetoothRouteTransitionTests extends TelecomTestCase {
     private enum ListenerUpdate {
-        DEVICE_LIST_CHANGED, DEVICE_AVAILABLE, DEVICE_UNAVAILABLE,
+        DEVICE_LIST_CHANGED, ACTIVE_DEVICE_PRESENT, ACTIVE_DEVICE_GONE,
         AUDIO_CONNECTED, AUDIO_DISCONNECTED
     }
 
@@ -70,6 +70,8 @@
         private BluetoothDevice expectedConnectionDevice;
         private String expectedFinalStateName;
         private BluetoothDevice[] connectedDevices;
+        // the active device as returned by BluetoothHeadset#getActiveDevice
+        private BluetoothDevice activeDevice = null;
 
         public BluetoothRouteTestParametersBuilder setName(String name) {
             this.name = name;
@@ -133,6 +135,11 @@
             return this;
         }
 
+        public BluetoothRouteTestParametersBuilder setActiveDevice(BluetoothDevice device) {
+            this.activeDevice = device;
+            return this;
+        }
+
         public BluetoothRouteTestParameters build() {
             return new BluetoothRouteTestParameters(name,
                     initialBluetoothState,
@@ -144,7 +151,9 @@
                     expectedFinalStateName,
                     connectedDevices,
                     messageDevice,
-                    audioOnDevice);
+                    audioOnDevice,
+                    activeDevice);
+
         }
     }
 
@@ -160,13 +169,15 @@
         public BluetoothDevice expectedConnectionDevice; // Expected device to connect to.
         public String expectedFinalStateName; // Expected name of the final state.
         public BluetoothDevice[] connectedDevices; // array of connected devices
+        // the active device as returned by BluetoothHeadset#getActiveDevice
+        private BluetoothDevice activeDevice = null;
 
         public BluetoothRouteTestParameters(String name, String initialBluetoothState,
                 BluetoothDevice initialDevice, int messageType, ListenerUpdate[]
                 expectedListenerUpdates, int expectedBluetoothInteraction, BluetoothDevice
                 expectedConnectionDevice, String expectedFinalStateName,
                 BluetoothDevice[] connectedDevices, BluetoothDevice messageDevice,
-                BluetoothDevice audioOnDevice) {
+                BluetoothDevice audioOnDevice, BluetoothDevice activeDevice) {
             this.name = name;
             this.initialBluetoothState = initialBluetoothState;
             this.initialDevice = initialDevice;
@@ -178,6 +189,7 @@
             this.connectedDevices = connectedDevices;
             this.messageDevice = messageDevice;
             this.audioOnDevice = audioOnDevice;
+            this.activeDevice = activeDevice;
         }
 
         @Override
@@ -193,6 +205,7 @@
                     ", expectedConnectionDevice='" + expectedConnectionDevice + '\'' +
                     ", expectedFinalStateName='" + expectedFinalStateName + '\'' +
                     ", connectedDevices=" + Arrays.toString(connectedDevices) +
+                    ", activeDevice='" + activeDevice + '\'' +
                     '}';
         }
     }
@@ -225,13 +238,17 @@
         BluetoothRouteManager sm = setupStateMachine(
                 mParams.initialBluetoothState, mParams.initialDevice);
 
-        setupConnectedDevices(mParams.connectedDevices, mParams.audioOnDevice);
+        setupConnectedDevices(mParams.connectedDevices,
+                mParams.audioOnDevice, mParams.activeDevice);
+        sm.setActiveDeviceCacheForTesting(mParams.activeDevice);
 
         // Go through the utility methods for these two messages
         if (mParams.messageType == BluetoothRouteManager.NEW_DEVICE_CONNECTED) {
             sm.onDeviceAdded(mParams.messageDevice.getAddress());
+            sm.onActiveDeviceChanged(mParams.messageDevice);
         } else if (mParams.messageType == BluetoothRouteManager.LOST_DEVICE) {
             sm.onDeviceLost(mParams.messageDevice.getAddress());
+            sm.onActiveDeviceChanged(null);
         } else {
             executeRoutingAction(sm, mParams.messageType,
                     mParams.messageDevice == null ? null : mParams.messageDevice.getAddress());
@@ -245,11 +262,11 @@
                 case DEVICE_LIST_CHANGED:
                     verify(mListener).onBluetoothDeviceListChanged();
                     break;
-                case DEVICE_AVAILABLE:
-                    verify(mListener).onBluetoothDeviceAvailable();
+                case ACTIVE_DEVICE_PRESENT:
+                    verify(mListener).onBluetoothActiveDevicePresent();
                     break;
-                case DEVICE_UNAVAILABLE:
-                    verify(mListener).onBluetoothDeviceUnavailable();
+                case ACTIVE_DEVICE_GONE:
+                    verify(mListener).onBluetoothActiveDeviceGone();
                     break;
                 case AUDIO_CONNECTED:
                     verify(mListener).onBluetoothAudioConnected();
@@ -282,12 +299,14 @@
         sm.quitNow();
     }
 
-    private void setupConnectedDevices(BluetoothDevice[] devices, BluetoothDevice activeDevice) {
+    private void setupConnectedDevices(BluetoothDevice[] devices,
+            BluetoothDevice audioOnDevice, BluetoothDevice activeDevice) {
         when(mDeviceManager.getNumConnectedDevices()).thenReturn(devices.length);
         when(mDeviceManager.getConnectedDevices()).thenReturn(Arrays.asList(devices));
         when(mHeadsetProxy.getConnectedDevices()).thenReturn(Arrays.asList(devices));
-        if (activeDevice != null) {
-            when(mHeadsetProxy.isAudioConnected(eq(activeDevice))).thenReturn(true);
+        when(mHeadsetProxy.getActiveDevice()).thenReturn(activeDevice);
+        if (audioOnDevice != null) {
+            when(mHeadsetProxy.isAudioConnected(eq(audioOnDevice))).thenReturn(true);
         }
         doAnswer(invocation -> {
             BluetoothDevice first = getFirstExcluding(devices,
@@ -342,23 +361,25 @@
                 .setConnectedDevices(DEVICE1)
                 .setMessageType(BluetoothRouteManager.NEW_DEVICE_CONNECTED)
                 .setMessageDevice(DEVICE1)
-                .setExpectedListenerUpdates(ListenerUpdate.DEVICE_AVAILABLE)
+                .setExpectedListenerUpdates(ListenerUpdate.DEVICE_LIST_CHANGED,
+                        ListenerUpdate.ACTIVE_DEVICE_PRESENT)
                 .setExpectedBluetoothInteraction(NONE)
                 .setExpectedConnectionDevice(null)
                 .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_OFF_STATE_NAME)
                 .build());
 
         result.add(new BluetoothRouteTestParametersBuilder()
-                .setName("Nonspecific connection request while audio off.")
+                .setName("Nonspecific connection request while audio off with BT-active device")
                 .setInitialBluetoothState(BluetoothRouteManager.AUDIO_OFF_STATE_NAME)
                 .setInitialDevice(null)
                 .setConnectedDevices(DEVICE2, DEVICE1)
+                .setActiveDevice(DEVICE1)
                 .setMessageType(BluetoothRouteManager.CONNECT_HFP)
                 .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
                 .setExpectedBluetoothInteraction(CONNECT)
-                .setExpectedConnectionDevice(DEVICE2)
+                .setExpectedConnectionDevice(DEVICE1)
                 .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX
-                        + ":" + DEVICE2)
+                        + ":" + DEVICE1)
                 .build());
 
         result.add(new BluetoothRouteTestParametersBuilder()
@@ -390,21 +411,21 @@
                 .build());
 
         result.add(new BluetoothRouteTestParametersBuilder()
-                .setName("Device loses HFP audio but remains connected. Fallback.")
+                .setName("Device loses HFP audio but remains connected."
+                        + " No fallback even though other devices available.")
                 .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX)
                 .setInitialDevice(DEVICE2)
                 .setConnectedDevices(DEVICE2, DEVICE1, DEVICE3)
                 .setMessageType(BluetoothRouteManager.HFP_LOST)
                 .setMessageDevice(DEVICE2)
-                .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
-                .setExpectedBluetoothInteraction(CONNECT)
-                .setExpectedConnectionDevice(DEVICE1)
-                .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX
-                        + ":" + DEVICE1)
+                .setExpectedListenerUpdates(ListenerUpdate.AUDIO_DISCONNECTED)
+                .setExpectedBluetoothInteraction(NONE)
+                .setExpectedConnectionDevice(null)
+                .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_OFF_STATE_NAME)
                 .build());
 
         result.add(new BluetoothRouteTestParametersBuilder()
-                .setName("Switch active devices")
+                .setName("Switch the device that audio is being routed to")
                 .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX)
                 .setInitialDevice(DEVICE2)
                 .setConnectedDevices(DEVICE2, DEVICE1, DEVICE3)
@@ -435,29 +456,30 @@
                 .setName("Device gets disconnected while active. No fallback.")
                 .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX)
                 .setInitialDevice(DEVICE2)
+                .setActiveDevice(DEVICE2)
                 .setConnectedDevices()
                 .setMessageType(BluetoothRouteManager.LOST_DEVICE)
                 .setMessageDevice(DEVICE2)
                 .setExpectedListenerUpdates(ListenerUpdate.AUDIO_DISCONNECTED,
-                        ListenerUpdate.DEVICE_LIST_CHANGED, ListenerUpdate.DEVICE_UNAVAILABLE)
+                        ListenerUpdate.DEVICE_LIST_CHANGED, ListenerUpdate.ACTIVE_DEVICE_GONE)
                 .setExpectedBluetoothInteraction(NONE)
                 .setExpectedConnectionDevice(null)
                 .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_OFF_STATE_NAME)
                 .build());
 
         result.add(new BluetoothRouteTestParametersBuilder()
-                .setName("Device gets disconnected while active. Fallback.")
+                .setName("Device gets disconnected while active."
+                        + " No fallback even though other devices available.")
                 .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX)
                 .setInitialDevice(DEVICE2)
                 .setConnectedDevices(DEVICE3)
                 .setMessageType(BluetoothRouteManager.LOST_DEVICE)
                 .setMessageDevice(DEVICE2)
-                .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED,
+                .setExpectedListenerUpdates(ListenerUpdate.AUDIO_DISCONNECTED,
                         ListenerUpdate.DEVICE_LIST_CHANGED)
-                .setExpectedBluetoothInteraction(CONNECT)
-                .setExpectedConnectionDevice(DEVICE3)
-                .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX
-                        + ":" + DEVICE3)
+                .setExpectedBluetoothInteraction(NONE)
+                .setExpectedConnectionDevice(null)
+                .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_OFF_STATE_NAME)
                 .build());
 
         result.add(new BluetoothRouteTestParametersBuilder()
@@ -488,18 +510,17 @@
                 .build());
 
         result.add(new BluetoothRouteTestParametersBuilder()
-                .setName("Device gets disconnected while pending. Fallback.")
+                .setName("Device gets disconnected while pending."
+                        + " No fallback even though other devices available.")
                 .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX)
                 .setInitialDevice(DEVICE2)
                 .setConnectedDevices(DEVICE3)
                 .setMessageType(BluetoothRouteManager.LOST_DEVICE)
                 .setMessageDevice(DEVICE2)
-                .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED,
+                .setExpectedListenerUpdates(ListenerUpdate.AUDIO_DISCONNECTED,
                         ListenerUpdate.DEVICE_LIST_CHANGED)
-                .setExpectedBluetoothInteraction(CONNECT)
-                .setExpectedConnectionDevice(DEVICE3)
-                .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX
-                        + ":" + DEVICE3)
+                .setExpectedBluetoothInteraction(NONE)
+                .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_OFF_STATE_NAME)
                 .build());
 
         result.add(new BluetoothRouteTestParametersBuilder()
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index d4c3d1d..6330996 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -212,9 +212,9 @@
         verifyNewSystemCallAudioState(initState, expectedEndState);
         resetMocks();
         stateMachine.sendMessageWithSessionInfo(
-                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH);
+                CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_GONE);
         stateMachine.sendMessageWithSessionInfo(
-                CallAudioRouteStateMachine.CONNECT_BLUETOOTH);
+                CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT);
 
         waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
         assertEquals(expectedEndState, stateMachine.getCurrentCallAudioState());
@@ -280,11 +280,11 @@
         verifyNewSystemCallAudioState(expectedMidState, expectedEndState);
 
         stateMachine.sendMessageWithSessionInfo(
-                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH);
+                CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_GONE);
         when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice())
                 .thenReturn(null);
         stateMachine.sendMessageWithSessionInfo(
-                CallAudioRouteStateMachine.CONNECT_BLUETOOTH);
+                CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT);
 
         waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
         // second wait needed for the BT_AUDIO_CONNECTED message
@@ -359,7 +359,8 @@
                 .thenReturn(Collections.singletonList(bluetoothDevice1));
         stateMachine.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.BLUETOOTH_DEVICE_LIST_CHANGED);
-        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.CONNECT_BLUETOOTH);
+        stateMachine.sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT);
         waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
 
         verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(null);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
index 609a488..19630b1 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
@@ -71,6 +71,12 @@
     private static final int OFF = 2;
     private static final int OPTIONAL = 3;
 
+    // This is used to simulate the first bluetooth device getting connected --
+    // it requires two messages: BT device list changed and active device present
+    private static final int SPECIAL_CONNECT_BT_ACTION = 998;
+    // Same, but for disconnection
+    private static final int SPECIAL_DISCONNECT_BT_ACTION = 999;
+
     static class RoutingTestParameters {
         public String name;
         public int initialRoute;
@@ -208,6 +214,26 @@
         when(fakeCall.getSupportedAudioRoutes()).thenReturn(params.callSupportedRoutes);
     }
 
+    private void sendActionToStateMachine(CallAudioRouteStateMachine sm) {
+        switch (mParams.action) {
+            case SPECIAL_CONNECT_BT_ACTION:
+                sm.sendMessageWithSessionInfo(
+                        CallAudioRouteStateMachine.BLUETOOTH_DEVICE_LIST_CHANGED);
+                sm.sendMessageWithSessionInfo(
+                        CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT);
+                break;
+            case SPECIAL_DISCONNECT_BT_ACTION:
+                sm.sendMessageWithSessionInfo(
+                        CallAudioRouteStateMachine.BLUETOOTH_DEVICE_LIST_CHANGED);
+                sm.sendMessageWithSessionInfo(
+                        CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_GONE);
+                break;
+            default:
+                sm.sendMessageWithSessionInfo(mParams.action);
+                break;
+        }
+    }
+
     @Test
     @SmallTest
     public void testActiveTransition() {
@@ -225,7 +251,8 @@
 
         // Set the initial CallAudioState object
         final CallAudioState initState = new CallAudioState(false,
-                mParams.initialRoute, (mParams.availableRoutes | CallAudioState.ROUTE_SPEAKER));
+                mParams.initialRoute, (mParams.availableRoutes | CallAudioState.ROUTE_SPEAKER),
+                mParams.initialBluetoothDevice, mParams.availableBluetoothDevices);
         stateMachine.initialize(initState);
 
         // Make the state machine have focus so that we actually do something
@@ -240,7 +267,8 @@
         // Reset mocks to discard stuff from initialization
         resetMocks();
         setupMocksForParams(stateMachine, mParams);
-        stateMachine.sendMessageWithSessionInfo(mParams.action);
+
+        sendActionToStateMachine(stateMachine);
 
         waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
         waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
@@ -290,7 +318,8 @@
 
         // Verify the end state
         CallAudioState expectedState = new CallAudioState(false, mParams.expectedRoute,
-                mParams.expectedAvailableRoutes | CallAudioState.ROUTE_SPEAKER);
+                mParams.expectedAvailableRoutes | CallAudioState.ROUTE_SPEAKER,
+                mParams.expectedBluetoothDevice, mParams.availableBluetoothDevices);
         verifyNewSystemCallAudioState(initState, expectedState);
     }
 
@@ -311,21 +340,23 @@
         when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(
                 (mParams.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0
                 || (mParams.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0);
+        when(mockBluetoothRouteManager.getConnectedDevices())
+                .thenReturn(mParams.availableBluetoothDevices);
         when(mockAudioManager.isSpeakerphoneOn()).thenReturn(
                 mParams.initialRoute == CallAudioState.ROUTE_SPEAKER);
         when(fakeCall.getSupportedAudioRoutes()).thenReturn(mParams.callSupportedRoutes);
 
         // Set the initial CallAudioState object
         CallAudioState initState = new CallAudioState(false,
-                mParams.initialRoute, (mParams.availableRoutes | CallAudioState.ROUTE_SPEAKER));
+                mParams.initialRoute, (mParams.availableRoutes | CallAudioState.ROUTE_SPEAKER),
+                mParams.initialBluetoothDevice, mParams.availableBluetoothDevices);
         stateMachine.initialize(initState);
         // Omit the focus-getting statement
-        stateMachine.sendMessageWithSessionInfo(mParams.action);
+        sendActionToStateMachine(stateMachine);
 
         waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
 
-        Handler h = stateMachine.getHandler();
-        waitForHandlerAction(h, TEST_TIMEOUT);
         stateMachine.quitStateMachine();
 
         // Verify that no substantive interactions have taken place with the
@@ -334,13 +365,15 @@
 
         // Verify the end state
         CallAudioState expectedState = new CallAudioState(false, mParams.expectedRoute,
-                mParams.expectedAvailableRoutes | CallAudioState.ROUTE_SPEAKER);
+                mParams.expectedAvailableRoutes | CallAudioState.ROUTE_SPEAKER,
+                mParams.expectedBluetoothDevice, mParams.availableBluetoothDevices);
         assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
     }
 
     @Parameterized.Parameters(name = "{0}")
     public static Collection<RoutingTestParameters> testParametersCollection() {
         List<RoutingTestParameters> params = new ArrayList<>();
+
         params.add(new RoutingTestParameters(
                 "Connect headset during earpiece", // name
                 CallAudioState.ROUTE_EARPIECE, // initialRoute
@@ -443,11 +476,11 @@
                 CallAudioState.ROUTE_EARPIECE, // availableRoutes
                 OPTIONAL, // speakerInteraction
                 ON, // bluetoothInteraction
-                CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
+                SPECIAL_CONNECT_BT_ACTION, // action
                 CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
                 CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_EARPIECE, // expectedAvailable
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
-        ));
+        ).setAvailableBluetoothDevices(BluetoothRouteManagerTest.DEVICE1));
 
         params.add(new RoutingTestParameters(
                 "Connect bluetooth during wired headset", // name
@@ -455,11 +488,11 @@
                 CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
                 OPTIONAL, // speakerInteraction
                 ON, // bluetoothInteraction
-                CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
+                SPECIAL_CONNECT_BT_ACTION, // action
                 CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
                 CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvai
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
-        ));
+        ).setAvailableBluetoothDevices(BluetoothRouteManagerTest.DEVICE1));
 
         params.add(new RoutingTestParameters(
                 "Connect bluetooth during speakerphone", // name
@@ -467,11 +500,11 @@
                 CallAudioState.ROUTE_EARPIECE, // availableRoutes
                 OFF, // speakerInteraction
                 ON, // bluetoothInteraction
-                CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
+                SPECIAL_CONNECT_BT_ACTION, // action
                 CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
                 CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_EARPIECE, // expectedAvailable
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
-        ));
+        ).setAvailableBluetoothDevices(BluetoothRouteManagerTest.DEVICE1));
 
         params.add(new RoutingTestParameters(
                 "Disconnect bluetooth during bluetooth without headset in", // name
@@ -479,19 +512,7 @@
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
                 OPTIONAL, // speakerInteraction
                 OFF, // bluetoothInteraction
-                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
-                CallAudioState.ROUTE_EARPIECE, // expectedRoute
-                CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Disconnect bluetooth during bluetooth without headset in, priority mode ", // name
-                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
-                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
-                OPTIONAL, // speakerInteraction
-                OFF, // bluetoothInteraction
-                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
+                SPECIAL_DISCONNECT_BT_ACTION, // action
                 CallAudioState.ROUTE_EARPIECE, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
@@ -503,7 +524,7 @@
                 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
                 OPTIONAL, // speakerInteraction
                 OFF, // bluetoothInteraction
-                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
+                SPECIAL_DISCONNECT_BT_ACTION, // action
                 CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
                 CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
@@ -515,7 +536,7 @@
                 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
                 OPTIONAL, // speakerInteraction
                 NONE, // bluetoothInteraction
-                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
+                SPECIAL_DISCONNECT_BT_ACTION, // action
                 CallAudioState.ROUTE_SPEAKER, // expectedRoute
                 CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
@@ -527,7 +548,7 @@
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
                 OPTIONAL, // speakerInteraction
                 NONE, // bluetoothInteraction
-                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
+                SPECIAL_DISCONNECT_BT_ACTION, // action
                 CallAudioState.ROUTE_EARPIECE, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
@@ -695,12 +716,36 @@
                 CallAudioState.ROUTE_BLUETOOTH,  // availableRoutes
                 ON, // speakerInteraction
                 OFF, // bluetoothInteraction
-                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
+                SPECIAL_DISCONNECT_BT_ACTION, // action
                 CallAudioState.ROUTE_SPEAKER, // expectedRoute
                 CallAudioState.ROUTE_SPEAKER, // expectedAvailableRoutes
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
         ).setCallSupportedRoutes(CallAudioState.ROUTE_ALL & ~CallAudioState.ROUTE_EARPIECE));
 
+        params.add(new RoutingTestParameters(
+                "Active device deselected during BT", // name
+                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+                OPTIONAL, // speakerInteraction
+                OFF, // bluetoothInteraction
+                CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_GONE, // action
+                CallAudioState.ROUTE_EARPIECE, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailabl
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Active device selected during earpiece", // name
+                CallAudioState.ROUTE_EARPIECE, // initialRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+                OPTIONAL, // speakerInteraction
+                ON, // bluetoothInteraction
+                CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT, // action
+                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailabl
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
         return params;
     }
 
@@ -717,9 +762,9 @@
         verify(mockConnectionServiceWrapper, timeout(TEST_TIMEOUT).atLeastOnce())
                 .onCallAudioStateChanged(same(fakeCall), newStateCaptor2.capture());
 
-        assertTrue(oldStateCaptor.getAllValues().get(0).equals(expectedOldState));
-        assertTrue(newStateCaptor1.getValue().equals(expectedNewState));
-        assertTrue(newStateCaptor2.getValue().equals(expectedNewState));
+        assertEquals(expectedOldState, oldStateCaptor.getAllValues().get(0));
+        assertEquals(expectedNewState, newStateCaptor1.getValue());
+        assertEquals(expectedNewState, newStateCaptor2.getValue());
     }
 
     private void verifyNoSystemAudioChanges() {