diff options
7 files changed, 149 insertions, 9 deletions
diff --git a/flags/telecom_callaudioroutestatemachine_flags.aconfig b/flags/telecom_callaudioroutestatemachine_flags.aconfig index 9da150762..7ff144063 100644 --- a/flags/telecom_callaudioroutestatemachine_flags.aconfig +++ b/flags/telecom_callaudioroutestatemachine_flags.aconfig @@ -15,8 +15,15 @@ flag { } flag { + name: "ignore_auto_route_to_watch_device" + namespace: "telecom" + description: "Ignore auto routing to wearable devices." + bug: "294378768" +} + +flag { name: "transit_route_before_audio_disconnect_bt" namespace: "telecom" description: "Fix audio route transition issue on call disconnection when bt audio connected." bug: "306113816" -} +}
\ No newline at end of file diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java index 44f738098..166e8b15d 100644 --- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java +++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java @@ -2022,6 +2022,30 @@ public class CallAudioRouteStateMachine extends StateMachine { return false; } + private boolean isWatchActiveOrOnlyWatchesAvailable() { + if (!mFeatureFlags.ignoreAutoRouteToWatchDevice()) { + return false; + } + + boolean containsWatchDevice = false; + boolean containsNonWatchDevice = false; + Collection<BluetoothDevice> connectedBtDevices = + mBluetoothRouteManager.getConnectedDevices(); + + for (BluetoothDevice connectedDevice: connectedBtDevices) { + if (mBluetoothRouteManager.isWatch(connectedDevice)) { + containsWatchDevice = true; + } else { + containsNonWatchDevice = true; + } + } + + // Don't ignore switch if watch is already the active device. + return containsWatchDevice && !containsNonWatchDevice + && !mBluetoothRouteManager.isWatch( + mBluetoothRouteManager.getBluetoothAudioConnectedDevice()); + } + private int calculateBaselineRouteMessage(boolean isExplicitUserRequest, boolean includeBluetooth) { boolean isSkipEarpiece = false; @@ -2034,7 +2058,7 @@ public class CallAudioRouteStateMachine extends StateMachine { } if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0 && !mHasUserExplicitlyLeftBluetooth - && includeBluetooth) { + && includeBluetooth && !isWatchActiveOrOnlyWatchesAvailable()) { return isExplicitUserRequest ? USER_SWITCH_BLUETOOTH : SWITCH_BLUETOOTH; } else if ((mAvailableRoutes & ROUTE_EARPIECE) != 0 && !isSkipEarpiece) { return isExplicitUserRequest ? USER_SWITCH_EARPIECE : SWITCH_EARPIECE; diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java index 57d7139f2..76777da47 100644 --- a/src/com/android/server/telecom/TelecomSystem.java +++ b/src/com/android/server/telecom/TelecomSystem.java @@ -261,7 +261,8 @@ public class TelecomSystem { mContext.getSystemService(BluetoothManager.class).getAdapter(), communicationDeviceTracker); BluetoothRouteManager bluetoothRouteManager = new BluetoothRouteManager(mContext, mLock, - bluetoothDeviceManager, new Timeouts.Adapter(), communicationDeviceTracker); + bluetoothDeviceManager, new Timeouts.Adapter(), + communicationDeviceTracker, featureFlags); BluetoothStateReceiver bluetoothStateReceiver = new BluetoothStateReceiver( bluetoothDeviceManager, bluetoothRouteManager, communicationDeviceTracker); mContext.registerReceiver(bluetoothStateReceiver, BluetoothStateReceiver.INTENT_FILTER); diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java index b411b25cf..516ca02a7 100644 --- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java +++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java @@ -17,6 +17,7 @@ package com.android.server.telecom.bluetooth; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothHearingAid; @@ -37,6 +38,7 @@ import com.android.internal.util.StateMachine; import com.android.server.telecom.CallAudioCommunicationDeviceTracker; import com.android.server.telecom.TelecomSystem; import com.android.server.telecom.Timeouts; +import com.android.server.telecom.flags.FeatureFlags; import java.util.Collection; import java.util.HashMap; @@ -470,10 +472,12 @@ public class BluetoothRouteManager extends StateMachine { private BluetoothDevice mLeAudioActiveDeviceCache = null; private BluetoothDevice mMostRecentlyReportedActiveDevice = null; private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker; + private FeatureFlags mFeatureFlags; public BluetoothRouteManager(Context context, TelecomSystem.SyncRoot lock, BluetoothDeviceManager deviceManager, Timeouts.Adapter timeoutsAdapter, - CallAudioCommunicationDeviceTracker communicationDeviceTracker) { + CallAudioCommunicationDeviceTracker communicationDeviceTracker, + FeatureFlags featureFlags) { super(BluetoothRouteManager.class.getSimpleName()); mContext = context; mLock = lock; @@ -481,6 +485,7 @@ public class BluetoothRouteManager extends StateMachine { mDeviceManager.setBluetoothRouteManager(this); mTimeoutsAdapter = timeoutsAdapter; mCommunicationDeviceTracker = communicationDeviceTracker; + mFeatureFlags = featureFlags; mAudioOffState = new AudioOffState(); addState(mAudioOffState); @@ -672,6 +677,31 @@ public class BluetoothRouteManager extends StateMachine { return mDeviceManager.getUniqueConnectedDevices(); } + public boolean isWatch(BluetoothDevice device) { + if (device == null) { + Log.i(this, "isWatch: device is null. Returning false"); + return false; + } + + BluetoothClass deviceClass = device.getBluetoothClass(); + if (deviceClass != null && deviceClass.getDeviceClass() + == BluetoothClass.Device.WEARABLE_WRIST_WATCH) { + return true; + } + + // Check metadata + byte[] deviceType = device.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE); + if (deviceType == null) { + return false; + } + String deviceTypeStr = new String(deviceType); + if (deviceTypeStr.equals(BluetoothDevice.DEVICE_TYPE_WATCH)) { + return true; + } + + return false; + } + private String connectBtAudio(String address, boolean switchingBtDevices) { return connectBtAudio(address, 0, switchingBtDevices); } @@ -701,10 +731,19 @@ public class BluetoothRouteManager extends StateMachine { ? address : getActiveDeviceAddress(); if (actualAddress == null) { Log.i(this, "No device specified and BT stack has no active device." - + " Using arbitrary device"); + + " Using arbitrary device - except watch"); if (deviceList.size() > 0) { - actualAddress = deviceList.iterator().next().getAddress(); - } else { + for (BluetoothDevice device : deviceList) { + if (mFeatureFlags.ignoreAutoRouteToWatchDevice() && isWatch(device)) { + Log.i(this, "Skipping a watch device: " + device); + continue; + } + actualAddress = device.getAddress(); + break; + } + } + + if (actualAddress == null) { Log.i(this, "No devices available at all. Not connecting."); return null; } diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java index 8e31f9c45..3ed96a07c 100644 --- a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java +++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java @@ -190,7 +190,7 @@ public class BluetoothRouteManagerTest extends TelecomTestCase { resetMocks(); BluetoothRouteManager sm = new BluetoothRouteManager(mContext, new TelecomSystem.SyncRoot() { }, mDeviceManager, - mTimeoutsAdapter, mCommunicationDeviceTracker); + mTimeoutsAdapter, mCommunicationDeviceTracker, mFeatureFlags); sm.setListener(mListener); sm.setInitialStateForTesting(initialState, initialDevice); waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java index 15a81d4c0..65854af35 100644 --- a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java +++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java @@ -419,7 +419,7 @@ public class BluetoothRouteTransitionTests extends TelecomTestCase { nullable(ContentResolver.class))).thenReturn(100000L); BluetoothRouteManager sm = new BluetoothRouteManager(mContext, new TelecomSystem.SyncRoot() { }, mDeviceManager, - mTimeoutsAdapter, mCommunicationDeviceTracker); + mTimeoutsAdapter, mCommunicationDeviceTracker, mFeatureFlags); sm.setListener(mListener); sm.setInitialStateForTesting(initialState, initialDevice); waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java index dcead7445..253381c0b 100644 --- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java +++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java @@ -98,6 +98,7 @@ public class CallAudioRouteStateMachineTest extends TelecomTestCase { @Mock Call fakeSelfManagedCall; @Mock Call fakeCall; @Mock CallAudioManager mockCallAudioManager; + @Mock BluetoothDevice mockWatchDevice; private CallAudioManager.AudioServiceFactory mAudioServiceFactory; private static final int TEST_TIMEOUT = 500; @@ -140,6 +141,7 @@ public class CallAudioRouteStateMachineTest extends TelecomTestCase { doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class), any(CallAudioState.class)); + when(mFeatureFlags.ignoreAutoRouteToWatchDevice()).thenReturn(false); } @Override @@ -1107,6 +1109,73 @@ public class CallAudioRouteStateMachineTest extends TelecomTestCase { assertEquals(expectedState, stateMachine.getCurrentCallAudioState()); } + @MediumTest + @Test + public void testIgnoreImplicitBTSwitchWhenDeviceIsWatch() { + when(mFeatureFlags.ignoreAutoRouteToWatchDevice()).thenReturn(true); + CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( + mContext, + mockCallsManager, + mockBluetoothRouteManager, + mockWiredHeadsetManager, + mockStatusBarNotifier, + mAudioServiceFactory, + CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, + mThreadHandler.getLooper(), + Runnable::run /** do async stuff sync for test purposes */, + mCommunicationDeviceTracker, + mFeatureFlags); + stateMachine.setCallAudioManager(mockCallAudioManager); + + AudioDeviceInfo headset = mock(AudioDeviceInfo.class); + when(headset.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET); + when(headset.getAddress()).thenReturn(""); + List<AudioDeviceInfo> devices = new ArrayList<>(); + devices.add(headset); + + when(mockAudioManager.getAvailableCommunicationDevices()) + .thenReturn(devices); + when(mockAudioManager.setCommunicationDevice(eq(headset))) + .thenReturn(true); + when(mockAudioManager.getCommunicationDevice()).thenReturn(headset); + + CallAudioState initState = new CallAudioState(false, + CallAudioState.ROUTE_WIRED_HEADSET, CallAudioState.ROUTE_WIRED_HEADSET + | CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH); + stateMachine.initialize(initState); + + // Switch to active + stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS, + CallAudioRouteStateMachine.ACTIVE_FOCUS); + waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); + + // Make sure that we've successfully switched to the active headset. + assertTrue(stateMachine.isInActiveState()); + ArgumentCaptor<AudioDeviceInfo> infoArgumentCaptor = ArgumentCaptor.forClass( + AudioDeviceInfo.class); + verify(mockAudioManager).setCommunicationDevice(infoArgumentCaptor.capture()); + assertEquals(AudioDeviceInfo.TYPE_WIRED_HEADSET, infoArgumentCaptor.getValue().getType()); + + // Set up watch device as only available BT device. + Collection<BluetoothDevice> availableDevices = Collections.singleton(mockWatchDevice); + + when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false); + when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true); + when(mockBluetoothRouteManager.getConnectedDevices()).thenReturn(availableDevices); + when(mockBluetoothRouteManager.isWatch(any(BluetoothDevice.class))).thenReturn(true); + + // Disconnect wired headset to force switch to BT (verify that we ignore the implicit switch + // to BT when the watch is the only connected device and that we move into the next + // available route. + stateMachine.sendMessageWithSessionInfo( + CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET); + waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); + CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE, + CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, + null, availableDevices); + assertEquals(expectedState, stateMachine.getCurrentCallAudioState()); + } + private void initializationTestHelper(CallAudioState expectedState, int earpieceControl) { when(mockWiredHeadsetManager.isPluggedIn()).thenReturn( |