From 6159842dec1f35a6f4e8b6cb1e5730bea43631e3 Mon Sep 17 00:00:00 2001 From: Stanley Tng Date: Sun, 13 May 2018 15:08:33 -0700 Subject: Fix the Hearing Aids connected state in Settings App When two Hearing Aids devices are bonded, we should only show one device in the Settings App and at the correct location based on the connected state of the HA devices. When at least one HA device is connected, then the Settings App should show it as connected. Otherwise, the Settings App should show it as disconnected when both devices are disconnected. Bug: 79760469 Test: Run the robotests for Settings and SettingsLib Change-Id: I33e19af054b686b3d71b00dcbd6ff16febde6099 --- .../bluetooth/BluetoothEventManager.java | 1 + .../bluetooth/CachedBluetoothDeviceManager.java | 103 ++++++++++- .../bluetooth/BluetoothEventManagerTest.java | 3 + .../CachedBluetoothDeviceManagerTest.java | 194 ++++++++++++++++++++- 4 files changed, 289 insertions(+), 12 deletions(-) diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 4e9e566b0d87..0b58c9dd0355 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -505,5 +505,6 @@ public class BluetoothEventManager { callback.onProfileConnectionStateChanged(device, state, bluetoothProfile); } } + mDeviceManager.onProfileConnectionStateChanged(device, state, bluetoothProfile); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java index 15f6983a684c..e9d96ae0310f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java @@ -19,6 +19,7 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BluetoothProfile; import android.content.Context; import android.util.Log; @@ -323,16 +324,31 @@ public class CachedBluetoothDeviceManager { if (cachedDevice.getHiSyncId() == hiSyncId) { if (firstMatchedIndex != -1) { /* Found the second one */ - mCachedDevices.remove(i); - mHearingAidDevicesNotAddedInCache.add(cachedDevice); + int indexToRemoveFromUi; + CachedBluetoothDevice deviceToRemoveFromUi; + // Since the hiSyncIds have been updated for a connected pair of hearing aids, // we remove the entry of one the hearing aids from the UI. Unless the - // hiSyncId get updated, the system does not know its a hearing aid, so we add + // hiSyncId get updated, the system does not know it is a hearing aid, so we add // both the hearing aids as separate entries in the UI first, then remove one - // of them after the hiSyncId is populated. - log("onHiSyncIdChanged: removed device=" + cachedDevice + ", with hiSyncId=" - + hiSyncId); - mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice); + // of them after the hiSyncId is populated. We will choose the device that + // is not connected to be removed. + if (cachedDevice.isConnected()) { + indexToRemoveFromUi = firstMatchedIndex; + deviceToRemoveFromUi = mCachedDevices.get(firstMatchedIndex); + mCachedDevicesMapForHearingAids.put(hiSyncId, cachedDevice); + } else { + indexToRemoveFromUi = i; + deviceToRemoveFromUi = cachedDevice; + mCachedDevicesMapForHearingAids.put(hiSyncId, + mCachedDevices.get(firstMatchedIndex)); + } + + mCachedDevices.remove(indexToRemoveFromUi); + mHearingAidDevicesNotAddedInCache.add(deviceToRemoveFromUi); + log("onHiSyncIdChanged: removed from UI device=" + deviceToRemoveFromUi + + ", with hiSyncId=" + hiSyncId); + mBtManager.getEventManager().dispatchDeviceRemoved(deviceToRemoveFromUi); break; } else { mCachedDevicesMapForHearingAids.put(hiSyncId, cachedDevice); @@ -342,6 +358,72 @@ public class CachedBluetoothDeviceManager { } } + private CachedBluetoothDevice getHearingAidOtherDevice(CachedBluetoothDevice thisDevice, + long hiSyncId) { + if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID) { + return null; + } + + // Searched the lists for the other side device with the matching hiSyncId. + for (CachedBluetoothDevice notCachedDevice : mHearingAidDevicesNotAddedInCache) { + if ((hiSyncId == notCachedDevice.getHiSyncId()) && + (!Objects.equals(notCachedDevice, thisDevice))) { + return notCachedDevice; + } + } + + CachedBluetoothDevice cachedDevice = mCachedDevicesMapForHearingAids.get(hiSyncId); + if (!Objects.equals(cachedDevice, thisDevice)) { + return cachedDevice; + } + return null; + } + + private void hearingAidSwitchDisplayDevice(CachedBluetoothDevice toDisplayDevice, + CachedBluetoothDevice toHideDevice, long hiSyncId) + { + log("hearingAidSwitchDisplayDevice: toDisplayDevice=" + toDisplayDevice + + ", toHideDevice=" + toHideDevice); + + // Remove the "toHideDevice" device from the UI. + mHearingAidDevicesNotAddedInCache.add(toHideDevice); + mCachedDevices.remove(toHideDevice); + mBtManager.getEventManager().dispatchDeviceRemoved(toHideDevice); + + // Add the "toDisplayDevice" device to the UI. + mHearingAidDevicesNotAddedInCache.remove(toDisplayDevice); + mCachedDevices.add(toDisplayDevice); + mCachedDevicesMapForHearingAids.put(hiSyncId, toDisplayDevice); + mBtManager.getEventManager().dispatchDeviceAdded(toDisplayDevice); + } + + public synchronized void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, + int state, int bluetoothProfile) { + if (bluetoothProfile == BluetoothProfile.HEARING_AID + && cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID + && cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) { + + long hiSyncId = cachedDevice.getHiSyncId(); + + CachedBluetoothDevice otherDevice = getHearingAidOtherDevice(cachedDevice, hiSyncId); + if (otherDevice == null) { + // no other side device. Nothing to do. + return; + } + + if (state == BluetoothProfile.STATE_CONNECTED && + mHearingAidDevicesNotAddedInCache.contains(cachedDevice)) { + hearingAidSwitchDisplayDevice(cachedDevice, otherDevice, hiSyncId); + } else if (state == BluetoothProfile.STATE_DISCONNECTED + && otherDevice.isConnected()) { + CachedBluetoothDevice mapDevice = mCachedDevicesMapForHearingAids.get(hiSyncId); + if ((mapDevice != null) && (Objects.equals(cachedDevice, mapDevice))) { + hearingAidSwitchDisplayDevice(otherDevice, cachedDevice, hiSyncId); + } + } + } + } + public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) { final long hiSyncId = device.getHiSyncId(); @@ -353,9 +435,16 @@ public class CachedBluetoothDeviceManager { // TODO: Look for more cleanups on unpairing the device. mHearingAidDevicesNotAddedInCache.remove(i); if (device == cachedDevice) continue; + log("onDeviceUnpaired: Unpair device=" + cachedDevice); cachedDevice.unpair(); } } + + CachedBluetoothDevice mappedDevice = mCachedDevicesMapForHearingAids.get(hiSyncId); + if ((mappedDevice != null) && (!Objects.equals(device, mappedDevice))) { + log("onDeviceUnpaired: Unpair mapped device=" + mappedDevice); + mappedDevice.unpair(); + } } public synchronized void dispatchAudioModeChanged() { 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 466980c38e8e..108069022567 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 @@ -98,5 +98,8 @@ public class BluetoothEventManagerTest { verify(mBluetoothCallback).onProfileConnectionStateChanged(mCachedBluetoothDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP); + + verify(mCachedDeviceManager).onProfileConnectionStateChanged(mCachedBluetoothDevice, + BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP); } } 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 bab3cab3795c..16ed85cf5afa 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 @@ -235,7 +235,7 @@ public class CachedBluetoothDeviceManagerTest { * Test to verify onHiSyncIdChanged() for hearing aid devices with same HiSyncId. */ @Test - public void testOnDeviceAdded_sameHiSyncId_populateInDifferentLists() { + public void testOnHiSyncIdChanged_sameHiSyncId_populateInDifferentLists() { CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter, mLocalProfileManager, mDevice1); assertThat(cachedDevice1).isNotNull(); @@ -261,16 +261,53 @@ public class CachedBluetoothDeviceManagerTest { assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1); Collection devices = mCachedDeviceManager.getCachedDevicesCopy(); assertThat(devices).contains(cachedDevice2); - assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values()) - .contains(cachedDevice2); - assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).contains(cachedDevice1); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids) + .containsKey(HISYNCID1); + } + + /** + * Test to verify onHiSyncIdChanged() for 2 hearing aid devices with same HiSyncId but one + * device is connected and other is disconnected. The connected device should be chosen. + */ + @Test + public void testOnHiSyncIdChanged_sameHiSyncIdAndOneConnected_chooseConnectedDevice() { + CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice1); + assertThat(cachedDevice1).isNotNull(); + CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice2); + assertThat(cachedDevice2).isNotNull(); + cachedDevice1.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + cachedDevice2.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + + /* Device 1 is connected and Device 2 is disconnected */ + when(mHearingAidProfile.getConnectionStatus(mDevice1)). + thenReturn(BluetoothProfile.STATE_CONNECTED); + when(mHearingAidProfile.getConnectionStatus(mDevice2)). + thenReturn(BluetoothProfile.STATE_DISCONNECTED); + + // Since both devices do not have hiSyncId, they should be added in mCachedDevices. + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(2); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).isEmpty(); + + cachedDevice1.setHiSyncId(HISYNCID1); + cachedDevice2.setHiSyncId(HISYNCID1); + mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1); + + // Only the connected device, device 1, should be visible to UI. + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).containsExactly(cachedDevice1); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids). + containsExactly(HISYNCID1, cachedDevice1); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache). + containsExactly(cachedDevice2); } /** * Test to verify onHiSyncIdChanged() for hearing aid devices with different HiSyncId. */ @Test - public void testOnDeviceAdded_differentHiSyncId_populateInSameList() { + public void testOnHiSyncIdChanged_differentHiSyncId_populateInSameList() { CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter, mLocalProfileManager, mDevice1); assertThat(cachedDevice1).isNotNull(); @@ -302,6 +339,153 @@ public class CachedBluetoothDeviceManagerTest { .contains(cachedDevice2); } + /** + * Test to verify onProfileConnectionStateChanged() for single hearing aid device connection. + */ + @Test + public void testOnProfileConnectionStateChanged_singleDeviceConnected_visible() { + CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice1); + assertThat(cachedDevice1).isNotNull(); + cachedDevice1.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + + // Since both devices do not have hiSyncId, they should be added in mCachedDevices. + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).containsExactly(cachedDevice1); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).isEmpty(); + + cachedDevice1.setHiSyncId(HISYNCID1); + mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1); + + // Connect the Device 1 + mCachedDeviceManager.onProfileConnectionStateChanged(cachedDevice1, + BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEARING_AID); + + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).containsExactly(cachedDevice1); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids). + containsExactly(HISYNCID1, cachedDevice1); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); + + // Disconnect the Device 1 + mCachedDeviceManager.onProfileConnectionStateChanged(cachedDevice1, + BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.HEARING_AID); + + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).containsExactly(cachedDevice1); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids). + containsExactly(HISYNCID1, cachedDevice1); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); + } + + /** + * Test to verify onProfileConnectionStateChanged() for two hearing aid devices where both + * devices are disconnected and they get connected. + */ + @Test + public void testOnProfileConnectionStateChanged_twoDevicesConnected_oneDeviceVisible() { + CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice1); + assertThat(cachedDevice1).isNotNull(); + CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice2); + assertThat(cachedDevice2).isNotNull(); + cachedDevice1.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + cachedDevice2.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + + // Since both devices do not have hiSyncId, they should be added in mCachedDevices. + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(2); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).isEmpty(); + + cachedDevice1.setHiSyncId(HISYNCID1); + cachedDevice2.setHiSyncId(HISYNCID1); + mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1); + + // There should be one cached device but can be either one. + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1); + + // Connect the Device 1 + mCachedDeviceManager.onProfileConnectionStateChanged(cachedDevice1, + BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEARING_AID); + + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).containsExactly(cachedDevice1); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids). + containsExactly(HISYNCID1, cachedDevice1); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).contains(cachedDevice2); + assertThat(mCachedDeviceManager.mCachedDevices).contains(cachedDevice1); + + when(mHearingAidProfile.getConnectionStatus(mDevice1)). + thenReturn(BluetoothProfile.STATE_CONNECTED); + when(mHearingAidProfile.getConnectionStatus(mDevice2)). + thenReturn(BluetoothProfile.STATE_DISCONNECTED); + + // Connect the Device 2 + mCachedDeviceManager.onProfileConnectionStateChanged(cachedDevice2, + BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEARING_AID); + + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).hasSize(1); + assertThat(mCachedDeviceManager.mCachedDevices).hasSize(1); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1); + } + + /** + * Test to verify onProfileConnectionStateChanged() for two hearing aid devices where both + * devices are connected and they get disconnected. + */ + @Test + public void testOnProfileConnectionStateChanged_twoDevicesDisconnected_oneDeviceVisible() { + CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice1); + assertThat(cachedDevice1).isNotNull(); + CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice2); + assertThat(cachedDevice2).isNotNull(); + cachedDevice1.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + cachedDevice2.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + + when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + when(mHearingAidProfile.getConnectionStatus(mDevice1)). + thenReturn(BluetoothProfile.STATE_CONNECTED); + when(mHearingAidProfile.getConnectionStatus(mDevice2)). + thenReturn(BluetoothProfile.STATE_CONNECTED); + + // Since both devices do not have hiSyncId, they should be added in mCachedDevices. + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(2); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).isEmpty(); + + cachedDevice1.setHiSyncId(HISYNCID1); + cachedDevice2.setHiSyncId(HISYNCID1); + mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1); + + /* Disconnect the Device 1 */ + mCachedDeviceManager.onProfileConnectionStateChanged(cachedDevice1, + BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.HEARING_AID); + + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).containsExactly(cachedDevice2); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).contains(cachedDevice1); + assertThat(mCachedDeviceManager.mCachedDevices).contains(cachedDevice2); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids) + .containsExactly(HISYNCID1, cachedDevice2); + + when(mHearingAidProfile.getConnectionStatus(mDevice1)). + thenReturn(BluetoothProfile.STATE_DISCONNECTED); + when(mHearingAidProfile.getConnectionStatus(mDevice2)). + thenReturn(BluetoothProfile.STATE_CONNECTED); + + /* Disconnect the Device 2 */ + mCachedDeviceManager.onProfileConnectionStateChanged(cachedDevice2, + BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.HEARING_AID); + + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).hasSize(1); + assertThat(mCachedDeviceManager.mCachedDevices).hasSize(1); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1); + } + /** * Test to verify OnDeviceUnpaired() for a paired hearing Aid device pair. */ -- cgit v1.2.3-59-g8ed1b