Merge cherrypicks of ['googleplex-android-review.googlesource.com/32668895', 'googleplex-android-review.googlesource.com/32709930'] into 25Q2-release.
Change-Id: I66387c68951c4bf25df96716bab4565ab79ee6f5
diff --git a/flags/telecom_callaudioroutestatemachine_flags.aconfig b/flags/telecom_callaudioroutestatemachine_flags.aconfig
index 96a5e38..76a7f74 100644
--- a/flags/telecom_callaudioroutestatemachine_flags.aconfig
+++ b/flags/telecom_callaudioroutestatemachine_flags.aconfig
@@ -163,17 +163,6 @@
}
}
-# OWNER=pmadapurmath TARGET=25Q2
-flag {
- name: "default_speaker_on_wired_headset_disconnect"
- namespace: "telecom"
- description: "Maybe route back into speaker (if it was the previous route) when a wired headset disconnects"
- bug: "376364368"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
# OWNER=tgunn TARGET=25Q2
flag {
name: "bus_device_is_a_speaker"
diff --git a/src/com/android/server/telecom/AudioRoute.java b/src/com/android/server/telecom/AudioRoute.java
index bcc0b59..2b03182 100644
--- a/src/com/android/server/telecom/AudioRoute.java
+++ b/src/com/android/server/telecom/AudioRoute.java
@@ -256,26 +256,26 @@
// Invoked when entered pending route whose dest route is this route
void onDestRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute,
BluetoothDevice device, AudioManager audioManager,
- BluetoothRouteManager bluetoothRouteManager, boolean isScoAudioConnected) {
- Log.i(this, "onDestRouteAsPendingRoute: active (%b), type (%s)", active,
- DEVICE_TYPE_STRINGS.get(mAudioRouteType));
+ BluetoothRouteManager bluetoothRouteManager, boolean isScoAlreadyConnected) {
+ Log.i(this, "onDestRouteAsPendingRoute: active (%b), type (%s), isScoAlreadyConnected(%s)",
+ active, DEVICE_TYPE_STRINGS.get(mAudioRouteType), isScoAlreadyConnected);
if (pendingAudioRoute.isActive() && !active) {
clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager);
} else if (active) {
// Handle BT routing case.
if (BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType)) {
+ // Check if the communication device was set for the device, even if
+ // BluetoothHeadset#connectAudio reports that the SCO connection wasn't
+ // successfully established.
boolean connectedBtAudio = connectBtAudio(pendingAudioRoute, device,
- audioManager, bluetoothRouteManager);
+ audioManager, bluetoothRouteManager, isScoAlreadyConnected);
// Special handling for SCO case.
if (!mIsScoManagedByAudio && mAudioRouteType == TYPE_BLUETOOTH_SCO) {
// Set whether the dest route is for the watch
mIsDestRouteForWatch = bluetoothRouteManager.isWatch(device);
- // Check if the communication device was set for the device, even if
- // BluetoothHeadset#connectAudio reports that the SCO connection wasn't
- // successfully established.
- if (connectedBtAudio || isScoAudioConnected) {
+ if (connectedBtAudio || isScoAlreadyConnected) {
pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType);
- if (!isScoAudioConnected) {
+ if (!isScoAlreadyConnected) {
pendingAudioRoute.addMessage(BT_AUDIO_CONNECTED, mBluetoothAddress);
}
} else {
@@ -311,7 +311,8 @@
result = audioManager.setCommunicationDevice(mInfo);
if (result) {
pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType);
- if (mAudioRouteType == TYPE_BLUETOOTH_SCO && !isScoAudioConnected
+ if (mAudioRouteType == TYPE_BLUETOOTH_SCO
+ && !isScoAlreadyConnected
&& mIsScoManagedByAudio) {
pendingAudioRoute.addMessage(BT_AUDIO_CONNECTED, mBluetoothAddress);
}
@@ -341,10 +342,12 @@
* @param bluetoothRouteManager The BT route manager.
*/
void onOrigRouteAsPendingRoute(boolean wasActive, PendingAudioRoute pendingAudioRoute,
- AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) {
- Log.i(this, "onOrigRouteAsPendingRoute: wasActive (%b), type (%s), pending(%s)", wasActive,
- DEVICE_TYPE_STRINGS.get(mAudioRouteType), pendingAudioRoute);
- if (wasActive) {
+ AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager,
+ boolean isScoAlreadyConnected) {
+ Log.i(this, "onOrigRouteAsPendingRoute: wasActive (%b), type (%s), pending(%s),"
+ + "isScoAlreadyConnected(%s)", wasActive, DEVICE_TYPE_STRINGS.get(mAudioRouteType),
+ pendingAudioRoute, isScoAlreadyConnected);
+ if (wasActive && !isScoAlreadyConnected) {
int result = clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager,
audioManager);
if (mAudioRouteType == TYPE_SPEAKER) {
@@ -395,11 +398,12 @@
}
private boolean connectBtAudio(PendingAudioRoute pendingAudioRoute, BluetoothDevice device,
- AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) {
+ AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager,
+ boolean isScoAlreadyConnected) {
// Ensure that if another BT device was set, it is disconnected before connecting
// the new one.
AudioRoute currentRoute = pendingAudioRoute.getOrigRoute();
- if (currentRoute.getBluetoothAddress() != null &&
+ if (!isScoAlreadyConnected && currentRoute.getBluetoothAddress() != null &&
!currentRoute.getBluetoothAddress().equals(device.getAddress())) {
clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager);
}
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index ce06d55..c9e0ab3 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -118,7 +118,7 @@
private FeatureFlags mFeatureFlags;
private int mFocusType;
private int mCallSupportedRouteMask = -1;
- private boolean mIsScoAudioConnected;
+ private BluetoothDevice mScoAudioConnectedDevice;
private boolean mAvailableRoutesUpdated;
private boolean mUsePreferredDeviceStrategy;
private AudioDeviceInfo mCurrentCommunicationDevice;
@@ -188,6 +188,7 @@
private boolean mIsMute;
private boolean mIsPending;
private boolean mIsActive;
+ private boolean mWasOnSpeaker;
private final TelecomMetricsController mMetricsController;
public CallAudioRouteController(
@@ -208,8 +209,9 @@
mFeatureFlags = featureFlags;
mMetricsController = metricsController;
mFocusType = NO_FOCUS;
- mIsScoAudioConnected = false;
+ mScoAudioConnectedDevice = null;
mUsePreferredDeviceStrategy = true;
+ mWasOnSpeaker = false;
setCurrentCommunicationDevice(null);
mTelecomLock = callsManager.getLock();
@@ -306,16 +308,16 @@
break;
case SWITCH_EARPIECE:
case USER_SWITCH_EARPIECE:
- handleSwitchEarpiece();
+ handleSwitchEarpiece(msg.what == USER_SWITCH_EARPIECE);
break;
case SWITCH_BLUETOOTH:
case USER_SWITCH_BLUETOOTH:
address = (String) ((SomeArgs) msg.obj).arg2;
- handleSwitchBluetooth(address);
+ handleSwitchBluetooth(address, msg.what == USER_SWITCH_BLUETOOTH);
break;
case SWITCH_HEADSET:
case USER_SWITCH_HEADSET:
- handleSwitchHeadset();
+ handleSwitchHeadset(msg.what == USER_SWITCH_HEADSET);
break;
case SWITCH_SPEAKER:
case USER_SWITCH_SPEAKER:
@@ -568,7 +570,7 @@
return mIsPending;
}
- private void routeTo(boolean active, AudioRoute destRoute) {
+ private void routeTo(boolean isDestRouteActive, AudioRoute destRoute) {
if (destRoute == null || (!destRoute.equals(mStreamingRoute)
&& !getCallSupportedRoutes().contains(destRoute))) {
Log.i(this, "Ignore routing to unavailable route: %s", destRoute);
@@ -578,42 +580,53 @@
}
return;
}
+ // If another BT device connects during RINGING_FOCUS, in-band ringing will be disabled by
+ // default. In this case, we should adjust the active routing value so that we don't try
+ // to connect to the BT device as it will fail.
+ isDestRouteActive = maybeAdjustActiveRouting(destRoute, isDestRouteActive);
+ // It's possible that there are multiple HFP devices connected and if we receive SCO audio
+ // connected for the destination route's BT device, then we shouldn't disconnect SCO when
+ // clearing the communication device for the original route if it was also a HFP device.
+ boolean isScoDeviceAlreadyConnected = mScoAudioConnectedDevice != null
+ && Objects.equals(mScoAudioConnectedDevice, mBluetoothRoutes.get(destRoute));
if (mIsPending) {
- if (destRoute.equals(mPendingAudioRoute.getDestRoute()) && (mIsActive == active)) {
+ if (destRoute.equals(mPendingAudioRoute.getDestRoute())
+ && (mIsActive == isDestRouteActive)) {
return;
}
Log.i(this, "Override current pending route destination from %s(active=%b) to "
+ "%s(active=%b)",
- mPendingAudioRoute.getDestRoute(), mIsActive, destRoute, active);
+ mPendingAudioRoute.getDestRoute(), mIsActive, destRoute, isDestRouteActive);
// Ensure we don't keep waiting for SPEAKER_ON if dest route gets overridden.
- if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue() && active
+ if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue() && isDestRouteActive
&& mPendingAudioRoute.getDestRoute().getType() == TYPE_SPEAKER) {
mPendingAudioRoute.clearPendingMessage(new Pair<>(SPEAKER_ON, null));
}
// override pending route while keep waiting for still pending messages for the
// previous pending route
mPendingAudioRoute.setOrigRoute(mIsActive /* origin */,
- mPendingAudioRoute.getDestRoute(), active /* dest */);
+ mPendingAudioRoute.getDestRoute(), isDestRouteActive /* dest */,
+ isScoDeviceAlreadyConnected);
} else {
- if (mCurrentRoute.equals(destRoute) && (mIsActive == active)) {
+ if (mCurrentRoute.equals(destRoute) && (mIsActive == isDestRouteActive)) {
return;
}
Log.i(this, "Enter pending route, orig%s(active=%b), dest%s(active=%b)", mCurrentRoute,
- mIsActive, destRoute, active);
+ mIsActive, destRoute, isDestRouteActive);
// route to pending route
if (getCallSupportedRoutes().contains(mCurrentRoute)) {
mPendingAudioRoute.setOrigRoute(mIsActive /* origin */, mCurrentRoute,
- active /* dest */);
+ isDestRouteActive /* dest */, isScoDeviceAlreadyConnected);
} else {
// Avoid waiting for pending messages for an unavailable route
mPendingAudioRoute.setOrigRoute(mIsActive /* origin */, DUMMY_ROUTE,
- active /* dest */);
+ isDestRouteActive /* dest */, isScoDeviceAlreadyConnected);
}
mIsPending = true;
}
- mPendingAudioRoute.setDestRoute(active, destRoute, mBluetoothRoutes.get(destRoute),
- mIsScoAudioConnected);
- mIsActive = active;
+ mPendingAudioRoute.setDestRoute(isDestRouteActive, destRoute,
+ mBluetoothRoutes.get(destRoute), isScoDeviceAlreadyConnected);
+ mIsActive = isDestRouteActive;
mPendingAudioRoute.evaluatePendingState();
if (mFeatureFlags.telecomMetricsSupport()) {
mMetricsController.getAudioRouteStats().onRouteEnter(mPendingAudioRoute);
@@ -678,10 +691,7 @@
// Preserve speaker routing if it was the last audio routing path when the wired headset
// disconnects. Ignore this special cased routing when the route isn't active
// (in other words, when we're not in a call).
- AudioRoute route = mFeatureFlags.defaultSpeakerOnWiredHeadsetDisconnect()
- && mIsActive && mPendingAudioRoute.getOrigRoute() != null
- && mPendingAudioRoute.getOrigRoute().getType() == TYPE_SPEAKER
- && mSpeakerDockRoute != null
+ AudioRoute route = mWasOnSpeaker && mIsActive && mSpeakerDockRoute != null
&& mSpeakerDockRoute.getType() == AudioRoute.TYPE_SPEAKER
? mSpeakerDockRoute : getBaseRoute(true, null);
routeTo(mIsActive, route);
@@ -894,6 +904,7 @@
}
}
if ((mIsPending && pendingRouteNeedsUpdate) || (!mIsPending && currentRouteNeedsUpdate)) {
+ maybeDisableWasOnSpeaker(true);
// Fallback to an available route excluding the previously active device.
routeTo(mIsActive, getBaseRoute(true, previouslyActiveDeviceAddress));
}
@@ -928,6 +939,7 @@
mFocusType = focus;
switch (focus) {
case NO_FOCUS -> {
+ mWasOnSpeaker = false;
// Notify the CallAudioModeStateMachine that audio operations are complete so
// that we can relinquish audio focus.
mCallAudioManager.notifyAudioOperationsComplete();
@@ -993,16 +1005,17 @@
}
}
- public void handleSwitchEarpiece() {
+ public void handleSwitchEarpiece(boolean isUserRequest) {
AudioRoute earpieceRoute = mTypeRoutes.get(AudioRoute.TYPE_EARPIECE);
if (earpieceRoute != null && getCallSupportedRoutes().contains(earpieceRoute)) {
+ maybeDisableWasOnSpeaker(isUserRequest);
routeTo(mIsActive, earpieceRoute);
} else {
Log.i(this, "ignore switch earpiece request");
}
}
- private void handleSwitchBluetooth(String address) {
+ private void handleSwitchBluetooth(String address, boolean isUserRequest) {
Log.i(this, "handle switch to bluetooth with address %s", address);
AudioRoute bluetoothRoute = null;
BluetoothDevice bluetoothDevice = null;
@@ -1020,6 +1033,7 @@
}
if (bluetoothRoute != null && bluetoothDevice != null) {
+ maybeDisableWasOnSpeaker(isUserRequest);
if (mFocusType == RINGING_FOCUS) {
routeTo(mBluetoothRouteManager
.isInbandRingEnabled(bluetoothRoute.getType(), bluetoothDevice)
@@ -1051,9 +1065,10 @@
}
}
- private void handleSwitchHeadset() {
+ private void handleSwitchHeadset(boolean isUserRequest) {
AudioRoute headsetRoute = mTypeRoutes.get(AudioRoute.TYPE_WIRED);
if (headsetRoute != null && getCallSupportedRoutes().contains(headsetRoute)) {
+ maybeDisableWasOnSpeaker(isUserRequest);
routeTo(mIsActive, headsetRoute);
} else {
Log.i(this, "ignore switch headset request");
@@ -1073,10 +1088,10 @@
String btAddressToExclude) {
Log.i(this, "handleSwitchBaselineRoute: includeBluetooth: %b, "
+ "btAddressToExclude: %s", includeBluetooth, btAddressToExclude);
+ AudioRoute pendingDestRoute = mPendingAudioRoute.getDestRoute();
boolean areExcludedBtAndDestBtSame = btAddressToExclude != null
- && mPendingAudioRoute.getDestRoute() != null
- && Objects.equals(btAddressToExclude, mPendingAudioRoute.getDestRoute()
- .getBluetoothAddress());
+ && pendingDestRoute != null
+ && Objects.equals(btAddressToExclude, pendingDestRoute.getBluetoothAddress());
Pair<Integer, String> btDevicePendingMsg =
new Pair<>(BT_AUDIO_CONNECTED, btAddressToExclude);
@@ -1084,8 +1099,8 @@
// we know that the device has reconnected or is in the middle of connecting. Ignore routing
// out of this BT device.
boolean isExcludedDeviceConnectingOrConnected = areExcludedBtAndDestBtSame
- && (mIsScoAudioConnected || mPendingAudioRoute.getPendingMessages()
- .contains(btDevicePendingMsg));
+ && (Objects.equals(mBluetoothRoutes.get(pendingDestRoute), mScoAudioConnectedDevice)
+ || mPendingAudioRoute.getPendingMessages().contains(btDevicePendingMsg));
// Check if the pending audio route or current route is already different from the route
// including the BT device that should be excluded from route selection.
boolean isCurrentOrDestRouteDifferent = btAddressToExclude != null
@@ -1103,6 +1118,7 @@
return;
}
}
+ maybeDisableWasOnSpeaker(isExplicitUserRequest);
routeTo(mIsActive, calculateBaselineRoute(isExplicitUserRequest, includeBluetooth,
btAddressToExclude));
}
@@ -1157,6 +1173,11 @@
mPendingAudioRoute.clearPendingMessages();
onCurrentRouteChanged();
if (mIsActive) {
+ // Only set mWasOnSpeaker if the routing was active. We don't want to consider this
+ // selection outside of a call.
+ if (mCurrentRoute.getType() == TYPE_SPEAKER) {
+ mWasOnSpeaker = true;
+ }
// Reinitialize the audio ops complete latch since the routing went active. We
// should always expect operations to complete after this point.
if (mAudioOperationsCompleteLatch.getCount() == 0) {
@@ -1649,8 +1670,9 @@
mIsPending = isPending;
}
- public void setIsScoAudioConnected(boolean value) {
- mIsScoAudioConnected = value;
+ @VisibleForTesting
+ public void setScoAudioConnectedDevice(BluetoothDevice device) {
+ mScoAudioConnectedDevice = device;
}
private void clearRingingBluetoothAddress() {
@@ -1757,4 +1779,37 @@
return mCurrentCommunicationDevice;
}
}
+
+ private void maybeDisableWasOnSpeaker(boolean isUserRequest) {
+ if (isUserRequest) {
+ mWasOnSpeaker = false;
+ }
+ }
+
+ /*
+ * Adjusts routing to go inactive if we're active in the case that we're processing
+ * RINGING_FOCUS and another BT headset is connected which causes in-band ringing to get
+ * disabled. If we stay in active routing, Telecom will send requests to connect to these BT
+ * devices while the call is ringing and each of these requests will fail at the BT stack side.
+ * By default, in-band ringtone is disabled when more than one BT device is paired. Instead,
+ * ringtone is played using the headset's default ringtone.
+ */
+ private boolean maybeAdjustActiveRouting(AudioRoute destRoute, boolean isDestRouteActive) {
+ BluetoothDevice device = mBluetoothRoutes.get(destRoute);
+ // If routing is active and in-band ringing is disabled while the call is ringing, move to
+ // inactive routing.
+ if (isDestRouteActive && mFocusType == RINGING_FOCUS && device != null
+ && !mBluetoothRouteManager.isInbandRingEnabled(destRoute.getType(), device)) {
+ return false;
+ }
+ else if (!isDestRouteActive && mFocusType == RINGING_FOCUS && (device == null
+ || mBluetoothRouteManager.isInbandRingEnabled(destRoute.getType(), device))) {
+ // If the routing is inactive while the call is ringing and we re-evaluate this to find
+ // that we're routing to a non-BT device or a BT device that does support in-band
+ // ringing, then re-enable active routing (i.e. second HFP headset is disconnected
+ // while call is ringing).
+ return true;
+ }
+ return isDestRouteActive;
+ }
}
diff --git a/src/com/android/server/telecom/PendingAudioRoute.java b/src/com/android/server/telecom/PendingAudioRoute.java
index dde1d8d..37c70ad 100644
--- a/src/com/android/server/telecom/PendingAudioRoute.java
+++ b/src/com/android/server/telecom/PendingAudioRoute.java
@@ -83,10 +83,11 @@
* @param origRoute The origin.
* @param isDestActive Whether the destination will be active.
*/
- void setOrigRoute(boolean isOriginActive, AudioRoute origRoute, boolean isDestActive) {
+ void setOrigRoute(boolean isOriginActive, AudioRoute origRoute, boolean isDestActive,
+ boolean isScoAlreadyConnected) {
mActive = isDestActive;
origRoute.onOrigRouteAsPendingRoute(isOriginActive, this, mAudioManager,
- mBluetoothRouteManager);
+ mBluetoothRouteManager, isScoAlreadyConnected);
mOrigRoute = origRoute;
}
@@ -95,9 +96,9 @@
}
void setDestRoute(boolean active, AudioRoute destRoute, BluetoothDevice device,
- boolean isScoAudioConnected) {
+ boolean isScoAlreadyConnected) {
destRoute.onDestRouteAsPendingRoute(active, this, device,
- mAudioManager, mBluetoothRouteManager, isScoAudioConnected);
+ mAudioManager, mBluetoothRouteManager, isScoAlreadyConnected);
mActive = active;
mDestRoute = destRoute;
}
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index 1330be4..0478fdc 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -52,6 +52,8 @@
import com.android.server.telecom.CallAudioRouteController;
import com.android.server.telecom.flags.FeatureFlags;
+import java.util.Objects;
+
public class BluetoothStateReceiver extends BroadcastReceiver {
private static final String LOG_TAG = BluetoothStateReceiver.class.getSimpleName();
public static final IntentFilter INTENT_FILTER;
@@ -124,16 +126,17 @@
if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
CallAudioRouteController audioRouteController =
(CallAudioRouteController) mCallAudioRouteAdapter;
- audioRouteController.setIsScoAudioConnected(true);
- if (audioRouteController.isPending()) {
+ audioRouteController.setScoAudioConnectedDevice(device);
+ AudioRoute btRoute = audioRouteController.getBluetoothRoute(
+ AudioRoute.TYPE_BLUETOOTH_SCO, device.getAddress());
+ if (audioRouteController.isPending() && Objects.equals(audioRouteController
+ .getPendingAudioRoute().getDestRoute(), btRoute)) {
mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0,
device);
} else {
// It's possible that the initial BT connection fails but BT_AUDIO_CONNECTED
// is sent later, indicating that SCO audio is on. We should route
// appropriately in order for the UI to reflect this state.
- AudioRoute btRoute = audioRouteController.getBluetoothRoute(
- AudioRoute.TYPE_BLUETOOTH_SCO, device.getAddress());
if (btRoute != null) {
audioRouteController.getPendingAudioRoute().overrideDestRoute(btRoute);
audioRouteController.overrideIsPending(true);
@@ -155,7 +158,7 @@
if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
CallAudioRouteController audioRouteController =
(CallAudioRouteController) mCallAudioRouteAdapter;
- audioRouteController.setIsScoAudioConnected(false);
+ audioRouteController.setScoAudioConnectedDevice(null);
if (audioRouteController.isPending()) {
mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0,
device);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
index 1b1ca56..f6f1686 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
@@ -530,23 +530,35 @@
@SmallTest
@Test
public void testDefaultSpeakerOnWiredHeadsetDisconnect() {
- when(mFeatureFlags.defaultSpeakerOnWiredHeadsetDisconnect()).thenReturn(true);
mController.initialize();
mController.setActive(true);
verifyMaybeDefaultSpeakerOnDisconnectWiredHeadset(
- CallAudioState.ROUTE_SPEAKER /* expectedAudioType */);
+ CallAudioState.ROUTE_SPEAKER /* expectedAudioType */,
+ false /* includeUserSwitch */);
}
@SmallTest
@Test
public void testIgnoreDefaultSpeakerOnWiredHeadsetDisconnect() {
- when(mFeatureFlags.defaultSpeakerOnWiredHeadsetDisconnect()).thenReturn(true);
// Note here that the routing isn't active to represent that we're not in a call. If a wired
// headset is disconnected and the last route was speaker, we shouldn't switch back to
// speaker when we're not in a call.
mController.initialize();
verifyMaybeDefaultSpeakerOnDisconnectWiredHeadset(
- CallAudioState.ROUTE_EARPIECE /* expectedAudioType */);
+ CallAudioState.ROUTE_EARPIECE /* expectedAudioType */,
+ false /* includeUserSwitch */);
+ }
+
+ @SmallTest
+ @Test
+ public void testIgnoreDefaultSpeakerOnWiredHeadsetDisconnect_UserSwitchesOutOfSpeaker() {
+ mController.initialize();
+ mController.setActive(true);
+ // Verify that when we turn speaker on/off when a wired headset is plugged in and after the
+ // headset is disconnected that we don't default audio routing back to speaker.
+ verifyMaybeDefaultSpeakerOnDisconnectWiredHeadset(
+ CallAudioState.ROUTE_EARPIECE /* expectedAudioType */,
+ true /* includeUserSwitch */);
}
@SmallTest
@@ -1104,7 +1116,7 @@
mController.sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE,
INCLUDE_BLUETOOTH_IN_BASELINE, BLUETOOTH_DEVICE_1.getAddress());
// Process BT_AUDIO_CONNECTED from connecting to BT device in active focus request.
- mController.setIsScoAudioConnected(true);
+ mController.setScoAudioConnectedDevice(BLUETOOTH_DEVICE_1);
mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0, BLUETOOTH_DEVICE_1);
// Verify SCO not disconnected and route stays on connected BT device.
verify(mBluetoothDeviceManager, timeout(TEST_TIMEOUT).times(0)).disconnectSco();
@@ -1407,6 +1419,81 @@
any(CallAudioState.class), eq(expectedState));
}
+ @Test
+ @SmallTest
+ public void testRouteToInactiveWhenInbandRingingDisabledDuringRinging() {
+ when(mBluetoothRouteManager.isInbandRingEnabled(eq(AudioRoute.TYPE_BLUETOOTH_SCO),
+ eq(BLUETOOTH_DEVICE_1))).thenReturn(true);
+ verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_SCO);
+ mController.sendMessageWithSessionInfo(SWITCH_FOCUS, RINGING_FOCUS, 0);
+ assertTrue(mController.isActive());
+
+ // Connect another HFP device while call is still ringing
+ BluetoothDevice scoDevice =
+ BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:03");
+ BLUETOOTH_DEVICES.add(scoDevice);
+
+ // Add SCO device.
+ mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
+ scoDevice);
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ when(mBluetoothRouteManager.isInbandRingEnabled(eq(AudioRoute.TYPE_BLUETOOTH_SCO),
+ any(BluetoothDevice.class))).thenReturn(false);
+ // Emulate second device becoming active and first device getting disconnected as in-band
+ // ringing is disabled.
+ mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+ AudioRoute.TYPE_BLUETOOTH_SCO, scoDevice.getAddress());
+ mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0,
+ BLUETOOTH_DEVICE_1);
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_SPEAKER, scoDevice, BLUETOOTH_DEVICES);
+ // Verify routing goes to the new HFP device but that the routing is now inactive.
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+ assertFalse(mController.isActive());
+ }
+
+ @Test
+ @SmallTest
+ public void testSkipConnectBluetoothWhenScoAudioAlreadyConnected() {
+ verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_SCO);
+ // Connect another HFP device while call is still ringing
+ BluetoothDevice scoDevice =
+ BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:03");
+ BLUETOOTH_DEVICES.add(scoDevice);
+
+ // Add SCO device.
+ mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
+ scoDevice);
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ // Emulate scenario where BT stack signals SCO audio connected for the second HFP device
+ // before Telecom finishes processing the route change to this device. We should ensure
+ // that we don't accidentally disconnect SCO in this case (thinking that we're disconnecting
+ // the first HFP device).
+ mController.setScoAudioConnectedDevice(scoDevice);
+ mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+ AudioRoute.TYPE_BLUETOOTH_SCO, scoDevice.getAddress());
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_SPEAKER, scoDevice, BLUETOOTH_DEVICES);
+ // Verify routing goes to the new HFP device and we never disconnect SCO when clearing the
+ // original pending route.
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+ verify(mBluetoothDeviceManager, timeout(TEST_TIMEOUT).times(0)).disconnectSco();
+ }
+
private void verifyConnectBluetoothDevice(int audioType) {
mController.initialize();
mController.setActive(true);
@@ -1473,7 +1560,7 @@
any(CallAudioState.class), eq(expectedState));
}
- private void verifyMaybeDefaultSpeakerOnDisconnectWiredHeadset(int expectedAudioType) {
+ private void verifyMaybeDefaultSpeakerOnDisconnectWiredHeadset(int expectedAudioType, boolean includeUserSwitch) {
// Ensure audio is routed to speaker initially
mController.sendMessageWithSessionInfo(SPEAKER_ON);
CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
@@ -1491,6 +1578,30 @@
verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
any(CallAudioState.class), eq(expectedState));
+ // Emulate scenario with user turning on/off speaker. This is to verify that when the user
+ // switches off speaker that we don't auto route back to speaker when the wired headset
+ // disconnects.
+ if (includeUserSwitch) {
+ // Verify speaker turned on from USER_SWITCH_SPEAKER
+ mController.sendMessageWithSessionInfo(USER_SWITCH_SPEAKER);
+ mController.sendMessageWithSessionInfo(SPEAKER_ON);
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER, null,
+ new HashSet<>());
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ // Verify speaker turned off from turning off speaker
+ mController.sendMessageWithSessionInfo(USER_SWITCH_BASELINE_ROUTE,
+ INCLUDE_BLUETOOTH_IN_BASELINE);
+ mController.sendMessageWithSessionInfo(SPEAKER_OFF);
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_WIRED_HEADSET,
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER, null,
+ new HashSet<>());
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+ }
+
// Verify that we route back into speaker once the wired headset disconnects
mController.sendMessageWithSessionInfo(DISCONNECT_WIRED_HEADSET);
expectedState = new CallAudioState(false, expectedAudioType,