Merge "Integrate connectionServiceFocusManager into call flow"
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 36bb4ed..4d97f4d 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.os.VibrationEffect;
 import android.telecom.Log;
+import android.telecom.TelecomManager;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.net.Uri;
@@ -125,6 +126,7 @@
         boolean isSelfManaged = foregroundCall.isSelfManaged();
 
         boolean isRingerAudible = isVolumeOverZero && shouldRingForContact && isRingtonePresent;
+        boolean hasExternalRinger = hasExternalRinger(foregroundCall);
         // Acquire audio focus under any of the following conditions:
         // 1. Should ring for contact and there's an HFP device attached
         // 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone
@@ -136,14 +138,16 @@
         // Don't do call waiting operations or vibration unless these are false.
         boolean isTheaterModeOn = mSystemSettingsUtil.isTheaterModeOn(mContext);
         boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging();
-        boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged;
+        boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged ||
+                hasExternalRinger;
 
         if (endEarly) {
             if (letDialerHandleRinging) {
                 Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING);
             }
             Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " +
-                    "isSelfManaged=%s", isTheaterModeOn, letDialerHandleRinging, isSelfManaged);
+                    "isSelfManaged=%s, hasExternalRinger=%s", isTheaterModeOn,
+                    letDialerHandleRinging, isSelfManaged, hasExternalRinger);
             return shouldAcquireAudioFocus;
         }
 
@@ -241,6 +245,15 @@
         return manager.matchesCallFilter(extras);
     }
 
+    private boolean hasExternalRinger(Call foregroundCall) {
+        Bundle intentExtras = foregroundCall.getIntentExtras();
+        if (intentExtras != null) {
+            return intentExtras.getBoolean(TelecomManager.EXTRA_CALL_EXTERNAL_RINGER, false);
+        } else {
+            return false;
+        }
+    }
+
     private boolean shouldVibrate(Context context, Call call) {
         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
         int ringerMode = audioManager.getRingerModeInternal();
diff --git a/tests/Android.mk b/tests/Android.mk
index 7f449fa..6f34793 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -63,6 +63,7 @@
 LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.server.telecom.*
 LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := com.android.server.telecom.tests.*
 
+LOCAL_COMPATIBILITY_SUITE := device-tests
 include frameworks/base/packages/SettingsLib/common.mk
 
 include $(BUILD_PACKAGE)
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
new file mode 100644
index 0000000..a55aeed
--- /dev/null
+++ b/tests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs Telecom Test Cases.">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="TelecomUnitTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-tag" value="TelecomUnitTests" />
+    <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" />
+    </test>
+</configuration>
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index 4f91df0..76048b7 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -20,15 +20,12 @@
 import android.content.Context;
 import android.media.AudioManager;
 import android.media.IAudioService;
-import android.os.Handler;
 import android.telecom.CallAudioState;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
 
 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;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.ConnectionServiceWrapper;
@@ -47,7 +44,6 @@
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -70,12 +66,7 @@
 import static org.mockito.Mockito.when;
 
 @RunWith(JUnit4.class)
-public class CallAudioRouteStateMachineTest
-        extends StateMachineTestBase<CallAudioRouteStateMachine> {
-    private static final int NONE = 0;
-    private static final int ON = 1;
-    private static final int OFF = 2;
-    private static final int OPTIONAL = 3;
+public class CallAudioRouteStateMachineTest extends TelecomSystemTest {
 
     private static final BluetoothDevice bluetoothDevice1 =
             BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:01");
@@ -84,77 +75,6 @@
     private static final BluetoothDevice bluetoothDevice3 =
             BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:03");
 
-    static class RoutingTestParameters extends TestParameters {
-        public String name;
-        public int initialRoute;
-        public BluetoothDevice initialBluetoothDevice = null;
-        public int availableRoutes; // may excl. speakerphone, because that's always available
-        public List<BluetoothDevice> availableBluetoothDevices = Collections.emptyList();
-        public int speakerInteraction; // one of NONE, ON, or OFF
-        public int bluetoothInteraction; // one of NONE, ON, or OFF
-        public int action;
-        public int expectedRoute;
-        public BluetoothDevice expectedBluetoothDevice = null;
-        public int expectedAvailableRoutes; // also may exclude the speakerphone.
-        public int earpieceControl; // Allows disabling the earpiece to simulate Wear or Car
-        public boolean shouldRunWithFocus;
-
-        public int callSupportedRoutes = CallAudioState.ROUTE_ALL;
-
-        public RoutingTestParameters(String name, int initialRoute,
-                int availableRoutes, int speakerInteraction,
-                int bluetoothInteraction, int action, int expectedRoute,
-                int expectedAvailableRoutes, int earpieceControl,
-                boolean shouldRunWithFocus) {
-            this.name = name;
-            this.initialRoute = initialRoute;
-            this.availableRoutes = availableRoutes;
-            this.speakerInteraction = speakerInteraction;
-            this.bluetoothInteraction = bluetoothInteraction;
-            this.action = action;
-            this.expectedRoute = expectedRoute;
-            this.expectedAvailableRoutes = expectedAvailableRoutes;
-            this.earpieceControl = earpieceControl;
-            this.shouldRunWithFocus = shouldRunWithFocus;
-        }
-
-        public RoutingTestParameters setCallSupportedRoutes(int routes) {
-            callSupportedRoutes = routes;
-            return this;
-        }
-
-        public RoutingTestParameters setInitialBluetoothDevice(BluetoothDevice device) {
-            initialBluetoothDevice = device;
-            return this;
-        }
-
-        public RoutingTestParameters setAvailableBluetoothDevices(BluetoothDevice... devices) {
-            availableBluetoothDevices = Arrays.asList(devices);
-            return this;
-        }
-
-        public RoutingTestParameters setExpectedBluetoothDevice(BluetoothDevice device) {
-            expectedBluetoothDevice = device;
-            return this;
-        }
-
-        @Override
-        public String toString() {
-            return "RoutingTestParameters{" +
-                    "name='" + name + '\'' +
-                    ", initialRoute=" + initialRoute +
-                    ", availableRoutes=" + availableRoutes +
-                    ", speakerInteraction=" + speakerInteraction +
-                    ", bluetoothInteraction=" + bluetoothInteraction +
-                    ", action=" + action +
-                    ", expectedRoute=" + expectedRoute +
-                    ", expectedAvailableRoutes=" + expectedAvailableRoutes +
-                    ", earpieceControl=" + earpieceControl +
-                    ", shouldRunWithFocus=" + shouldRunWithFocus +
-                    '}';
-        }
-    }
-
     @Mock CallsManager mockCallsManager;
     @Mock BluetoothRouteManager mockBluetoothRouteManager;
     @Mock IAudioService mockAudioService;
@@ -212,20 +132,6 @@
         assertNotNull(stateMachine);
     }
 
-    @LargeTest
-    @Test
-    public void testStateMachineTransitionsWithFocus() throws Throwable {
-        List<RoutingTestParameters> paramList = generateTransitionTests(true);
-        parametrizedTestStateMachine(paramList);
-    }
-
-    @LargeTest
-    @Test
-    public void testStateMachineTransitionsWithoutFocus() throws Throwable {
-        List<RoutingTestParameters> paramList = generateTransitionTests(false);
-        parametrizedTestStateMachine(paramList);
-    }
-
     @MediumTest
     @Test
     public void testSpeakerPersistence() {
@@ -266,7 +172,7 @@
 
         stateMachine.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET);
-        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
         verifyNewSystemCallAudioState(expectedMiddleState, initState);
     }
 
@@ -300,7 +206,7 @@
                 CallAudioState.ROUTE_EARPIECE,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
 
-        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
         verifyNewSystemCallAudioState(initState, expectedEndState);
         resetMocks();
         stateMachine.sendMessageWithSessionInfo(
@@ -308,7 +214,7 @@
         stateMachine.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.CONNECT_BLUETOOTH);
 
-        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
         assertEquals(expectedEndState, stateMachine.getCurrentCallAudioState());
     }
 
@@ -346,7 +252,7 @@
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH,
                 null, availableDevices);
 
-        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
         verifyNewSystemCallAudioState(initState, expectedMidState);
         resetMocks();
 
@@ -365,9 +271,9 @@
                 CallAudioState.ROUTE_BLUETOOTH,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH,
                 bluetoothDevice1, availableDevices);
-        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
         // second wait needed for the BT_AUDIO_CONNECTED message
-        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
         verifyNewSystemCallAudioState(expectedMidState, expectedEndState);
 
         stateMachine.sendMessageWithSessionInfo(
@@ -377,9 +283,9 @@
         stateMachine.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.CONNECT_BLUETOOTH);
 
-        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
         // second wait needed for the BT_AUDIO_CONNECTED message
-        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
         // Verify that we're still on bluetooth.
         assertEquals(expectedEndState, stateMachine.getCurrentCallAudioState());
     }
@@ -408,13 +314,13 @@
 
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
                 CallAudioRouteStateMachine.RINGING_FOCUS);
-        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
 
         verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(nullable(String.class));
 
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
                 CallAudioRouteStateMachine.ACTIVE_FOCUS);
-        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
         verify(mockBluetoothRouteManager, times(1)).connectBluetoothAudio(nullable(String.class));
     }
 
@@ -441,7 +347,7 @@
                 CallAudioRouteStateMachine.RINGING_FOCUS);
         // Wait for the state machine to finish transiting to ActiveEarpiece before hooking up
         // bluetooth mocks
-        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
 
         when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
         when(mockBluetoothRouteManager.getConnectedDevices())
@@ -449,7 +355,7 @@
         stateMachine.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.BLUETOOTH_DEVICE_LIST_CHANGED);
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.CONNECT_BLUETOOTH);
-        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
 
         verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(null);
         CallAudioState expectedEndState = new CallAudioState(false,
@@ -460,7 +366,7 @@
 
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
                 CallAudioRouteStateMachine.ACTIVE_FOCUS);
-        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
         verify(mockBluetoothRouteManager, times(1)).connectBluetoothAudio(null);
     }
 
@@ -499,10 +405,10 @@
 
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH,
                 0, bluetoothDevice2.getAddress());
-        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
 
         verify(mockBluetoothRouteManager).connectBluetoothAudio(bluetoothDevice2.getAddress());
-        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
         CallAudioState expectedEndState = new CallAudioState(false,
                 CallAudioState.ROUTE_BLUETOOTH,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH,
@@ -598,579 +504,6 @@
         assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
     }
 
-    private List<RoutingTestParameters> generateTransitionTests(boolean shouldRunWithFocus) {
-        List<RoutingTestParameters> params = new ArrayList<>();
-        params.add(new RoutingTestParameters(
-                "Connect headset during earpiece", // name
-                CallAudioState.ROUTE_EARPIECE, // initialRoute
-                CallAudioState.ROUTE_EARPIECE, // availableRoutes
-                OPTIONAL, // speakerInteraction
-                NONE, // bluetoothInteraction
-                CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action
-                CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
-                CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Connect headset during bluetooth", // name
-                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
-                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
-                OPTIONAL, // speakerInteraction
-                OFF, // bluetoothInteraction
-                CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action
-                CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
-                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // expectedAvai
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Connect headset during speakerphone", // name
-                CallAudioState.ROUTE_SPEAKER, // initialRoute
-                CallAudioState.ROUTE_EARPIECE, // availableRoutes
-                OFF, // speakerInteraction
-                NONE, // bluetoothInteraction
-                CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action
-                CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
-                CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Disconnect headset during headset", // name
-                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
-                CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
-                OPTIONAL, // speakerInteraction
-                NONE, // bluetoothInteraction
-                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
-                CallAudioState.ROUTE_EARPIECE, // expectedRoute
-                CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Disconnect headset during headset with bluetooth available", // name
-                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
-                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
-                OPTIONAL, // speakerInteraction
-                ON, // bluetoothInteraction
-                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
-                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
-                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Disconnect headset during bluetooth", // name
-                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
-                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
-                OPTIONAL, // speakerInteraction
-                NONE, // bluetoothInteraction
-                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
-                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
-                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Disconnect headset during speakerphone", // name
-                CallAudioState.ROUTE_SPEAKER, // initialRoute
-                CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
-                OPTIONAL, // speakerInteraction
-                NONE, // bluetoothInteraction
-                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
-                CallAudioState.ROUTE_SPEAKER, // expectedRoute
-                CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Disconnect headset during speakerphone with bluetooth available", // name
-                CallAudioState.ROUTE_SPEAKER, // initialRoute
-                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
-                OPTIONAL, // speakerInteraction
-                NONE, // bluetoothInteraction
-                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
-                CallAudioState.ROUTE_SPEAKER, // expectedRoute
-                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Connect bluetooth during earpiece", // name
-                CallAudioState.ROUTE_EARPIECE, // initialRoute
-                CallAudioState.ROUTE_EARPIECE, // availableRoutes
-                OPTIONAL, // speakerInteraction
-                ON, // bluetoothInteraction
-                CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
-                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
-                CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_EARPIECE, // expectedAvailable
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Connect bluetooth during wired headset", // name
-                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
-                CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
-                OPTIONAL, // speakerInteraction
-                ON, // bluetoothInteraction
-                CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
-                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
-                CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvai
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Connect bluetooth during speakerphone", // name
-                CallAudioState.ROUTE_SPEAKER, // initialRoute
-                CallAudioState.ROUTE_EARPIECE, // availableRoutes
-                OFF, // speakerInteraction
-                ON, // bluetoothInteraction
-                CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
-                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
-                CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_EARPIECE, // expectedAvailable
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Disconnect bluetooth during bluetooth without headset in", // name
-                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
-                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
-                shouldRunWithFocus
-        ));
-
-        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
-                CallAudioState.ROUTE_EARPIECE, // expectedRoute
-                CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Disconnect bluetooth during bluetooth with headset in", // name
-                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
-                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
-                OPTIONAL, // speakerInteraction
-                OFF, // bluetoothInteraction
-                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
-                CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
-                CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Disconnect bluetooth during speakerphone", // name
-                CallAudioState.ROUTE_SPEAKER, // initialRoute
-                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
-                OPTIONAL, // speakerInteraction
-                NONE, // bluetoothInteraction
-                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
-                CallAudioState.ROUTE_SPEAKER, // expectedRoute
-                CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Disconnect bluetooth during earpiece", // name
-                CallAudioState.ROUTE_EARPIECE, // initialRoute
-                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
-                OPTIONAL, // speakerInteraction
-                NONE, // bluetoothInteraction
-                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
-                CallAudioState.ROUTE_EARPIECE, // expectedRoute
-                CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Switch to speakerphone from earpiece", // name
-                CallAudioState.ROUTE_EARPIECE, // initialRoute
-                CallAudioState.ROUTE_EARPIECE, // availableRoutes
-                ON, // speakerInteraction
-                NONE, // bluetoothInteraction
-                CallAudioRouteStateMachine.SWITCH_SPEAKER, // action
-                CallAudioState.ROUTE_SPEAKER, // expectedRoute
-                CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Switch to speakerphone from headset", // name
-                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
-                CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
-                ON, // speakerInteraction
-                NONE, // bluetoothInteraction
-                CallAudioRouteStateMachine.SWITCH_SPEAKER, // action
-                CallAudioState.ROUTE_SPEAKER, // expectedRoute
-                CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Switch to speakerphone from bluetooth", // name
-                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
-                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
-                ON, // speakerInteraction
-                OFF, // bluetoothInteraction
-                CallAudioRouteStateMachine.SWITCH_SPEAKER, // action
-                CallAudioState.ROUTE_SPEAKER, // expectedRoute
-                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // expectedAvai
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Switch to earpiece from bluetooth", // name
-                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
-                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
-                OPTIONAL, // speakerInteraction
-                OFF, // bluetoothInteraction
-                CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
-                CallAudioState.ROUTE_EARPIECE, // expectedRoute
-                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Switch to earpiece from speakerphone", // name
-                CallAudioState.ROUTE_SPEAKER, // initialRoute
-                CallAudioState.ROUTE_EARPIECE, // availableRoutes
-                OFF, // speakerInteraction
-                NONE, // bluetoothInteraction
-                CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
-                CallAudioState.ROUTE_EARPIECE, // expectedRoute
-                CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Switch to earpiece from speakerphone, priority notifications", // name
-                CallAudioState.ROUTE_SPEAKER, // initialRoute
-                CallAudioState.ROUTE_EARPIECE, // availableRoutes
-                OFF, // speakerInteraction
-                NONE, // bluetoothInteraction
-                CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
-                CallAudioState.ROUTE_EARPIECE, // expectedRoute
-                CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Switch to earpiece from speakerphone, silent mode", // name
-                CallAudioState.ROUTE_SPEAKER, // initialRoute
-                CallAudioState.ROUTE_EARPIECE, // availableRoutes
-                OFF, // speakerInteraction
-                NONE, // bluetoothInteraction
-                CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
-                CallAudioState.ROUTE_EARPIECE, // expectedRoute
-                CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Switch to bluetooth from speakerphone", // name
-                CallAudioState.ROUTE_SPEAKER, // initialRoute
-                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
-                OFF, // speakerInteraction
-                ON, // bluetoothInteraction
-                CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action
-                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
-                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Switch to bluetooth from earpiece", // name
-                CallAudioState.ROUTE_EARPIECE, // initialRoute
-                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
-                OPTIONAL, // speakerInteraction
-                ON, // bluetoothInteraction
-                CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action
-                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
-                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Switch to bluetooth from wired headset", // name
-                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
-                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
-                OPTIONAL, // speakerInteraction
-                ON, // bluetoothInteraction
-                CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action
-                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
-                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // expectedAvai
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Switch from bluetooth to wired/earpiece when neither are available", // name
-                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
-                CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
-                ON, // speakerInteraction
-                OFF, // bluetoothInteraction
-                CallAudioRouteStateMachine.SWITCH_BASELINE_ROUTE, // action
-                CallAudioState.ROUTE_SPEAKER, // expectedRoute
-                CallAudioState.ROUTE_BLUETOOTH, // expectedAvailableRoutes
-                CallAudioRouteStateMachine.EARPIECE_FORCE_DISABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Disconnect wired headset when device does not support earpiece", // name
-                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
-                CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
-                ON, // speakerInteraction
-                NONE, // bluetoothInteraction
-                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
-                CallAudioState.ROUTE_SPEAKER, // expectedRoute
-                CallAudioState.ROUTE_SPEAKER, // expectedAvailableRoutes
-                CallAudioRouteStateMachine.EARPIECE_FORCE_DISABLED, // earpieceControl
-                shouldRunWithFocus
-        ));
-
-        params.add(new RoutingTestParameters(
-                "Disconnect wired headset when call doesn't support earpiece", // name
-                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
-                CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
-                ON, // speakerInteraction
-                NONE, // bluetoothInteraction
-                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
-                CallAudioState.ROUTE_SPEAKER, // expectedRoute
-                CallAudioState.ROUTE_SPEAKER, // expectedAvailableRoutes
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ).setCallSupportedRoutes(CallAudioState.ROUTE_ALL & ~CallAudioState.ROUTE_EARPIECE));
-
-        params.add(new RoutingTestParameters(
-                "Disconnect bluetooth when call does not support earpiece", // name
-                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
-                CallAudioState.ROUTE_BLUETOOTH,  // availableRoutes
-                ON, // speakerInteraction
-                OFF, // bluetoothInteraction
-                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
-                CallAudioState.ROUTE_SPEAKER, // expectedRoute
-                CallAudioState.ROUTE_SPEAKER, // expectedAvailableRoutes
-                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, // earpieceControl
-                shouldRunWithFocus
-        ).setCallSupportedRoutes(CallAudioState.ROUTE_ALL & ~CallAudioState.ROUTE_EARPIECE));
-
-        return params;
-    }
-
-    @Override
-    protected void runParametrizedTestCase(TestParameters _params) throws Throwable {
-        RoutingTestParameters params = (RoutingTestParameters) _params;
-        if (params.shouldRunWithFocus) {
-            runParametrizedTestCaseWithFocus(params);
-        } else {
-            runParametrizedTestCaseWithoutFocus(params);
-        }
-    }
-
-    private void runParametrizedTestCaseWithFocus(final RoutingTestParameters params)
-            throws Throwable {
-        resetMocks();
-
-        // Construct a fresh state machine on every case
-        final CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
-                mContext,
-                mockCallsManager,
-                mockBluetoothRouteManager,
-                mockWiredHeadsetManager,
-                mockStatusBarNotifier,
-                mAudioServiceFactory,
-                params.earpieceControl);
-
-        setupMocksForParams(stateMachine, params);
-
-        // Set the initial CallAudioState object
-        final CallAudioState initState = new CallAudioState(false,
-                params.initialRoute, (params.availableRoutes | CallAudioState.ROUTE_SPEAKER));
-        stateMachine.initialize(initState);
-
-        // Make the state machine have focus so that we actually do something
-        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
-                CallAudioRouteStateMachine.ACTIVE_FOCUS);
-        // Tell the state machine that BT is on, if that's what the params say.
-        if (params.initialRoute == CallAudioState.ROUTE_BLUETOOTH) {
-            stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
-        }
-        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
-
-        // Reset mocks one more time to discard stuff from initialization
-        resetMocks();
-        setupMocksForParams(stateMachine, params);
-        stateMachine.sendMessageWithSessionInfo(params.action);
-
-        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
-        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
-
-        Handler h = stateMachine.getHandler();
-        waitForHandlerAction(h, TEST_TIMEOUT);
-        stateMachine.quitStateMachine();
-
-        // Verify interactions with the speakerphone and bluetooth systems
-        switch (params.bluetoothInteraction) {
-            case NONE:
-                verify(mockBluetoothRouteManager, never()).disconnectBluetoothAudio();
-                verify(mockBluetoothRouteManager, never())
-                        .connectBluetoothAudio(nullable(String.class));
-                break;
-            case ON:
-                if (params.expectedBluetoothDevice == null) {
-                    verify(mockBluetoothRouteManager).connectBluetoothAudio(null);
-                } else {
-                    verify(mockBluetoothRouteManager).connectBluetoothAudio(
-                            params.expectedBluetoothDevice.getAddress());
-                }
-                verify(mockBluetoothRouteManager, never()).disconnectBluetoothAudio();
-                break;
-            case OFF:
-                verify(mockBluetoothRouteManager, never())
-                        .connectBluetoothAudio(nullable(String.class));
-                verify(mockBluetoothRouteManager).disconnectBluetoothAudio();
-                break;
-            case OPTIONAL:
-                // optional, don't test
-                break;
-        }
-
-        switch (params.speakerInteraction) {
-            case NONE:
-                verify(mockAudioManager, never()).setSpeakerphoneOn(any(Boolean.class));
-                break;
-            case ON: // fall through
-            case OFF:
-                verify(mockAudioManager).setSpeakerphoneOn(params.speakerInteraction == ON);
-                break;
-            case OPTIONAL:
-                // optional, don't test
-                break;
-        }
-
-        // Verify the end state
-        CallAudioState expectedState = new CallAudioState(false, params.expectedRoute,
-                params.expectedAvailableRoutes | CallAudioState.ROUTE_SPEAKER);
-        verifyNewSystemCallAudioState(initState, expectedState);
-    }
-
-    private void setupMocksForParams(final CallAudioRouteStateMachine sm,
-            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(mockBluetoothRouteManager.getConnectedDevices())
-                .thenReturn(params.availableBluetoothDevices);
-        if (params.initialBluetoothDevice != null) {
-            when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice())
-                    .thenReturn(params.initialBluetoothDevice);
-        }
-
-
-        doAnswer(invocation -> {
-            sm.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
-            return null;
-        }).when(mockBluetoothRouteManager).connectBluetoothAudio(nullable(String.class));
-
-        when(mockAudioManager.isSpeakerphoneOn()).thenReturn(
-                params.initialRoute == CallAudioState.ROUTE_SPEAKER);
-        when(fakeCall.getSupportedAudioRoutes()).thenReturn(params.callSupportedRoutes);
-    }
-
-    private void runParametrizedTestCaseWithoutFocus(final RoutingTestParameters params)
-            throws Throwable {
-        resetMocks();
-
-        // Construct a fresh state machine on every case
-        final CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
-                mContext,
-                mockCallsManager,
-                mockBluetoothRouteManager,
-                mockWiredHeadsetManager,
-                mockStatusBarNotifier,
-                mAudioServiceFactory,
-                params.earpieceControl);
-
-        // Set up bluetooth and speakerphone state
-        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);
-
-        // Set the initial CallAudioState object
-        CallAudioState initState = new CallAudioState(false,
-                params.initialRoute, (params.availableRoutes | CallAudioState.ROUTE_SPEAKER));
-        stateMachine.initialize(initState);
-        // Omit the focus-getting statement
-        stateMachine.sendMessageWithSessionInfo(params.action);
-
-        waitForStateMachineActionCompletion(stateMachine, CallAudioModeStateMachine.RUN_RUNNABLE);
-
-        Handler h = stateMachine.getHandler();
-        waitForHandlerAction(h, TEST_TIMEOUT);
-        stateMachine.quitStateMachine();
-
-        // Verify that no substantive interactions have taken place with the
-        // rest of the system
-        verifyNoSystemAudioChanges();
-
-        // Verify the end state
-        CallAudioState expectedState = new CallAudioState(false, params.expectedRoute,
-                params.expectedAvailableRoutes | CallAudioState.ROUTE_SPEAKER);
-        assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
-    }
-
-    private void verifyNoSystemAudioChanges() {
-        verify(mockBluetoothRouteManager, never()).disconnectBluetoothAudio();
-        verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(nullable(String.class));
-        verify(mockAudioManager, never()).setSpeakerphoneOn(any(Boolean.class));
-        verify(mockCallsManager, never()).onCallAudioStateChanged(any(CallAudioState.class),
-                any(CallAudioState.class));
-        verify(mockConnectionServiceWrapper, never()).onCallAudioStateChanged(
-                any(Call.class), any(CallAudioState.class));
-    }
-
     private void verifyNewSystemCallAudioState(CallAudioState expectedOldState,
             CallAudioState expectedNewState) {
         ArgumentCaptor<CallAudioState> oldStateCaptor = ArgumentCaptor.forClass(
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
new file mode 100644
index 0000000..345312e
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
@@ -0,0 +1,744 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.telecom.tests;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.IAudioService;
+import android.os.Handler;
+import android.telecom.CallAudioState;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.CallAudioModeStateMachine;
+import com.android.server.telecom.CallAudioRouteStateMachine;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.StatusBarNotifier;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.WiredHeadsetManager;
+import com.android.server.telecom.bluetooth.BluetoothRouteManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(Parameterized.class)
+public class CallAudioRouteTransitionTests extends TelecomTestCase {
+    private static final int NONE = 0;
+    private static final int ON = 1;
+    private static final int OFF = 2;
+    private static final int OPTIONAL = 3;
+
+    static class RoutingTestParameters {
+        public String name;
+        public int initialRoute;
+        public BluetoothDevice initialBluetoothDevice = null;
+        public int availableRoutes; // may excl. speakerphone, because that's always available
+        public List<BluetoothDevice> availableBluetoothDevices = Collections.emptyList();
+        public int speakerInteraction; // one of NONE, ON, or OFF
+        public int bluetoothInteraction; // one of NONE, ON, or OFF
+        public int action;
+        public int expectedRoute;
+        public BluetoothDevice expectedBluetoothDevice = null;
+        public int expectedAvailableRoutes; // also may exclude the speakerphone.
+        public int earpieceControl; // Allows disabling the earpiece to simulate Wear or Car
+
+        public int callSupportedRoutes = CallAudioState.ROUTE_ALL;
+
+        public RoutingTestParameters(String name, int initialRoute,
+                int availableRoutes, int speakerInteraction,
+                int bluetoothInteraction, int action, int expectedRoute,
+                int expectedAvailableRoutes, int earpieceControl) {
+            this.name = name;
+            this.initialRoute = initialRoute;
+            this.availableRoutes = availableRoutes;
+            this.speakerInteraction = speakerInteraction;
+            this.bluetoothInteraction = bluetoothInteraction;
+            this.action = action;
+            this.expectedRoute = expectedRoute;
+            this.expectedAvailableRoutes = expectedAvailableRoutes;
+            this.earpieceControl = earpieceControl;
+        }
+
+        public RoutingTestParameters setCallSupportedRoutes(int routes) {
+            callSupportedRoutes = routes;
+            return this;
+        }
+
+        public RoutingTestParameters setInitialBluetoothDevice(BluetoothDevice device) {
+            initialBluetoothDevice = device;
+            return this;
+        }
+
+        public RoutingTestParameters setAvailableBluetoothDevices(BluetoothDevice... devices) {
+            availableBluetoothDevices = Arrays.asList(devices);
+            return this;
+        }
+
+        public RoutingTestParameters setExpectedBluetoothDevice(BluetoothDevice device) {
+            expectedBluetoothDevice = device;
+            return this;
+        }
+
+        @Override
+        public String toString() {
+            return "RoutingTestParameters{" +
+                    "name='" + name + '\'' +
+                    ", initialRoute=" + initialRoute +
+                    ", availableRoutes=" + availableRoutes +
+                    ", speakerInteraction=" + speakerInteraction +
+                    ", bluetoothInteraction=" + bluetoothInteraction +
+                    ", action=" + action +
+                    ", expectedRoute=" + expectedRoute +
+                    ", expectedAvailableRoutes=" + expectedAvailableRoutes +
+                    ", earpieceControl=" + earpieceControl +
+                    '}';
+        }
+    }
+
+    private final RoutingTestParameters mParams;
+    @Mock CallsManager mockCallsManager;
+    @Mock BluetoothRouteManager mockBluetoothRouteManager;
+    @Mock IAudioService mockAudioService;
+    @Mock ConnectionServiceWrapper mockConnectionServiceWrapper;
+    @Mock WiredHeadsetManager mockWiredHeadsetManager;
+    @Mock StatusBarNotifier mockStatusBarNotifier;
+    @Mock Call fakeCall;
+    private CallAudioManager.AudioServiceFactory mAudioServiceFactory;
+    private static final int TEST_TIMEOUT = 500;
+    private AudioManager mockAudioManager;
+    private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
+
+    public CallAudioRouteTransitionTests(RoutingTestParameters params) {
+        mParams = params;
+    }
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+        mockAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+
+        mAudioServiceFactory = new CallAudioManager.AudioServiceFactory() {
+            @Override
+            public IAudioService getAudioService() {
+                return mockAudioService;
+            }
+        };
+
+        when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall);
+        when(mockCallsManager.getLock()).thenReturn(mLock);
+        when(mockCallsManager.hasVideoCall()).thenReturn(false);
+        when(fakeCall.getConnectionService()).thenReturn(mockConnectionServiceWrapper);
+        when(fakeCall.isAlive()).thenReturn(true);
+        when(fakeCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_ALL);
+
+        doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class),
+                any(CallAudioState.class));
+    }
+
+    private void setupMocksForParams(final CallAudioRouteStateMachine sm,
+            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(mockBluetoothRouteManager.getConnectedDevices())
+                .thenReturn(params.availableBluetoothDevices);
+        if (params.initialBluetoothDevice != null) {
+            when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice())
+                    .thenReturn(params.initialBluetoothDevice);
+        }
+
+
+        doAnswer(invocation -> {
+            sm.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+            return null;
+        }).when(mockBluetoothRouteManager).connectBluetoothAudio(nullable(String.class));
+
+        when(mockAudioManager.isSpeakerphoneOn()).thenReturn(
+                params.initialRoute == CallAudioState.ROUTE_SPEAKER);
+        when(fakeCall.getSupportedAudioRoutes()).thenReturn(params.callSupportedRoutes);
+    }
+
+    @Test
+    @SmallTest
+    public void testActiveTransition() {
+        final CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                mParams.earpieceControl);
+
+        setupMocksForParams(stateMachine, mParams);
+
+        // Set the initial CallAudioState object
+        final CallAudioState initState = new CallAudioState(false,
+                mParams.initialRoute, (mParams.availableRoutes | CallAudioState.ROUTE_SPEAKER));
+        stateMachine.initialize(initState);
+
+        // Make the state machine have focus so that we actually do something
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        // Tell the state machine that BT is on, if that's what the mParams say.
+        if (mParams.initialRoute == CallAudioState.ROUTE_BLUETOOTH) {
+            stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+        }
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+
+        // Reset mocks to discard stuff from initialization
+        resetMocks();
+        setupMocksForParams(stateMachine, mParams);
+        stateMachine.sendMessageWithSessionInfo(mParams.action);
+
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+
+        Handler h = stateMachine.getHandler();
+        waitForHandlerAction(h, TEST_TIMEOUT);
+        stateMachine.quitStateMachine();
+
+        // Verify interactions with the speakerphone and bluetooth systems
+        switch (mParams.bluetoothInteraction) {
+            case NONE:
+                verify(mockBluetoothRouteManager, never()).disconnectBluetoothAudio();
+                verify(mockBluetoothRouteManager, never())
+                        .connectBluetoothAudio(nullable(String.class));
+                break;
+            case ON:
+                if (mParams.expectedBluetoothDevice == null) {
+                    verify(mockBluetoothRouteManager).connectBluetoothAudio(null);
+                } else {
+                    verify(mockBluetoothRouteManager).connectBluetoothAudio(
+                            mParams.expectedBluetoothDevice.getAddress());
+                }
+                verify(mockBluetoothRouteManager, never()).disconnectBluetoothAudio();
+                break;
+            case OFF:
+                verify(mockBluetoothRouteManager, never())
+                        .connectBluetoothAudio(nullable(String.class));
+                verify(mockBluetoothRouteManager).disconnectBluetoothAudio();
+                break;
+            case OPTIONAL:
+                // optional, don't test
+                break;
+        }
+
+        switch (mParams.speakerInteraction) {
+            case NONE:
+                verify(mockAudioManager, never()).setSpeakerphoneOn(any(Boolean.class));
+                break;
+            case ON: // fall through
+            case OFF:
+                verify(mockAudioManager).setSpeakerphoneOn(mParams.speakerInteraction == ON);
+                break;
+            case OPTIONAL:
+                // optional, don't test
+                break;
+        }
+
+        // Verify the end state
+        CallAudioState expectedState = new CallAudioState(false, mParams.expectedRoute,
+                mParams.expectedAvailableRoutes | CallAudioState.ROUTE_SPEAKER);
+        verifyNewSystemCallAudioState(initState, expectedState);
+    }
+
+    @Test
+    @SmallTest
+    public void testQuiescentTransition() {
+        final CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                mParams.earpieceControl);
+
+        // Set up bluetooth and speakerphone state
+        when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(
+                (mParams.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0
+                || (mParams.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0);
+        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));
+        stateMachine.initialize(initState);
+        // Omit the focus-getting statement
+        stateMachine.sendMessageWithSessionInfo(mParams.action);
+
+        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
+        // rest of the system
+        verifyNoSystemAudioChanges();
+
+        // Verify the end state
+        CallAudioState expectedState = new CallAudioState(false, mParams.expectedRoute,
+                mParams.expectedAvailableRoutes | CallAudioState.ROUTE_SPEAKER);
+        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
+                CallAudioState.ROUTE_EARPIECE, // availableRoutes
+                OPTIONAL, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action
+                CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
+                CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Connect headset during bluetooth", // name
+                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+                OPTIONAL, // speakerInteraction
+                OFF, // bluetoothInteraction
+                CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action
+                CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
+                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // expectedAvai
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Connect headset during speakerphone", // name
+                CallAudioState.ROUTE_SPEAKER, // initialRoute
+                CallAudioState.ROUTE_EARPIECE, // availableRoutes
+                OFF, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action
+                CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
+                CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Disconnect headset during headset", // name
+                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+                CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
+                OPTIONAL, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
+                CallAudioState.ROUTE_EARPIECE, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Disconnect headset during headset with bluetooth available", // name
+                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
+                OPTIONAL, // speakerInteraction
+                ON, // bluetoothInteraction
+                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
+                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Disconnect headset during bluetooth", // name
+                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
+                OPTIONAL, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
+                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Disconnect headset during speakerphone", // name
+                CallAudioState.ROUTE_SPEAKER, // initialRoute
+                CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
+                OPTIONAL, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
+                CallAudioState.ROUTE_SPEAKER, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Disconnect headset during speakerphone with bluetooth available", // name
+                CallAudioState.ROUTE_SPEAKER, // initialRoute
+                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
+                OPTIONAL, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
+                CallAudioState.ROUTE_SPEAKER, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Connect bluetooth during earpiece", // name
+                CallAudioState.ROUTE_EARPIECE, // initialRoute
+                CallAudioState.ROUTE_EARPIECE, // availableRoutes
+                OPTIONAL, // speakerInteraction
+                ON, // bluetoothInteraction
+                CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
+                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
+                CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_EARPIECE, // expectedAvailable
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Connect bluetooth during wired headset", // name
+                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+                CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
+                OPTIONAL, // speakerInteraction
+                ON, // bluetoothInteraction
+                CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
+                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
+                CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvai
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Connect bluetooth during speakerphone", // name
+                CallAudioState.ROUTE_SPEAKER, // initialRoute
+                CallAudioState.ROUTE_EARPIECE, // availableRoutes
+                OFF, // speakerInteraction
+                ON, // bluetoothInteraction
+                CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
+                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
+                CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_EARPIECE, // expectedAvailable
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Disconnect bluetooth during bluetooth without headset in", // name
+                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+                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
+                CallAudioState.ROUTE_EARPIECE, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Disconnect bluetooth during bluetooth with headset in", // name
+                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
+                OPTIONAL, // speakerInteraction
+                OFF, // bluetoothInteraction
+                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
+                CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
+                CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Disconnect bluetooth during speakerphone", // name
+                CallAudioState.ROUTE_SPEAKER, // initialRoute
+                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
+                OPTIONAL, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
+                CallAudioState.ROUTE_SPEAKER, // expectedRoute
+                CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Disconnect bluetooth during earpiece", // name
+                CallAudioState.ROUTE_EARPIECE, // initialRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+                OPTIONAL, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
+                CallAudioState.ROUTE_EARPIECE, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Switch to speakerphone from earpiece", // name
+                CallAudioState.ROUTE_EARPIECE, // initialRoute
+                CallAudioState.ROUTE_EARPIECE, // availableRoutes
+                ON, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.SWITCH_SPEAKER, // action
+                CallAudioState.ROUTE_SPEAKER, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Switch to speakerphone from headset", // name
+                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+                CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
+                ON, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.SWITCH_SPEAKER, // action
+                CallAudioState.ROUTE_SPEAKER, // expectedRoute
+                CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Switch to speakerphone from bluetooth", // name
+                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
+                ON, // speakerInteraction
+                OFF, // bluetoothInteraction
+                CallAudioRouteStateMachine.SWITCH_SPEAKER, // action
+                CallAudioState.ROUTE_SPEAKER, // expectedRoute
+                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // expectedAvai
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Switch to earpiece from bluetooth", // name
+                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+                OPTIONAL, // speakerInteraction
+                OFF, // bluetoothInteraction
+                CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
+                CallAudioState.ROUTE_EARPIECE, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Switch to earpiece from speakerphone", // name
+                CallAudioState.ROUTE_SPEAKER, // initialRoute
+                CallAudioState.ROUTE_EARPIECE, // availableRoutes
+                OFF, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
+                CallAudioState.ROUTE_EARPIECE, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Switch to earpiece from speakerphone, priority notifications", // name
+                CallAudioState.ROUTE_SPEAKER, // initialRoute
+                CallAudioState.ROUTE_EARPIECE, // availableRoutes
+                OFF, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
+                CallAudioState.ROUTE_EARPIECE, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Switch to earpiece from speakerphone, silent mode", // name
+                CallAudioState.ROUTE_SPEAKER, // initialRoute
+                CallAudioState.ROUTE_EARPIECE, // availableRoutes
+                OFF, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
+                CallAudioState.ROUTE_EARPIECE, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Switch to bluetooth from speakerphone", // name
+                CallAudioState.ROUTE_SPEAKER, // initialRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+                OFF, // speakerInteraction
+                ON, // bluetoothInteraction
+                CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action
+                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Switch to bluetooth from earpiece", // name
+                CallAudioState.ROUTE_EARPIECE, // initialRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+                OPTIONAL, // speakerInteraction
+                ON, // bluetoothInteraction
+                CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action
+                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Switch to bluetooth from wired headset", // name
+                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
+                OPTIONAL, // speakerInteraction
+                ON, // bluetoothInteraction
+                CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action
+                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
+                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // expectedAvai
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Switch from bluetooth to wired/earpiece when neither are available", // name
+                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+                CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+                ON, // speakerInteraction
+                OFF, // bluetoothInteraction
+                CallAudioRouteStateMachine.SWITCH_BASELINE_ROUTE, // action
+                CallAudioState.ROUTE_SPEAKER, // expectedRoute
+                CallAudioState.ROUTE_BLUETOOTH, // expectedAvailableRoutes
+                CallAudioRouteStateMachine.EARPIECE_FORCE_DISABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Disconnect wired headset when device does not support earpiece", // name
+                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+                CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
+                ON, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
+                CallAudioState.ROUTE_SPEAKER, // expectedRoute
+                CallAudioState.ROUTE_SPEAKER, // expectedAvailableRoutes
+                CallAudioRouteStateMachine.EARPIECE_FORCE_DISABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Disconnect wired headset when call doesn't support earpiece", // name
+                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+                CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
+                ON, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // 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(
+                "Disconnect bluetooth when call does not support earpiece", // name
+                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+                CallAudioState.ROUTE_BLUETOOTH,  // availableRoutes
+                ON, // speakerInteraction
+                OFF, // bluetoothInteraction
+                CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
+                CallAudioState.ROUTE_SPEAKER, // expectedRoute
+                CallAudioState.ROUTE_SPEAKER, // expectedAvailableRoutes
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ).setCallSupportedRoutes(CallAudioState.ROUTE_ALL & ~CallAudioState.ROUTE_EARPIECE));
+
+        return params;
+    }
+
+    private void verifyNewSystemCallAudioState(CallAudioState expectedOldState,
+            CallAudioState expectedNewState) {
+        ArgumentCaptor<CallAudioState> oldStateCaptor = ArgumentCaptor.forClass(
+                CallAudioState.class);
+        ArgumentCaptor<CallAudioState> newStateCaptor1 = ArgumentCaptor.forClass(
+                CallAudioState.class);
+        ArgumentCaptor<CallAudioState> newStateCaptor2 = ArgumentCaptor.forClass(
+                CallAudioState.class);
+        verify(mockCallsManager, timeout(TEST_TIMEOUT).atLeastOnce()).onCallAudioStateChanged(
+                oldStateCaptor.capture(), newStateCaptor1.capture());
+        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));
+    }
+
+    private void verifyNoSystemAudioChanges() {
+        verify(mockBluetoothRouteManager, never()).disconnectBluetoothAudio();
+        verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(nullable(String.class));
+        verify(mockAudioManager, never()).setSpeakerphoneOn(any(Boolean.class));
+        verify(mockCallsManager, never()).onCallAudioStateChanged(any(CallAudioState.class),
+                any(CallAudioState.class));
+        verify(mockConnectionServiceWrapper, never()).onCallAudioStateChanged(
+                any(Call.class), any(CallAudioState.class));
+    }
+
+    private void resetMocks() {
+        reset(mockAudioManager, mockBluetoothRouteManager, mockCallsManager,
+                mockConnectionServiceWrapper);
+        fakeCall = mock(Call.class);
+        when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall);
+        when(fakeCall.getConnectionService()).thenReturn(mockConnectionServiceWrapper);
+        when(fakeCall.isAlive()).thenReturn(true);
+        when(fakeCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_ALL);
+        when(mockCallsManager.getLock()).thenReturn(mLock);
+        doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class),
+                any(CallAudioState.class));
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index 47ac174..2a45649 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -24,6 +24,7 @@
 import android.os.Bundle;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.telecom.TelecomManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.server.telecom.AsyncRingtonePlayer;
@@ -96,6 +97,22 @@
 
     @SmallTest
     @Test
+    public void testNoActionWithExternalRinger() {
+        Bundle externalRingerExtra = new Bundle();
+        externalRingerExtra.putBoolean(TelecomManager.EXTRA_CALL_EXTERNAL_RINGER, true);
+        when(mockCall1.getIntentExtras()).thenReturn(externalRingerExtra);
+        when(mockCall2.getIntentExtras()).thenReturn(externalRingerExtra);
+        // Start call waiting to make sure that it doesn't stop when we start ringing
+        mRingerUnderTest.startCallWaiting(mockCall1);
+        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
+        verify(mockTonePlayer, never()).stopTone();
+        verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class));
+        verify(mockVibrator, never())
+                .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
+    }
+
+    @SmallTest
+    @Test
     public void testNoActionWhenDialerRings() {
         // Start call waiting to make sure that it doesn't stop when we start ringing
         mRingerUnderTest.startCallWaiting(mockCall1);