From c61fb84113654374b5869399a7b3de7b0ddba911 Mon Sep 17 00:00:00 2001 From: jasonwshsu Date: Mon, 15 May 2023 05:30:49 +0800 Subject: [PATCH] Fix Audio output sometimes fails to apply when bluetooth re-enabled Root Cause: AudioManager#getDevices(AudioManager.GET_DEVICES_OUTPUTS) is not guarantee to return the expected device after receiving profile STATE_CONNECTING broadcast. It will cause HearingAidAudioRoutingHelper#getMatchedHearingDeviceAttributes() can not find the expected output hearing device. Solution: Listen to AudioDeviceCallback#onAudioDevicesAdded(). It was listened by HearingAidService and sent broadcast intent BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED. Use SettingsLib onActiveDeviceChanged() to handle it. Bug: 282547878 Test: make RunSettingsLibRoboTests ROBOTEST_FILTER="(BluetoothEventManagerTest|CachedBluetoothDeviceManagerTest|HearingAidDeviceManagerTest)" Change-Id: I47a500e855b94becf979392d67c317533e0ce2a9 --- .../bluetooth/BluetoothEventManager.java | 1 + .../CachedBluetoothDeviceManager.java | 7 ++++++ .../bluetooth/HearingAidDeviceManager.java | 17 +++++++------ .../bluetooth/BluetoothEventManagerTest.java | 16 ++++++++++++ .../CachedBluetoothDeviceManagerTest.java | 18 +++++++++++++ .../HearingAidDeviceManagerTest.java | 25 +++++-------------- 6 files changed, 57 insertions(+), 27 deletions(-) diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 4d6dd4b538ccc..f5bacb62b6b28 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -250,6 +250,7 @@ public class BluetoothEventManager { } } cachedDevice.onActiveDeviceChanged(isActive, bluetoothProfile); + mDeviceManager.onActiveDeviceChanged(cachedDevice); } for (BluetoothCallback callback : mCallbacks) { callback.onActiveDeviceChanged(activeDevice, bluetoothProfile); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java index 7b4c86207a2a3..e31af9de8090d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java @@ -325,6 +325,13 @@ public class CachedBluetoothDeviceManager { return false; } + /** Handles when the device been set as active/inactive. */ + public synchronized void onActiveDeviceChanged(CachedBluetoothDevice cachedBluetoothDevice) { + if (cachedBluetoothDevice.isHearingAidDevice()) { + mHearingAidDeviceManager.onActiveDeviceChanged(cachedBluetoothDevice); + } + } + public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) { device.setGroupId(BluetoothCsipSetCoordinator.GROUP_ID_INVALID); CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(device); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java index 4354e0c6e9521..e5e57824f6ef0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java @@ -224,15 +224,9 @@ public class HearingAidDeviceManager { // It is necessary to do remove and add for updating the mapping on // preference and device mBtManager.getEventManager().dispatchDeviceAdded(mainDevice); - // Only need to set first device of a set. AudioDeviceInfo for - // GET_DEVICES_OUTPUTS will not change device. - setAudioRoutingConfig(cachedDevice); } return true; } - // Only need to set first device of a set. AudioDeviceInfo for GET_DEVICES_OUTPUTS - // will not change device. - setAudioRoutingConfig(cachedDevice); break; case BluetoothProfile.STATE_DISCONNECTED: mainDevice = findMainDevice(cachedDevice); @@ -258,13 +252,20 @@ public class HearingAidDeviceManager { return true; } - // Only need to clear when last device of a set get disconnected - clearAudioRoutingConfig(); break; } return false; } + void onActiveDeviceChanged(CachedBluetoothDevice device) { + if (device.isActiveDevice(BluetoothProfile.HEARING_AID) || device.isActiveDevice( + BluetoothProfile.LE_AUDIO)) { + setAudioRoutingConfig(device); + } else { + clearAudioRoutingConfig(); + } + } + private void setAudioRoutingConfig(CachedBluetoothDevice device) { AudioDeviceAttributes hearingDeviceAttributes = mRoutingHelper.getMatchedHearingDeviceAttributes(device); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java index 3361a66e958d2..8c316d1c4f212 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java @@ -46,6 +46,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import java.util.ArrayList; +import java.util.Collections; import java.util.List; @RunWith(RobolectricTestRunner.class) @@ -395,6 +396,21 @@ public class BluetoothEventManagerTest { assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.HEARING_AID)).isFalse(); } + @Test + public void dispatchActiveDeviceChanged_callExpectedOnActiveDeviceChanged() { + mBluetoothEventManager.registerCallback(mBluetoothCallback); + when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn( + Collections.singletonList(mCachedDevice1)); + + mBluetoothEventManager.dispatchActiveDeviceChanged(mCachedDevice1, + BluetoothProfile.HEARING_AID); + + verify(mCachedDeviceManager).onActiveDeviceChanged(mCachedDevice1); + verify(mBluetoothCallback).onActiveDeviceChanged(mCachedDevice1, + BluetoothProfile.HEARING_AID); + } + @Test public void showUnbondMessage_reasonAuthTimeout_showCorrectedErrorCode() { mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java index 4b3820eb0444a..7e7c76e6ecba4 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java @@ -17,7 +17,9 @@ package com.android.settingslib.bluetooth; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -604,4 +606,20 @@ public class CachedBluetoothDeviceManagerTest { verify(mDevice2).setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED); verify(mDevice2).createBond(BluetoothDevice.TRANSPORT_LE); } + + @Test + public void onActiveDeviceChanged_validHiSyncId_callExpectedFunction() { + mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mContext, mLocalBluetoothManager, + mCachedDeviceManager.mCachedDevices)); + doNothing().when(mHearingAidDeviceManager).onActiveDeviceChanged(any()); + mCachedDeviceManager.mHearingAidDeviceManager = mHearingAidDeviceManager; + when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); + cachedDevice1.setHearingAidInfo( + new HearingAidInfo.Builder().setHiSyncId(HISYNCID1).build()); + + mCachedDeviceManager.onActiveDeviceChanged(cachedDevice1); + + verify(mHearingAidDeviceManager).onActiveDeviceChanged(cachedDevice1); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java index a839136934588..0d5de88cc394c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java @@ -478,37 +478,24 @@ public class HearingAidDeviceManagerTest { } @Test - public void onProfileConnectionStateChanged_connected_callSetStrategies() { + public void onActiveDeviceChanged_connected_callSetStrategies() { when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn( mHearingDeviceAttribute); + when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true); - mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1, - BluetoothProfile.STATE_CONNECTED); + mHearingAidDeviceManager.onActiveDeviceChanged(mCachedDevice1); verify(mHelper, atLeastOnce()).setPreferredDeviceRoutingStrategies( eq(List.of(mAudioStrategy)), any(AudioDeviceAttributes.class), anyInt()); } @Test - public void onProfileConnectionStateChanged_disconnected_callSetStrategiesWithAutoValue() { + public void onActiveDeviceChanged_disconnected_callSetStrategiesWithAutoValue() { when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn( mHearingDeviceAttribute); + when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(false); - mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1, - BluetoothProfile.STATE_DISCONNECTED); - - verify(mHelper, atLeastOnce()).setPreferredDeviceRoutingStrategies( - eq(List.of(mAudioStrategy)), /* hearingDevice= */ isNull(), - eq(HearingAidAudioRoutingConstants.RoutingValue.AUTO)); - } - @Test - public void onProfileConnectionStateChanged_unpairing_callSetStrategiesWithAutoValue() { - when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn( - mHearingDeviceAttribute); - - when(mCachedDevice1.getUnpairing()).thenReturn(true); - mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1, - BluetoothProfile.STATE_DISCONNECTED); + mHearingAidDeviceManager.onActiveDeviceChanged(mCachedDevice1); verify(mHelper, atLeastOnce()).setPreferredDeviceRoutingStrategies( eq(List.of(mAudioStrategy)), /* hearingDevice= */ isNull(),