Merge changes from topic "cherry-picks"

* changes:
  Apply test fix that got left out
  Exempt tests from hidden API checks.
  Bluetooth: Enable in-band ringing in vibration mode (4/4)
  Fix tests
  Switch to active BT route when changing focus
  Remove volume logging
  Bring Telecom logic into agreement with Bluetooth
  Add null check for call handle
diff --git a/src/com/android/server/telecom/BluetoothHeadsetProxy.java b/src/com/android/server/telecom/BluetoothHeadsetProxy.java
index 7de49fc..0f492df 100644
--- a/src/com/android/server/telecom/BluetoothHeadsetProxy.java
+++ b/src/com/android/server/telecom/BluetoothHeadsetProxy.java
@@ -56,8 +56,8 @@
         return mBluetoothHeadset.getConnectionState(device);
     }
 
-    public boolean isAudioConnected(BluetoothDevice device) {
-        return mBluetoothHeadset.isAudioConnected(device);
+    public int getAudioState(BluetoothDevice device) {
+        return mBluetoothHeadset.getAudioState(device);
     }
 
     public boolean connectAudio() {
@@ -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/Call.java b/src/com/android/server/telecom/Call.java
index b9527e0..4e5ad2a 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -1173,6 +1173,17 @@
             return false;
         }
 
+        if (getHandle() == null) {
+            // No point in logging a null-handle call. Some self-managed calls will have this.
+            return false;
+        }
+
+        if (!PhoneAccount.SCHEME_SIP.equals(getHandle().getScheme()) &&
+                !PhoneAccount.SCHEME_TEL.equals(getHandle().getScheme())) {
+            // Can't log schemes other than SIP or TEL for now.
+            return false;
+        }
+
         return phoneAccount.getExtras() != null && phoneAccount.getExtras().getBoolean(
                 PhoneAccount.EXTRA_LOG_SELF_MANAGED_CALLS, false);
     }
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index ad446ec..7bc2519 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -93,6 +93,7 @@
 
         mPlayerFactory.setCallAudioManager(this);
         mCallAudioModeStateMachine.setCallAudioManager(this);
+        mCallAudioRouteStateMachine.setCallAudioManager(this);
     }
 
     @Override
@@ -385,6 +386,11 @@
                 CallAudioRouteStateMachine.TOGGLE_MUTE);
     }
 
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void onRingerModeChange() {
+        mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.RINGER_MODE_CHANGE);
+    }
+
     @VisibleForTesting
     public void mute(boolean shouldMute) {
         Log.v(this, "mute, shouldMute: %b", shouldMute);
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 297414b..990a453 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -93,6 +93,8 @@
 
     public static final int FOREGROUND_VOIP_MODE_CHANGE = 4001;
 
+    public static final int RINGER_MODE_CHANGE = 5001;
+
     public static final int RUN_RUNNABLE = 9001;
 
     private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
@@ -111,6 +113,7 @@
         put(TONE_STARTED_PLAYING, "TONE_STARTED_PLAYING");
         put(TONE_STOPPED_PLAYING, "TONE_STOPPED_PLAYING");
         put(FOREGROUND_VOIP_MODE_CHANGE, "FOREGROUND_VOIP_MODE_CHANGE");
+        put(RINGER_MODE_CHANGE, "RINGER_MODE_CHANGE");
 
         put(RUN_RUNNABLE, "RUN_RUNNABLE");
     }};
@@ -208,18 +211,22 @@
     }
 
     private class RingingFocusState extends BaseState {
-        @Override
-        public void enter() {
-            Log.i(LOG_TAG, "Audio focus entering RINGING state");
+        private void tryStartRinging() {
             if (mCallAudioManager.startRinging()) {
                 mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_RING,
                         AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
                 mAudioManager.setMode(AudioManager.MODE_RINGTONE);
-                mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.RINGING_FOCUS);
+                mCallAudioManager.setCallAudioRouteFocusState(
+                        CallAudioRouteStateMachine.RINGING_FOCUS);
             } else {
-                Log.i(LOG_TAG, "Entering RINGING but not acquiring focus -- silent ringtone");
+                Log.i(LOG_TAG, "RINGING state, try start ringing but not acquiring audio focus");
             }
+        }
 
+        @Override
+        public void enter() {
+            Log.i(LOG_TAG, "Audio focus entering RINGING state");
+            tryStartRinging();
             mCallAudioManager.stopCallWaiting();
         }
 
@@ -281,6 +288,11 @@
                     transitionTo(args.foregroundCallIsVoip
                             ? mVoipCallFocusState : mSimCallFocusState);
                     return HANDLED;
+                case RINGER_MODE_CHANGE: {
+                    Log.i(LOG_TAG, "RINGING state, received RINGER_MODE_CHANGE");
+                    tryStartRinging();
+                    return HANDLED;
+                }
                 default:
                     // The forced focus switch commands are handled by BaseState.
                     return NOT_HANDLED;
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 3863cf9..0984503 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -109,11 +109,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;
@@ -169,11 +169,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");
@@ -241,9 +241,7 @@
                     "Entering state " + getName());
             if (isActive()) {
                 Log.addEvent(mCallsManager.getForegroundCall(),
-                        AUDIO_ROUTE_TO_LOG_EVENT.get(getRouteCode(), LogUtils.Events.AUDIO_ROUTE),
-                        getVolumeString()
-                );
+                        AUDIO_ROUTE_TO_LOG_EVENT.get(getRouteCode(), LogUtils.Events.AUDIO_ROUTE));
             }
         }
 
@@ -260,6 +258,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,
@@ -267,11 +267,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");
@@ -280,10 +275,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,
@@ -331,20 +329,6 @@
         abstract public void updateSystemAudioState();
         abstract public boolean isActive();
         abstract public int getRouteCode();
-
-        private String getVolumeString() {
-            if (mAudioManager == null) {
-                return "";
-            }
-            StringBuilder sb = new StringBuilder();
-            sb.append("Volume: rng=");
-            sb.append(mAudioManager.getStreamVolume(AudioManager.STREAM_RING));
-            sb.append(", call=");
-            sb.append(mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL));
-            sb.append(", bt=");
-            sb.append(mAudioManager.getStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO));
-            return sb.toString();
-        }
     }
 
     class ActiveEarpieceRoute extends EarpieceRoute {
@@ -511,7 +495,7 @@
                 case CONNECT_WIRED_HEADSET:
                     sendInternalMessage(SWITCH_HEADSET);
                     return HANDLED;
-                case CONNECT_BLUETOOTH:
+                case BT_ACTIVE_DEVICE_PRESENT:
                     if (!mHasUserExplicitlyLeftBluetooth) {
                         sendInternalMessage(SWITCH_BLUETOOTH);
                     } else {
@@ -519,7 +503,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:
@@ -706,7 +690,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 {
@@ -714,7 +698,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:
@@ -763,6 +747,10 @@
                     mBluetoothRouteManager.getConnectedDevices());
             setSystemAudioState(newState, true);
             updateInternalCallAudioState();
+            // Do not send RINGER_MODE_CHANGE if no Bluetooth SCO audio device is available
+            if (mBluetoothRouteManager.getBluetoothAudioConnectedDevice() != null) {
+                mCallAudioManager.onRingerModeChange();
+            }
         }
 
         @Override
@@ -788,6 +776,9 @@
                     }
                     return HANDLED;
                 case BT_AUDIO_CONNECTED:
+                    // Send ringer mode change because we transit to ActiveBluetoothState even
+                    // when HFP is connecting
+                    mCallAudioManager.onRingerModeChange();
                     // Update the in-call app on the new active BT device in case that changed.
                     updateSystemAudioState();
                     return HANDLED;
@@ -1011,12 +1002,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;
@@ -1215,7 +1205,7 @@
                 case CONNECT_WIRED_HEADSET:
                     sendInternalMessage(SWITCH_HEADSET);
                     return HANDLED;
-                case CONNECT_BLUETOOTH:
+                case BT_ACTIVE_DEVICE_PRESENT:
                     if (!mHasUserExplicitlyLeftBluetooth) {
                         sendInternalMessage(SWITCH_BLUETOOTH);
                     } else {
@@ -1223,7 +1213,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:
@@ -1301,6 +1291,8 @@
     private CallAudioState mCurrentCallAudioState;
     private CallAudioState mLastKnownCallAudioState;
 
+    private CallAudioManager mCallAudioManager;
+
     public CallAudioRouteStateMachine(
             Context context,
             CallsManager callsManager,
@@ -1357,6 +1349,10 @@
         mRouteCodeToQuiescentState.put(ROUTE_WIRED_HEADSET, mQuiescentHeadsetRoute);
     }
 
+    public void setCallAudioManager(CallAudioManager callAudioManager) {
+        mCallAudioManager = callAudioManager;
+    }
+
     /**
      * Initializes the state machine with info on initial audio route, supported audio routes,
      * and mute status.
@@ -1474,7 +1470,10 @@
             BluetoothDevice connectedDevice =
                     mBluetoothRouteManager.getBluetoothAudioConnectedDevice();
             if (address == null && connectedDevice != null) {
-                // null means connect to any device, so don't bother reconnecting
+                // null means connect to any device, so don't bother reconnecting. Also, send a
+                // message to ourselves telling us that BT audio is already connected.
+                Log.i(this, "HFP audio already on. Skipping connecting.");
+                sendInternalMessage(BT_AUDIO_CONNECTED);
                 return;
             }
             if (connectedDevice == null || !Objects.equals(address, connectedDevice.getAddress())) {
@@ -1652,7 +1651,7 @@
         return UserHandle.USER_OWNER;
     }
 
-    private boolean isInActiveState() {
+    public boolean isInActiveState() {
         AudioState currentState = (AudioState) getCurrentState();
         if (currentState == null) {
             Log.w(this, "Current state is null, assuming inactive state");
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 6a527ff..6287bb8 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -1002,7 +1002,9 @@
                 final String handleScheme = handle.getSchemeSpecificPart();
                 Call fromCall = mCalls.stream()
                         .filter((c) -> mPhoneNumberUtilsAdapter.isSamePhoneNumber(
-                                c.getHandle().getSchemeSpecificPart(), handleScheme))
+                                (c.getHandle() == null
+                                        ? null : c.getHandle().getSchemeSpecificPart()),
+                                handleScheme))
                         .findFirst()
                         .orElse(null);
                 if (fromCall != null) {
@@ -1424,6 +1426,8 @@
                 com.android.internal.R.bool.config_requireCallCapableAccountForHandle);
         final boolean isOutgoingCallPermitted = isOutgoingCallPermitted(call,
                 call.getTargetPhoneAccount());
+        final String callHandleScheme =
+                call.getHandle() == null ? null : call.getHandle().getScheme();
         if (call.getTargetPhoneAccount() != null || call.isEmergencyCall()) {
             // If the account has been set, proceed to place the outgoing call.
             // Otherwise the connection will be initiated when the account is set by the user.
@@ -1438,7 +1442,7 @@
                 call.startCreateConnection(mPhoneAccountRegistrar);
             }
         } else if (mPhoneAccountRegistrar.getCallCapablePhoneAccounts(
-                requireCallCapableAccountByHandle ? call.getHandle().getScheme() : null, false,
+                requireCallCapableAccountByHandle ? callHandleScheme : null, false,
                 call.getInitiatingUser()).isEmpty()) {
             // If there are no call capable accounts, disconnect the call.
             markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.CANCELED,
@@ -3721,11 +3725,11 @@
     }
 
     public void acceptHandover(Uri srcAddr, int videoState, PhoneAccountHandle destAcct) {
-
         final String handleScheme = srcAddr.getSchemeSpecificPart();
         Call fromCall = mCalls.stream()
                 .filter((c) -> mPhoneNumberUtilsAdapter.isSamePhoneNumber(
-                        c.getHandle().getSchemeSpecificPart(), handleScheme))
+                        (c.getHandle() == null ? null : c.getHandle().getSchemeSpecificPart()),
+                        handleScheme))
                 .findFirst()
                 .orElse(null);
 
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index bb011bf..6dd9a3a 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -89,6 +89,9 @@
                         }
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -109,6 +112,9 @@
                         // Log.w(this, "setActive, unknown call id: %s", msg.obj);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -129,6 +135,9 @@
                         // Log.w(this, "setRinging, unknown call id: %s", msg.obj);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -148,6 +157,9 @@
                         call.setVideoProvider(videoProvider);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -168,6 +180,9 @@
                         // Log.w(this, "setDialing, unknown call id: %s", msg.obj);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -175,7 +190,7 @@
         }
 
         @Override
-	    public void setPulling(String callId, Session.Info sessionInfo) {
+        public void setPulling(String callId, Session.Info sessionInfo) {
             Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_PULLING);
             long token = Binder.clearCallingIdentity();
             try {
@@ -186,6 +201,9 @@
                         mCallsManager.markCallAsPulling(call);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -208,6 +226,9 @@
                         // Log.w(this, "setDisconnected, unknown call id: %s", args.arg1);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -228,6 +249,9 @@
                         // Log.w(this, "setOnHold, unknown call id: %s", msg.obj);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -249,6 +273,9 @@
                         // Log.w(this, "setRingback, unknown call id: %s", args.arg1);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -272,6 +299,9 @@
                         }
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -294,6 +324,9 @@
                         // "setConnectionCapabilities, unknown call id: %s", msg.obj);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -313,6 +346,9 @@
                         call.setConnectionProperties(connectionProperties);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -340,6 +376,9 @@
                         // Log.w(this, "setIsConferenced, unknown call id: %s", args.arg1);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -363,6 +402,9 @@
                         Log.w(this, "setConferenceMergeFailed, unknown call id: %s", callId);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -451,6 +493,9 @@
                         }
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -472,6 +517,9 @@
                         // Log.w(this, "onPostDialWait, unknown call id: %s", args.arg1);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -493,6 +541,9 @@
                         // Log.w(this, "onPostDialChar, unknown call id: %s", args.arg1);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -511,6 +562,9 @@
                     ConnectionServiceWrapper.this
                             .queryRemoteConnectionServices(callingUserHandle, callback);
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -529,6 +583,9 @@
                         call.setVideoState(videoState);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -547,6 +604,9 @@
                         call.setIsVoipAudioMode(isVoip);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -564,6 +624,9 @@
                             CallAudioState.audioRouteToString(audioRoute));
                     mCallsManager.setAudioRoute(audioRoute, bluetoothAddress);
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -583,6 +646,9 @@
                         call.setStatusHints(statusHints);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -601,6 +667,9 @@
                         call.putExtras(Call.SOURCE_CONNECTION_SERVICE, extras);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -619,6 +688,9 @@
                         call.removeExtras(Call.SOURCE_CONNECTION_SERVICE, keys);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -638,6 +710,9 @@
                         call.setHandle(address, presentation);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -658,6 +733,9 @@
                         call.setCallerDisplayName(callerDisplayName, presentation);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -687,6 +765,9 @@
                         call.setConferenceableCalls(conferenceableCalls);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -694,8 +775,8 @@
         }
 
         @Override
-	public void addExistingConnection(String callId, ParcelableConnection connection,
-	        Session.Info sessionInfo) {
+        public void addExistingConnection(String callId, ParcelableConnection connection,
+                Session.Info sessionInfo) {
             Log.startSession(sessionInfo, "CSW.aEC");
             UserHandle userHandle = Binder.getCallingUserHandle();
             // Check that the Calling Package matches PhoneAccountHandle's Component Package
@@ -760,6 +841,9 @@
                                 "addExistingConnection.");
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -779,6 +863,9 @@
                         call.onConnectionEvent(event, extras);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -803,6 +890,9 @@
                         call.onRttConnectionFailure(reason);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -827,6 +917,9 @@
                         call.onRemoteRttRequest();
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -850,6 +943,9 @@
                         call.setTargetPhoneAccount(pHandle);
                     }
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
@@ -866,6 +962,9 @@
                     mConnSvrFocusListener.onConnectionServiceReleased(
                             ConnectionServiceWrapper.this);
                 }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
             } finally {
                 Binder.restoreCallingIdentity(token);
                 Log.endSession();
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index f5bcd7d..7eb3801 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -374,7 +374,8 @@
                         PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)) {
                     CallAttemptRecord callAttemptRecord = new CallAttemptRecord(callManagerHandle,
                             mPhoneAccountRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
-                                    mCall.getHandle().getScheme()));
+                                    mCall.getHandle() == null
+                                            ? null : mCall.getHandle().getScheme()));
                     if (!mAttemptRecords.contains(callAttemptRecord)) {
                         Log.i(this, "Will try Connection Manager account %s for emergency",
                                 callManager);
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index 06a065d..be115b3 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 98d8903..f2ed9e0 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom.bluetooth;
 
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
 import android.content.Context;
 import android.os.Message;
 import android.telecom.Log;
@@ -72,8 +73,8 @@
 
     public interface BluetoothStateListener {
         void onBluetoothDeviceListChanged();
-        void onBluetoothDeviceAvailable();
-        void onBluetoothDeviceUnavailable();
+        void onBluetoothActiveDevicePresent();
+        void onBluetoothActiveDeviceGone();
         void onBluetoothAudioConnected();
         void onBluetoothAudioDisconnected();
     }
@@ -243,15 +244,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 +358,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 +409,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 +439,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 +548,6 @@
         sendMessage(NEW_DEVICE_CONNECTED, args);
 
         mListener.onBluetoothDeviceListChanged();
-        if (mDeviceManager.getConnectedDevices().size() == 1) {
-            mListener.onBluetoothDeviceAvailable();
-        }
     }
 
     public void onDeviceLost(String lostDeviceAddress) {
@@ -577,8 +557,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 +577,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 +586,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 +624,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() {
@@ -679,7 +654,8 @@
 
         for (int i = 0; i < deviceList.size(); i++) {
             BluetoothDevice device = deviceList.get(i);
-            boolean isAudioOn = bluetoothHeadset.isAudioConnected(device);
+            boolean isAudioOn = bluetoothHeadset.getAudioState(device)
+                    != BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
             Log.v(this, "isBluetoothAudioConnected: ==> isAudioOn = " + isAudioOn
                     + "for headset: " + device);
             if (isAudioOn) {
@@ -810,4 +786,9 @@
             Log.i(LOG_TAG, "transition for testing done: %s", stateName);
         });
     }
+
+    @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/AndroidTest.xml b/tests/AndroidTest.xml
index a55aeed..aebc8ba 100644
--- a/tests/AndroidTest.xml
+++ b/tests/AndroidTest.xml
@@ -23,5 +23,6 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.server.telecom.tests" />
         <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
     </test>
 </configuration>
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
index 5cf4c1d..a932a34 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom.tests;
 
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
 import android.content.ContentResolver;
 import android.os.Parcel;
 import android.telecom.Log;
@@ -39,6 +40,7 @@
 import java.util.Objects;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeast;
@@ -75,7 +77,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 +115,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();
@@ -228,8 +131,11 @@
         when(mDeviceManager.getNumConnectedDevices()).thenReturn(devices.length);
         when(mDeviceManager.getConnectedDevices()).thenReturn(Arrays.asList(devices));
         when(mHeadsetProxy.getConnectedDevices()).thenReturn(Arrays.asList(devices));
+        when(mHeadsetProxy.getAudioState(any(BluetoothDevice.class)))
+                .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
         if (activeDevice != null) {
-            when(mHeadsetProxy.isAudioConnected(eq(activeDevice))).thenReturn(true);
+            when(mHeadsetProxy.getAudioState(eq(activeDevice)))
+                    .thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTED);
         }
         doAnswer(invocation -> {
             BluetoothDevice first = getFirstExcluding(devices,
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
index bf35506..db111d4 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom.tests;
 
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
 import android.content.ContentResolver;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -43,6 +44,7 @@
 import static com.android.server.telecom.tests.BluetoothRouteManagerTest.DEVICE3;
 import static com.android.server.telecom.tests.BluetoothRouteManagerTest.executeRoutingAction;
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.doAnswer;
@@ -54,7 +56,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 +72,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 +137,11 @@
             return this;
         }
 
+        public BluetoothRouteTestParametersBuilder setActiveDevice(BluetoothDevice device) {
+            this.activeDevice = device;
+            return this;
+        }
+
         public BluetoothRouteTestParameters build() {
             return new BluetoothRouteTestParameters(name,
                     initialBluetoothState,
@@ -144,7 +153,9 @@
                     expectedFinalStateName,
                     connectedDevices,
                     messageDevice,
-                    audioOnDevice);
+                    audioOnDevice,
+                    activeDevice);
+
         }
     }
 
@@ -160,13 +171,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 +191,7 @@
             this.connectedDevices = connectedDevices;
             this.messageDevice = messageDevice;
             this.audioOnDevice = audioOnDevice;
+            this.activeDevice = activeDevice;
         }
 
         @Override
@@ -193,6 +207,7 @@
                     ", expectedConnectionDevice='" + expectedConnectionDevice + '\'' +
                     ", expectedFinalStateName='" + expectedFinalStateName + '\'' +
                     ", connectedDevices=" + Arrays.toString(connectedDevices) +
+                    ", activeDevice='" + activeDevice + '\'' +
                     '}';
         }
     }
@@ -225,13 +240,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 +264,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 +301,17 @@
         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);
+        when(mHeadsetProxy.getAudioState(any(BluetoothDevice.class)))
+                .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+        if (audioOnDevice != null) {
+            when(mHeadsetProxy.getAudioState(eq(audioOnDevice)))
+                    .thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTED);
         }
         doAnswer(invocation -> {
             BluetoothDevice first = getFirstExcluding(devices,
@@ -342,23 +366,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 +416,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 +461,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 +515,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/CallAudioModeStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
index 64f5fb8..68c5014 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
@@ -21,6 +21,7 @@
 
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallAudioModeStateMachine;
+import com.android.server.telecom.CallAudioRouteStateMachine;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -32,6 +33,7 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -80,6 +82,51 @@
         verify(mCallAudioManager).stopCallWaiting();
     }
 
+    @SmallTest
+    @Test
+    public void testRegainFocusWhenHfpIsConnectedSilenced() throws Throwable {
+        CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mAudioManager);
+        sm.setCallAudioManager(mCallAudioManager);
+        sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+        resetMocks();
+        when(mCallAudioManager.startRinging()).thenReturn(false);
+
+        sm.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL,
+                new CallAudioModeStateMachine.MessageArgs(
+                        false, // hasActiveOrDialingCalls
+                        true, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ));
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+        assertEquals(CallAudioModeStateMachine.RING_STATE_NAME, sm.getCurrentStateName());
+
+        verify(mAudioManager, never()).requestAudioFocusForCall(anyInt(), anyInt());
+        verify(mAudioManager, never()).setMode(anyInt());
+
+        verify(mCallAudioManager, never()).stopRinging();
+
+        verify(mCallAudioManager).stopCallWaiting();
+
+        when(mCallAudioManager.startRinging()).thenReturn(true);
+
+        sm.sendMessage(CallAudioModeStateMachine.RINGER_MODE_CHANGE);
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+        verify(mCallAudioManager, times(2)).startRinging();
+        verify(mAudioManager).requestAudioFocusForCall(AudioManager.STREAM_RING,
+                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+        verify(mAudioManager).setMode(AudioManager.MODE_RINGTONE);
+        verify(mCallAudioManager).setCallAudioRouteFocusState(
+                CallAudioRouteStateMachine.RINGING_FOCUS);
+    }
+
+
     private void resetMocks() {
         reset(mCallAudioManager, mAudioManager);
     }
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index 76048b7..9c90d3e 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -82,6 +82,7 @@
     @Mock WiredHeadsetManager mockWiredHeadsetManager;
     @Mock StatusBarNotifier mockStatusBarNotifier;
     @Mock Call fakeCall;
+    @Mock CallAudioManager mockCallAudioManager;
 
     private CallAudioManager.AudioServiceFactory mAudioServiceFactory;
     private static final int TEST_TIMEOUT = 500;
@@ -187,6 +188,7 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
         when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
@@ -210,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());
@@ -229,6 +231,7 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
         Collection<BluetoothDevice> availableDevices = Collections.singleton(bluetoothDevice1);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -277,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
@@ -301,6 +304,7 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
         when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
@@ -335,6 +339,7 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
         setInBandRing(false);
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
         when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(false);
@@ -354,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);
@@ -368,6 +374,12 @@
                 CallAudioRouteStateMachine.ACTIVE_FOCUS);
         waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
         verify(mockBluetoothRouteManager, times(1)).connectBluetoothAudio(null);
+
+        when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice())
+                .thenReturn(bluetoothDevice1);
+        stateMachine.sendMessage(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        verify(mockCallAudioManager, times(1)).onRingerModeChange();
     }
 
     @SmallTest
@@ -381,6 +393,7 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
         List<BluetoothDevice> availableDevices =
                 Arrays.asList(bluetoothDevice1, bluetoothDevice2, bluetoothDevice3);
 
@@ -420,6 +433,46 @@
 
     @SmallTest
     @Test
+    public void testFocusChangeWithAlreadyActiveBtDevice() {
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+        List<BluetoothDevice> availableDevices =
+                Arrays.asList(bluetoothDevice1, bluetoothDevice2);
+
+        // Set up a state where there's an HFP connected bluetooth device already.
+        when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
+        when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true);
+        when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
+        when(mockBluetoothRouteManager.getConnectedDevices()).thenReturn(availableDevices);
+        when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice())
+                .thenReturn(bluetoothDevice1);
+
+        // We want to be in the QuiescentBluetoothRoute because there's no call yet
+        CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH,
+                bluetoothDevice1, availableDevices);
+        stateMachine.initialize(initState);
+
+        // Switch to active, pretending that a call came in.
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+
+        // Make sure that we've successfully switched to the active BT route without actually
+        // calling connectAudio.
+        verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(nullable(String.class));
+        assertTrue(stateMachine.isInActiveState());
+    }
+
+    @SmallTest
+    @Test
     public void testInitializationWithEarpieceNoHeadsetNoBluetooth() {
         CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
index 345312e..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;
@@ -146,6 +152,7 @@
     @Mock WiredHeadsetManager mockWiredHeadsetManager;
     @Mock StatusBarNotifier mockStatusBarNotifier;
     @Mock Call fakeCall;
+    @Mock CallAudioManager mockCallAudioManager;
     private CallAudioManager.AudioServiceFactory mAudioServiceFactory;
     private static final int TEST_TIMEOUT = 500;
     private AudioManager mockAudioManager;
@@ -207,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() {
@@ -218,12 +245,14 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 mParams.earpieceControl);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
 
         setupMocksForParams(stateMachine, mParams);
 
         // 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
@@ -238,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);
@@ -288,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);
     }
 
@@ -303,26 +334,29 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 mParams.earpieceControl);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
 
         // Set up bluetooth and speakerphone state
         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
@@ -331,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
@@ -440,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
@@ -452,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
@@ -464,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
@@ -476,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
@@ -500,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
@@ -512,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
@@ -524,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
@@ -692,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;
     }
 
@@ -714,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() {