Add ringing state for bluetooth audio route
Previously, Telecom was setting up SCO audio at the same time as the
RING command was being sent to the BT headset. This turns out to have
been a violation of the HFP spec, and ag/1199401 started enforcing it,
which caused calls routed over bluetooth to mishandle audio routing.
This change adds a ringing state to the Bluetooth route in CARSM so that
we can be in a bluetooth route with audio focus but without having set
up SCO audio.
Change-Id: I8aabb983ddd4b546d52b10f78929773b823bfbd0
Fix: 30800730
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 92adca6..c4dbab1 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -213,7 +213,7 @@
mCallAudioManager.stopCallWaiting();
mCallAudioManager.startRinging();
- mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+ mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.RINGING_FOCUS);
}
@Override
@@ -288,7 +288,7 @@
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
mMostRecentMode = AudioManager.MODE_IN_CALL;
- mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+ mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
}
@Override
@@ -350,7 +350,7 @@
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
mMostRecentMode = AudioManager.MODE_IN_COMMUNICATION;
- mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+ mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
}
@Override
@@ -407,7 +407,7 @@
mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
mAudioManager.setMode(mMostRecentMode);
- mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+ mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
}
@Override
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index f4bdc4d..a501dca 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -105,7 +105,8 @@
/** Valid values for mAudioFocusType */
public static final int NO_FOCUS = 1;
- public static final int HAS_FOCUS = 2;
+ public static final int ACTIVE_FOCUS = 2;
+ public static final int RINGING_FOCUS = 3;
private static final SparseArray<String> AUDIO_ROUTE_TO_LOG_EVENT = new SparseArray<String>() {{
put(CallAudioState.ROUTE_BLUETOOTH, Log.Events.AUDIO_ROUTE_BT);
@@ -135,6 +136,8 @@
put(USER_SWITCH_SPEAKER, "USER_SWITCH_SPEAKER");
put(USER_SWITCH_BASELINE_ROUTE, "USER_SWITCH_BASELINE_ROUTE");
+ put(UPDATE_SYSTEM_AUDIO_ROUTE, "UPDATE_SYSTEM_AUDIO_ROUTE");
+
put(MUTE_ON, "MUTE_ON");
put(MUTE_OFF, "MUTE_OFF");
put(TOGGLE_MUTE, "TOGGLE_MUTE");
@@ -148,6 +151,7 @@
private static final String ACTIVE_BLUETOOTH_ROUTE_NAME = "ActiveBluetoothRoute";
private static final String ACTIVE_SPEAKER_ROUTE_NAME = "ActiveSpeakerRoute";
private static final String ACTIVE_HEADSET_ROUTE_NAME = "ActiveHeadsetRoute";
+ private static final String RINGING_BLUETOOTH_ROUTE_NAME = "RingingBluetoothRoute";
private static final String QUIESCENT_EARPIECE_ROUTE_NAME = "QuiescentEarpieceRoute";
private static final String QUIESCENT_BLUETOOTH_ROUTE_NAME = "QuiescentBluetoothRoute";
private static final String QUIESCENT_SPEAKER_ROUTE_NAME = "QuiescentSpeakerRoute";
@@ -160,7 +164,7 @@
if (msg.obj != null && msg.obj instanceof Session) {
String messageCodeName = MESSAGE_CODE_TO_NAME.get(msg.what, "unknown");
Log.continueSession((Session) msg.obj, "CARSM.pM_" + messageCodeName);
- Log.i(this, "Message received: %s=%d", messageCodeName, msg.what);
+ Log.i(this, "Message received: %s=%d, arg1=%d", messageCodeName, msg.what, msg.arg1);
}
}
@@ -217,6 +221,9 @@
case USER_SWITCH_BASELINE_ROUTE:
sendInternalMessage(calculateBaselineRouteMessage(true));
return HANDLED;
+ case SWITCH_FOCUS:
+ mAudioFocusType = msg.arg1;
+ return NOT_HANDLED;
default:
return NOT_HANDLED;
}
@@ -268,7 +275,8 @@
case SWITCH_BLUETOOTH:
case USER_SWITCH_BLUETOOTH:
if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
- transitionTo(mActiveBluetoothRoute);
+ transitionTo(mAudioFocusType == ACTIVE_FOCUS ?
+ mActiveBluetoothRoute : mRingingBluetoothRoute);
} else {
Log.w(this, "Ignoring switch to bluetooth command. Not available.");
}
@@ -350,7 +358,7 @@
transitionTo(mQuiescentSpeakerRoute);
return HANDLED;
case SWITCH_FOCUS:
- if (msg.arg1 == HAS_FOCUS) {
+ if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
transitionTo(mActiveEarpieceRoute);
}
return HANDLED;
@@ -449,7 +457,8 @@
case SWITCH_BLUETOOTH:
case USER_SWITCH_BLUETOOTH:
if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
- transitionTo(mActiveBluetoothRoute);
+ transitionTo(mAudioFocusType == ACTIVE_FOCUS ?
+ mActiveBluetoothRoute : mRingingBluetoothRoute);
} else {
Log.w(this, "Ignoring switch to bluetooth command. Not available.");
}
@@ -527,7 +536,7 @@
transitionTo(mQuiescentSpeakerRoute);
return HANDLED;
case SWITCH_FOCUS:
- if (msg.arg1 == HAS_FOCUS) {
+ if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
transitionTo(mActiveHeadsetRoute);
}
return HANDLED;
@@ -652,6 +661,8 @@
case SWITCH_FOCUS:
if (msg.arg1 == NO_FOCUS) {
reinitialize();
+ } else if (msg.arg1 == RINGING_FOCUS) {
+ transitionTo(mRingingBluetoothRoute);
}
return HANDLED;
case BT_AUDIO_DISCONNECT:
@@ -663,6 +674,87 @@
}
}
+ class RingingBluetoothRoute extends BluetoothRoute {
+ @Override
+ public String getName() {
+ return RINGING_BLUETOOTH_ROUTE_NAME;
+ }
+
+ @Override
+ public boolean isActive() {
+ return false;
+ }
+
+ @Override
+ public void enter() {
+ super.enter();
+ setSpeakerphoneOn(false);
+ // Do not enable SCO audio here, since RING is being sent to the headset.
+ CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH,
+ mAvailableRoutes);
+ setSystemAudioState(newState);
+ updateInternalCallAudioState();
+ }
+
+ @Override
+ public void updateSystemAudioState() {
+ updateInternalCallAudioState();
+ setSystemAudioState(mCurrentCallAudioState);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (super.processMessage(msg) == HANDLED) {
+ return HANDLED;
+ }
+ switch (msg.what) {
+ case USER_SWITCH_EARPIECE:
+ mHasUserExplicitlyLeftBluetooth = true;
+ // fall through
+ case SWITCH_EARPIECE:
+ if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
+ transitionTo(mActiveEarpieceRoute);
+ } else {
+ Log.w(this, "Ignoring switch to earpiece command. Not available.");
+ }
+ return HANDLED;
+ case SWITCH_BLUETOOTH:
+ case USER_SWITCH_BLUETOOTH:
+ // Nothing to do
+ return HANDLED;
+ case USER_SWITCH_HEADSET:
+ mHasUserExplicitlyLeftBluetooth = true;
+ // fall through
+ case SWITCH_HEADSET:
+ if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
+ transitionTo(mActiveHeadsetRoute);
+ } else {
+ Log.w(this, "Ignoring switch to headset command. Not available.");
+ }
+ return HANDLED;
+ case USER_SWITCH_SPEAKER:
+ mHasUserExplicitlyLeftBluetooth = true;
+ // fall through
+ case SWITCH_SPEAKER:
+ transitionTo(mActiveSpeakerRoute);
+ return HANDLED;
+ case SWITCH_FOCUS:
+ if (msg.arg1 == NO_FOCUS) {
+ reinitialize();
+ } else if (msg.arg1 == ACTIVE_FOCUS) {
+ transitionTo(mActiveBluetoothRoute);
+ }
+ return HANDLED;
+ case BT_AUDIO_DISCONNECT:
+ // Ignore BT_AUDIO_DISCONNECT when ringing, since SCO audio should not be
+ // connected.
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
class QuiescentBluetoothRoute extends BluetoothRoute {
@Override
public String getName() {
@@ -717,8 +809,10 @@
transitionTo(mQuiescentSpeakerRoute);
return HANDLED;
case SWITCH_FOCUS:
- if (msg.arg1 == HAS_FOCUS) {
+ if (msg.arg1 == ACTIVE_FOCUS) {
transitionTo(mActiveBluetoothRoute);
+ } else if (msg.arg1 == RINGING_FOCUS) {
+ transitionTo(mRingingBluetoothRoute);
}
return HANDLED;
case BT_AUDIO_DISCONNECT:
@@ -816,7 +910,8 @@
// fall through
case SWITCH_BLUETOOTH:
if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
- transitionTo(mActiveBluetoothRoute);
+ transitionTo(mAudioFocusType == ACTIVE_FOCUS ?
+ mActiveBluetoothRoute : mRingingBluetoothRoute);
} else {
Log.w(this, "Ignoring switch to bluetooth command. Not available.");
}
@@ -906,7 +1001,7 @@
// Nothing to do
return HANDLED;
case SWITCH_FOCUS:
- if (msg.arg1 == HAS_FOCUS) {
+ if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
transitionTo(mActiveSpeakerRoute);
}
return HANDLED;
@@ -962,6 +1057,7 @@
private final ActiveHeadsetRoute mActiveHeadsetRoute = new ActiveHeadsetRoute();
private final ActiveBluetoothRoute mActiveBluetoothRoute = new ActiveBluetoothRoute();
private final ActiveSpeakerRoute mActiveSpeakerRoute = new ActiveSpeakerRoute();
+ private final RingingBluetoothRoute mRingingBluetoothRoute = new RingingBluetoothRoute();
private final QuiescentEarpieceRoute mQuiescentEarpieceRoute = new QuiescentEarpieceRoute();
private final QuiescentHeadsetRoute mQuiescentHeadsetRoute = new QuiescentHeadsetRoute();
private final QuiescentBluetoothRoute mQuiescentBluetoothRoute = new QuiescentBluetoothRoute();
@@ -972,6 +1068,7 @@
* states
*/
private int mAvailableRoutes;
+ private int mAudioFocusType;
private boolean mWasOnSpeaker;
private boolean mIsMuted;
@@ -1006,6 +1103,7 @@
addState(mActiveHeadsetRoute);
addState(mActiveBluetoothRoute);
addState(mActiveSpeakerRoute);
+ addState(mRingingBluetoothRoute);
addState(mQuiescentEarpieceRoute);
addState(mQuiescentHeadsetRoute);
addState(mQuiescentBluetoothRoute);
@@ -1025,6 +1123,7 @@
mStateNameToRouteCode.put(mQuiescentBluetoothRoute.getName(), ROUTE_BLUETOOTH);
mStateNameToRouteCode.put(mQuiescentHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
mStateNameToRouteCode.put(mQuiescentSpeakerRoute.getName(), ROUTE_SPEAKER);
+ mStateNameToRouteCode.put(mRingingBluetoothRoute.getName(), ROUTE_BLUETOOTH);
mStateNameToRouteCode.put(mActiveEarpieceRoute.getName(), ROUTE_EARPIECE);
mStateNameToRouteCode.put(mActiveBluetoothRoute.getName(), ROUTE_BLUETOOTH);
mStateNameToRouteCode.put(mActiveHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index c4526e4..615fd0e 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -50,6 +50,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -177,7 +178,7 @@
stateMachine.initialize(initState);
stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
- CallAudioRouteStateMachine.HAS_FOCUS);
+ CallAudioRouteStateMachine.ACTIVE_FOCUS);
stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET);
CallAudioState expectedMiddleState = new CallAudioState(false,
CallAudioState.ROUTE_WIRED_HEADSET,
@@ -209,7 +210,7 @@
stateMachine.initialize(initState);
stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
- CallAudioRouteStateMachine.HAS_FOCUS);
+ CallAudioRouteStateMachine.ACTIVE_FOCUS);
stateMachine.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE);
CallAudioState expectedEndState = new CallAudioState(false,
@@ -230,6 +231,72 @@
assertEquals(expectedEndState, stateMachine.getCurrentCallAudioState());
}
+ @MediumTest
+ public void testBluetoothRinging() {
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory,
+ true);
+
+ when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
+ when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(true);
+ when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
+ CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
+ stateMachine.initialize(initState);
+
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+ CallAudioRouteStateMachine.RINGING_FOCUS);
+ waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+
+ verify(mockBluetoothManager, never()).connectBluetoothAudio();
+
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+ CallAudioRouteStateMachine.ACTIVE_FOCUS);
+ waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+ verify(mockBluetoothManager, times(1)).connectBluetoothAudio();
+ }
+
+ @MediumTest
+ public void testConnectBluetoothDuringRinging() {
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory,
+ true);
+
+ when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
+ when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(false);
+ when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
+ CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+ CallAudioState.ROUTE_EARPIECE);
+ stateMachine.initialize(initState);
+
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+ CallAudioRouteStateMachine.RINGING_FOCUS);
+ when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(true);
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.CONNECT_BLUETOOTH);
+ waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+
+ verify(mockBluetoothManager, never()).connectBluetoothAudio();
+ CallAudioState expectedEndState = new CallAudioState(false,
+ CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
+ verifyNewSystemCallAudioState(initState, expectedEndState);
+
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+ CallAudioRouteStateMachine.ACTIVE_FOCUS);
+ waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+ verify(mockBluetoothManager, times(1)).connectBluetoothAudio();
+ }
+
@SmallTest
public void testInitializationWithEarpieceNoHeadsetNoBluetooth() {
CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
@@ -677,7 +744,7 @@
stateMachine.initialize(initState);
// Make the state machine have focus so that we actually do something
stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
- CallAudioRouteStateMachine.HAS_FOCUS);
+ CallAudioRouteStateMachine.ACTIVE_FOCUS);
stateMachine.sendMessageWithSessionInfo(params.action);
waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);