Merge "Fix issue that music popped up from speaker during ringing call"
diff --git a/src/com/android/server/telecom/Analytics.java b/src/com/android/server/telecom/Analytics.java
index 0a15b26..4daa525 100644
--- a/src/com/android/server/telecom/Analytics.java
+++ b/src/com/android/server/telecom/Analytics.java
@@ -35,6 +35,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.PriorityQueue;
 import java.util.stream.Collectors;
 
 import static android.telecom.ParcelableCallAnalytics.AnalyticsEvent;
@@ -523,8 +524,11 @@
 
     public static final long MILLIS_IN_1_SECOND = ParcelableCallAnalytics.MILLIS_IN_1_SECOND;
 
+    public static final int MAX_NUM_CALLS_TO_STORE = 100;
+
     private static final Object sLock = new Object(); // Coarse lock for all of analytics
     private static final Map<String, CallInfoImpl> sCallIdToInfo = new HashMap<>();
+    private static final LinkedList<String> sActiveCallIds = new LinkedList<>();
     private static final List<SessionTiming> sSessionTimings = new LinkedList<>();
 
     public static void addSessionTiming(String sessionName, long time) {
@@ -540,7 +544,12 @@
         Log.d(TAG, "Starting analytics for call " + callId);
         CallInfoImpl callInfo = new CallInfoImpl(callId, direction);
         synchronized (sLock) {
+            while (sActiveCallIds.size() >= MAX_NUM_CALLS_TO_STORE) {
+                String callToRemove = sActiveCallIds.remove();
+                sCallIdToInfo.remove(callToRemove);
+            }
             sCallIdToInfo.put(callId, callInfo);
+            sActiveCallIds.add(callId);
         }
         return callInfo;
     }
diff --git a/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java b/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
index 9b62443..96646b2 100644
--- a/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
+++ b/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
@@ -16,66 +16,68 @@
 
 package com.android.server.telecom;
 
+import com.android.server.telecom.bluetooth.BluetoothRouteManager;
+
 /**
  * A class that acts as a listener to things that could change call audio routing, namely
  * bluetooth status, wired headset status, and dock status.
  */
 public class CallAudioRoutePeripheralAdapter implements WiredHeadsetManager.Listener,
-        DockManager.Listener, BluetoothManager.BluetoothStateListener {
+        DockManager.Listener, BluetoothRouteManager.BluetoothStateListener {
 
     private final CallAudioRouteStateMachine mCallAudioRouteStateMachine;
-    private final BluetoothManager mBluetoothManager;
+    private final BluetoothRouteManager mBluetoothRouteManager;
 
     public CallAudioRoutePeripheralAdapter(
             CallAudioRouteStateMachine callAudioRouteStateMachine,
-            BluetoothManager bluetoothManager,
+            BluetoothRouteManager bluetoothManager,
             WiredHeadsetManager wiredHeadsetManager,
             DockManager dockManager) {
         mCallAudioRouteStateMachine = callAudioRouteStateMachine;
-        mBluetoothManager = bluetoothManager;
+        mBluetoothRouteManager = bluetoothManager;
 
-        mBluetoothManager.setBluetoothStateListener(this);
+        mBluetoothRouteManager.setListener(this);
         wiredHeadsetManager.addListener(this);
         dockManager.addListener(this);
     }
 
     public boolean isBluetoothAudioOn() {
-        return mBluetoothManager.isBluetoothAudioConnected();
+        return mBluetoothRouteManager.isBluetoothAudioConnectedOrPending();
     }
 
     @Override
     public void onBluetoothStateChange(int oldState, int newState) {
         switch (oldState) {
-            case BluetoothManager.BLUETOOTH_DISCONNECTED:
-            case BluetoothManager.BLUETOOTH_UNINITIALIZED:
+            case BluetoothRouteManager.BLUETOOTH_DISCONNECTED:
+            case BluetoothRouteManager.BLUETOOTH_UNINITIALIZED:
                 switch (newState) {
-                    case BluetoothManager.BLUETOOTH_DEVICE_CONNECTED:
-                    case BluetoothManager.BLUETOOTH_AUDIO_CONNECTED:
+                    case BluetoothRouteManager.BLUETOOTH_DEVICE_CONNECTED:
+                    case BluetoothRouteManager.BLUETOOTH_AUDIO_CONNECTED:
                         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
                                 CallAudioRouteStateMachine.CONNECT_BLUETOOTH);
                         break;
                 }
                 break;
-            case BluetoothManager.BLUETOOTH_DEVICE_CONNECTED:
+            case BluetoothRouteManager.BLUETOOTH_DEVICE_CONNECTED:
                 switch (newState) {
-                    case BluetoothManager.BLUETOOTH_DISCONNECTED:
+                    case BluetoothRouteManager.BLUETOOTH_DISCONNECTED:
                         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
                                 CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH);
                         break;
-                    case BluetoothManager.BLUETOOTH_AUDIO_CONNECTED:
+                    case BluetoothRouteManager.BLUETOOTH_AUDIO_CONNECTED:
                         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
                                 CallAudioRouteStateMachine.SWITCH_BLUETOOTH);
                         break;
                 }
                 break;
-            case BluetoothManager.BLUETOOTH_AUDIO_CONNECTED:
-            case BluetoothManager.BLUETOOTH_AUDIO_PENDING:
+            case BluetoothRouteManager.BLUETOOTH_AUDIO_CONNECTED:
+            case BluetoothRouteManager.BLUETOOTH_AUDIO_PENDING:
                 switch (newState) {
-                    case BluetoothManager.BLUETOOTH_DISCONNECTED:
+                    case BluetoothRouteManager.BLUETOOTH_DISCONNECTED:
                         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
                                 CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH);
                         break;
-                    case BluetoothManager.BLUETOOTH_DEVICE_CONNECTED:
+                    case BluetoothRouteManager.BLUETOOTH_DEVICE_CONNECTED:
                         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
                                 CallAudioRouteStateMachine.BT_AUDIO_DISCONNECT);
                         break;
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index cb1526a..377ab48 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -39,6 +39,7 @@
 import com.android.internal.util.IState;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 
 import java.util.HashMap;
 
@@ -829,6 +830,7 @@
             super.enter();
             mHasUserExplicitlyLeftBluetooth = false;
             updateInternalCallAudioState();
+            setBluetoothOn(false);
         }
 
         @Override
@@ -1135,7 +1137,7 @@
     private final Context mContext;
     private final CallsManager mCallsManager;
     private final AudioManager mAudioManager;
-    private final BluetoothManager mBluetoothManager;
+    private final BluetoothRouteManager mBluetoothRouteManager;
     private final WiredHeadsetManager mWiredHeadsetManager;
     private final StatusBarNotifier mStatusBarNotifier;
     private final CallAudioManager.AudioServiceFactory mAudioServiceFactory;
@@ -1155,7 +1157,7 @@
     public CallAudioRouteStateMachine(
             Context context,
             CallsManager callsManager,
-            BluetoothManager bluetoothManager,
+            BluetoothRouteManager bluetoothManager,
             WiredHeadsetManager wiredHeadsetManager,
             StatusBarNotifier statusBarNotifier,
             CallAudioManager.AudioServiceFactory audioServiceFactory,
@@ -1175,7 +1177,7 @@
         mContext = context;
         mCallsManager = callsManager;
         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        mBluetoothManager = bluetoothManager;
+        mBluetoothRouteManager = bluetoothManager;
         mWiredHeadsetManager = wiredHeadsetManager;
         mStatusBarNotifier = statusBarNotifier;
         mAudioServiceFactory = audioServiceFactory;
@@ -1290,7 +1292,7 @@
                 return;
             default:
                 Log.e(this, new IllegalStateException(),
-                        "Unexpected message code");
+                        "Unexpected message code %d", msg.what);
         }
     }
 
@@ -1350,14 +1352,13 @@
     }
 
     private void setBluetoothOn(boolean on) {
-        if (mBluetoothManager.isBluetoothAvailable()) {
-            boolean isAlreadyOn = mBluetoothManager.isBluetoothAudioConnectedOrPending();
-            if (on != isAlreadyOn) {
+        if (mBluetoothRouteManager.isBluetoothAvailable()) {
+            if (on != mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()) {
                 Log.i(this, "connecting bluetooth %s", on);
                 if (on) {
-                    mBluetoothManager.connectBluetoothAudio();
+                    mBluetoothRouteManager.connectBluetoothAudio(null /*TODO: add real address*/);
                 } else {
-                    mBluetoothManager.disconnectBluetoothAudio();
+                    mBluetoothRouteManager.disconnectBluetoothAudio();
                 }
             }
         }
@@ -1365,8 +1366,8 @@
 
     private void setMuteOn(boolean mute) {
         mIsMuted = mute;
-        Log.addEvent(mCallsManager.getForegroundCall(), mute ? LogUtils.Events.MUTE : LogUtils.Events.UNMUTE);
-
+        Log.addEvent(mCallsManager.getForegroundCall(), mute ?
+                LogUtils.Events.MUTE : LogUtils.Events.UNMUTE);
         if (mute != mAudioManager.isMicrophoneMute() && isInActiveState()) {
             IAudioService audio = mAudioServiceFactory.getAudioService();
             Log.i(this, "changing microphone mute state to: %b [serviceIsNull=%b]",
@@ -1450,7 +1451,7 @@
             routeMask |= CallAudioState.ROUTE_EARPIECE;
         }
 
-        if (mBluetoothManager.isBluetoothAvailable()) {
+        if (mBluetoothRouteManager.isBluetoothAvailable()) {
             routeMask |=  CallAudioState.ROUTE_BLUETOOTH;
         }
 
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 432eddd..2aacfab 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -57,6 +57,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 import com.android.server.telecom.callfiltering.AsyncBlockCheckFilter;
 import com.android.server.telecom.callfiltering.BlockCheckerAdapter;
 import com.android.server.telecom.callfiltering.CallFilterResultCallback;
@@ -179,7 +180,7 @@
             new ConcurrentHashMap<CallsManagerListener, Boolean>(16, 0.9f, 1));
     private final HeadsetMediaButton mHeadsetMediaButton;
     private final WiredHeadsetManager mWiredHeadsetManager;
-    private final BluetoothManager mBluetoothManager;
+    private final BluetoothRouteManager mBluetoothRouteManager;
     private final DockManager mDockManager;
     private final TtyManager mTtyManager;
     private final ProximitySensorManager mProximitySensorManager;
@@ -221,7 +222,7 @@
             ProximitySensorManagerFactory proximitySensorManagerFactory,
             InCallWakeLockControllerFactory inCallWakeLockControllerFactory,
             CallAudioManager.AudioServiceFactory audioServiceFactory,
-            BluetoothManager bluetoothManager,
+            BluetoothRouteManager bluetoothManager,
             WiredHeadsetManager wiredHeadsetManager,
             SystemStateProvider systemStateProvider,
             DefaultDialerCache defaultDialerCache,
@@ -238,8 +239,8 @@
         mMissedCallNotifier = missedCallNotifier;
         StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this);
         mWiredHeadsetManager = wiredHeadsetManager;
-        mBluetoothManager = bluetoothManager;
         mDefaultDialerCache = defaultDialerCache;
+        mBluetoothRouteManager = bluetoothManager;
         mDockManager = new DockManager(context);
         mTimeoutsAdapter = timeoutsAdapter;
         mCallerInfoLookupHelper = new CallerInfoLookupHelper(context, mCallerInfoAsyncQueryFactory,
@@ -1069,7 +1070,7 @@
     public boolean isSpeakerphoneAutoEnabledForVideoCalls(int videoState) {
         return VideoProfile.isVideo(videoState) &&
             !mWiredHeadsetManager.isPluggedIn() &&
-            !mBluetoothManager.isBluetoothAvailable() &&
+            !mBluetoothRouteManager.isBluetoothAvailable() &&
             isSpeakerEnabledForVideoCalls();
     }
 
@@ -1083,7 +1084,7 @@
     private boolean isSpeakerphoneEnabledForDock() {
         return mDockManager.isDocked() &&
             !mWiredHeadsetManager.isPluggedIn() &&
-            !mBluetoothManager.isBluetoothAvailable();
+            !mBluetoothRouteManager.isBluetoothAvailable();
     }
 
     /**
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 8fec2dd..5c95fb0 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -17,6 +17,8 @@
 package com.android.server.telecom;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
+import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 import com.android.server.telecom.components.UserCallIntentProcessor;
 import com.android.server.telecom.components.UserCallIntentProcessorFactory;
 import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
@@ -200,8 +202,10 @@
                         return context.getContentResolver().openInputStream(uri);
                     }
                 });
-        BluetoothManager bluetoothManager = new BluetoothManager(mContext,
-                new BluetoothAdapterProxy());
+        BluetoothDeviceManager bluetoothDeviceManager = new BluetoothDeviceManager(mContext,
+                new BluetoothAdapterProxy(), mLock);
+        BluetoothRouteManager bluetoothRouteManager = new BluetoothRouteManager(mContext, mLock,
+                bluetoothDeviceManager, new Timeouts.Adapter());
         WiredHeadsetManager wiredHeadsetManager = new WiredHeadsetManager(mContext);
         SystemStateProvider systemStateProvider = new SystemStateProvider(mContext);
 
@@ -219,7 +223,7 @@
                 proximitySensorManagerFactory,
                 inCallWakeLockControllerFactory,
                 audioServiceFactory,
-                bluetoothManager,
+                bluetoothRouteManager,
                 wiredHeadsetManager,
                 systemStateProvider,
                 defaultDialerCache,
diff --git a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
index b22ae82..601e1b6 100644
--- a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
+++ b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
@@ -37,6 +37,7 @@
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -338,6 +339,26 @@
                 analyticsProto.callLogs[0].getConnectionProperties() & expectedProperties);
     }
 
+    @SmallTest
+    public void testAnalyticsMaxSize() throws Exception {
+        Analytics.reset();
+        for (int i = 0; i < Analytics.MAX_NUM_CALLS_TO_STORE * 2; i++) {
+            Analytics.initiateCallAnalytics(String.valueOf(i), Analytics.INCOMING_DIRECTION)
+                    .addCallTechnology(i);
+        }
+
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        Analytics.dumpToEncodedProto(pw, new String[]{});
+        TelecomLogClass.TelecomLog analyticsProto =
+                TelecomLogClass.TelecomLog.parseFrom(Base64.decode(sw.toString(), Base64.DEFAULT));
+
+        assertEquals(Analytics.MAX_NUM_CALLS_TO_STORE, analyticsProto.callLogs.length);
+        assertEquals(Arrays.stream(analyticsProto.callLogs)
+                .filter(x -> x.getCallTechnologies() < 100)
+                .count(), 0);
+    }
+
     private void assertIsRoundedToOneSigFig(long x) {
         assertEquals(x, Analytics.roundToOneSigFig(x));
     }
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
index 38ea10e..af1d3a2 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
@@ -604,6 +604,8 @@
                 verify(mCallAudioManager).stopCallWaiting();
                 break;
         }
+
+        sm.quitNow();
     }
 
     private void resetMocks() {
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index 0766b72..d53f270 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -25,7 +25,7 @@
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.server.telecom.BluetoothManager;
+import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallAudioModeStateMachine;
 import com.android.server.telecom.CallAudioRouteStateMachine;
@@ -135,7 +135,7 @@
     }
 
     @Mock CallsManager mockCallsManager;
-    @Mock BluetoothManager mockBluetoothManager;
+    @Mock BluetoothRouteManager mockBluetoothRouteManager;
     @Mock IAudioService mockAudioService;
     @Mock ConnectionServiceWrapper mockConnectionServiceWrapper;
     @Mock WiredHeadsetManager mockWiredHeadsetManager;
@@ -207,15 +207,15 @@
         CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
                 mContext,
                 mockCallsManager,
-                mockBluetoothManager,
+                mockBluetoothRouteManager,
                 mockWiredHeadsetManager,
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 mMockInterruptionFilterProxy,
                 true);
 
-        when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
-        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(true);
+        when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
+        when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
         when(mockAudioManager.isSpeakerphoneOn()).thenReturn(true);
         doAnswer(new Answer() {
             @Override
@@ -236,7 +236,7 @@
                 CallAudioState.ROUTE_WIRED_HEADSET,
                 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER);
         verifyNewSystemCallAudioState(initState, expectedMiddleState);
-        resetMocks();
+        resetMocks(true);
 
         stateMachine.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET);
@@ -248,15 +248,15 @@
         CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
                 mContext,
                 mockCallsManager,
-                mockBluetoothManager,
+                mockBluetoothRouteManager,
                 mockWiredHeadsetManager,
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 mMockInterruptionFilterProxy,
                 true);
 
-        when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
-        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(true);
+        when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
+        when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
         when(mockAudioManager.isSpeakerphoneOn()).thenReturn(true);
 
         CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
@@ -276,7 +276,7 @@
         // Expecting to end up in earpiece, so we expect notifications to be filtered.
         assertEquals(NotificationManager.INTERRUPTION_FILTER_ALARMS,
                 mMockInterruptionFilterProxy.getCurrentInterruptionFilter());
-        resetMocks();
+        resetMocks(false);
         stateMachine.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH);
         stateMachine.sendMessageWithSessionInfo(
@@ -291,15 +291,15 @@
         CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
                 mContext,
                 mockCallsManager,
-                mockBluetoothManager,
+                mockBluetoothRouteManager,
                 mockWiredHeadsetManager,
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 mMockInterruptionFilterProxy,
                 true);
 
-        when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
-        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(true);
+        when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
+        when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
         when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
 
         CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
@@ -310,7 +310,7 @@
                 CallAudioRouteStateMachine.RINGING_FOCUS);
         waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
 
-        verify(mockBluetoothManager, never()).connectBluetoothAudio();
+        verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(null);
         // Shouldn't change interruption filter when in bluetooth route.
         assertEquals(NotificationManager.INTERRUPTION_FILTER_ALL,
                 mMockInterruptionFilterProxy.getCurrentInterruptionFilter());
@@ -318,7 +318,7 @@
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
                 CallAudioRouteStateMachine.ACTIVE_FOCUS);
         waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
-        verify(mockBluetoothManager, times(1)).connectBluetoothAudio();
+        verify(mockBluetoothRouteManager, times(1)).connectBluetoothAudio(null);
     }
 
     @MediumTest
@@ -326,15 +326,15 @@
         CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
                 mContext,
                 mockCallsManager,
-                mockBluetoothManager,
+                mockBluetoothRouteManager,
                 mockWiredHeadsetManager,
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 mMockInterruptionFilterProxy,
                 true);
 
-        when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
-        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(false);
+        when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
+        when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(false);
         when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
         CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
                 CallAudioState.ROUTE_EARPIECE);
@@ -342,10 +342,12 @@
 
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
                 CallAudioRouteStateMachine.RINGING_FOCUS);
-        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(true);
+
+        when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.CONNECT_BLUETOOTH);
         waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
-        verify(mockBluetoothManager, never()).connectBluetoothAudio();
+
+        verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(null);
         CallAudioState expectedEndState = new CallAudioState(false,
                 CallAudioState.ROUTE_BLUETOOTH,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
@@ -354,7 +356,7 @@
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
                 CallAudioRouteStateMachine.ACTIVE_FOCUS);
         waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
-        verify(mockBluetoothManager, times(1)).connectBluetoothAudio();
+        verify(mockBluetoothRouteManager, times(1)).connectBluetoothAudio(null);
     }
 
     @SmallTest
@@ -420,13 +422,13 @@
             boolean doesDeviceSupportEarpiece) {
         when(mockWiredHeadsetManager.isPluggedIn()).thenReturn(
                 (expectedState.getSupportedRouteMask() & CallAudioState.ROUTE_WIRED_HEADSET) != 0);
-        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(
+        when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(
                 (expectedState.getSupportedRouteMask() & CallAudioState.ROUTE_BLUETOOTH) != 0);
 
         CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
                 mContext,
                 mockCallsManager,
-                mockBluetoothManager,
+                mockBluetoothRouteManager,
                 mockWiredHeadsetManager,
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
@@ -933,7 +935,7 @@
 
     private void runParametrizedTestCaseWithFocus(final RoutingTestParameters params)
             throws Throwable {
-        resetMocks();
+        resetMocks(true);
         when(mMockInterruptionFilterProxy.getCurrentInterruptionFilter()).thenReturn(
                 params.initialNotificationFilter);
 
@@ -941,22 +943,14 @@
         final CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
                 mContext,
                 mockCallsManager,
-                mockBluetoothManager,
+                mockBluetoothRouteManager,
                 mockWiredHeadsetManager,
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 mMockInterruptionFilterProxy,
                 params.doesDeviceSupportEarpiece);
 
-        // Set up bluetooth and speakerphone state
-        when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn(
-                params.initialRoute == CallAudioState.ROUTE_BLUETOOTH);
-        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(
-                (params.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0
-                        || (params.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0);
-        doReturn(params.initialRoute == CallAudioState.ROUTE_SPEAKER).when(mockAudioManager).
-                isSpeakerphoneOn();
-        when(fakeCall.getSupportedAudioRoutes()).thenReturn(params.callSupportedRoutes);
+        setupMocksForParams(params);
 
         // Set the initial CallAudioState object
         final CallAudioState initState = new CallAudioState(false,
@@ -966,6 +960,11 @@
         // Make the state machine have focus so that we actually do something
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
                 CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+
+        // Reset mocks one more time to discard stuff from initialization
+        resetMocks(false);
+        setupMocksForParams(params);
         stateMachine.sendMessageWithSessionInfo(params.action);
 
         waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
@@ -991,17 +990,17 @@
         // Verify interactions with the speakerphone and bluetooth systems
         switch (params.bluetoothInteraction) {
             case NONE:
-                verify(mockBluetoothManager, never()).disconnectBluetoothAudio();
-                verify(mockBluetoothManager, never()).connectBluetoothAudio();
+                verify(mockBluetoothRouteManager, never()).disconnectBluetoothAudio();
+                verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(null);
                 break;
             case ON:
-                verify(mockBluetoothManager).connectBluetoothAudio();
+                verify(mockBluetoothRouteManager).connectBluetoothAudio(null);
 
-                verify(mockBluetoothManager, never()).disconnectBluetoothAudio();
+                verify(mockBluetoothRouteManager, never()).disconnectBluetoothAudio();
                 break;
             case OFF:
-                verify(mockBluetoothManager, never()).connectBluetoothAudio();
-                verify(mockBluetoothManager).disconnectBluetoothAudio();
+                verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(null);
+                verify(mockBluetoothRouteManager).disconnectBluetoothAudio();
         }
 
         switch (params.speakerInteraction) {
@@ -1019,15 +1018,27 @@
         verifyNewSystemCallAudioState(initState, expectedState);
     }
 
+    private void setupMocksForParams(RoutingTestParameters params) {
+        // Set up bluetooth and speakerphone state
+        when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(
+                params.initialRoute == CallAudioState.ROUTE_BLUETOOTH);
+        when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(
+                (params.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0
+                        || (params.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0);
+        when(mockAudioManager.isSpeakerphoneOn()).thenReturn(
+                params.initialRoute == CallAudioState.ROUTE_SPEAKER);
+        when(fakeCall.getSupportedAudioRoutes()).thenReturn(params.callSupportedRoutes);
+    }
+
     private void runParametrizedTestCaseWithoutFocus(final RoutingTestParameters params)
             throws Throwable {
-        resetMocks();
+        resetMocks(true);
 
         // Construct a fresh state machine on every case
         final CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
                 mContext,
                 mockCallsManager,
-                mockBluetoothManager,
+                mockBluetoothRouteManager,
                 mockWiredHeadsetManager,
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
@@ -1035,7 +1046,7 @@
                 params.doesDeviceSupportEarpiece);
 
         // Set up bluetooth and speakerphone state
-        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(
+        when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(
                 (params.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0
                 || (params.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0);
         when(mockAudioManager.isSpeakerphoneOn()).thenReturn(
@@ -1064,8 +1075,8 @@
     }
 
     private void verifyNoSystemAudioChanges() {
-        verify(mockBluetoothManager, never()).disconnectBluetoothAudio();
-        verify(mockBluetoothManager, never()).connectBluetoothAudio();
+        verify(mockBluetoothRouteManager, never()).disconnectBluetoothAudio();
+        verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(null);
         verify(mockAudioManager, never()).setSpeakerphoneOn(any(Boolean.class));
         verify(mockCallsManager, never()).onCallAudioStateChanged(any(CallAudioState.class),
                 any(CallAudioState.class));
@@ -1091,11 +1102,14 @@
         assertTrue(newStateCaptor2.getValue().equals(expectedNewState));
     }
 
-    private void resetMocks() {
-        reset(mockAudioManager, mockBluetoothManager, mockCallsManager,
-                mockConnectionServiceWrapper, mMockInterruptionFilterProxy, fakeCall);
-        mMockInterruptionFilterProxy = mock(InterruptionFilterProxy.class);
-        setupInterruptionFilterMocks();
+    private void resetMocks(boolean resetNotificationFilter) {
+        reset(mockAudioManager, mockBluetoothRouteManager, mockCallsManager,
+                mockConnectionServiceWrapper, fakeCall);
+        if (resetNotificationFilter) {
+            reset(mMockInterruptionFilterProxy);
+            mMockInterruptionFilterProxy = mock(InterruptionFilterProxy.class);
+            setupInterruptionFilterMocks();
+        }
         when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall);
         when(fakeCall.getConnectionService()).thenReturn(mockConnectionServiceWrapper);
         when(fakeCall.isAlive()).thenReturn(true);
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index ece6b73..f590e8c 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -322,6 +322,10 @@
 
     @Override
     public void tearDown() throws Exception {
+        mTelecomSystem.getCallsManager().getCallAudioManager()
+                .getCallAudioRouteStateMachine().quitNow();
+        mTelecomSystem.getCallsManager().getCallAudioManager()
+                .getCallAudioModeStateMachine().quitNow();
         mTelecomSystem = null;
         super.tearDown();
     }