OSDN Git Service

Show only 1 entry for hearing aid device after killing the activity.
authorIsha Bobra <ibobra@google.com>
Fri, 9 Feb 2018 00:04:36 +0000 (16:04 -0800)
committerStanley Tng <stng@google.com>
Fri, 13 Apr 2018 22:51:06 +0000 (22:51 +0000)
This CL detects Bluetooth hearing aid devices and tries to
combine the entry of the hearing aids with the same HiSyncIds and
shows only 1 entry for each pair in the connected devices list. This CL
works for scenarios just after pairing and reopening settings
activity after killing it.

This CL also has the logic to combine the entries just after pairing and
to forget both the devices on pressing forget for the combined entry.

Test: RunSettingsLibRoboTests
Bug: 74204427
Change-Id: Ib4c76eb0cae12937dd8403e37a0af8297a4aedc2

packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java

index 06fe4de..fb268ab 100644 (file)
@@ -272,6 +272,14 @@ public class BluetoothEventManager {
         }
     }
 
+    void dispatchDeviceRemoved(CachedBluetoothDevice cachedDevice) {
+        synchronized (mCallbacks) {
+            for (BluetoothCallback callback : mCallbacks) {
+                callback.onDeviceDeleted(cachedDevice);
+            }
+        }
+    }
+
     private class DeviceDisappearedHandler implements Handler {
         public void onReceive(Context context, Intent intent,
                 BluetoothDevice device) {
@@ -331,6 +339,10 @@ public class BluetoothEventManager {
             cachedDevice.onBondingStateChanged(bondState);
 
             if (bondState == BluetoothDevice.BOND_NONE) {
+                /* Check if we need to remove other Hearing Aid devices */
+                if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
+                    mDeviceManager.onDeviceUnpaired(cachedDevice);
+                }
                 int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
                         BluetoothDevice.ERROR);
 
index dc2ecea..d641d5b 100644 (file)
@@ -18,6 +18,7 @@ package com.android.settingslib.bluetooth;
 
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.content.Context;
@@ -53,6 +54,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
     private final BluetoothDevice mDevice;
     //TODO: consider remove, BluetoothDevice.getName() is already cached
     private String mName;
+    private long mHiSyncId;
     // Need this since there is no method for getting RSSI
     private short mRssi;
     //TODO: consider remove, BluetoothDevice.getBluetoothClass() is already cached
@@ -94,6 +96,17 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
      */
     private boolean mIsConnectingErrorPossible;
 
+    public long getHiSyncId() {
+        return mHiSyncId;
+    }
+
+    public void setHiSyncId(long id) {
+        if (Utils.D) {
+            Log.d(TAG, "setHiSyncId: mDevice " + mDevice + ", id " + id);
+        }
+        mHiSyncId = id;
+    }
+
     /**
      * Last time a bt profile auto-connect was attempted.
      * If an ACTION_UUID intent comes in within
@@ -175,6 +188,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
         mDevice = device;
         mProfileConnectionState = new HashMap<LocalBluetoothProfile, Integer>();
         fillData();
+        mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID;
     }
 
     public void disconnect() {
@@ -336,7 +350,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
                     }
                 } else if (Utils.V) {
                     Log.v(TAG, "Framework rejected command immediately:REMOVE_BOND " +
-                            describe(null));
+                        describe(null));
                 }
             }
         }
index a8e0039..2554d59 100644 (file)
@@ -18,12 +18,17 @@ package com.android.settingslib.bluetooth;
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
 import android.content.Context;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -34,10 +39,20 @@ public class CachedBluetoothDeviceManager {
     private static final boolean DEBUG = Utils.D;
 
     private Context mContext;
-    private final List<CachedBluetoothDevice> mCachedDevices =
-            new ArrayList<CachedBluetoothDevice>();
     private final LocalBluetoothManager mBtManager;
 
+    @VisibleForTesting
+    final List<CachedBluetoothDevice> mCachedDevices =
+        new ArrayList<CachedBluetoothDevice>();
+    // Contains the list of hearing aid devices that should not be shown in the UI.
+    @VisibleForTesting
+    final List<CachedBluetoothDevice> mHearingAidDevicesNotAddedInCache
+        = new ArrayList<CachedBluetoothDevice>();
+    // Maintains a list of devices which are added in mCachedDevices and have hiSyncIds.
+    @VisibleForTesting
+    final Map<Long, CachedBluetoothDevice> mCachedDevicesMapForHearingAids
+        = new HashMap<Long, CachedBluetoothDevice>();
+
     CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) {
         mContext = context;
         mBtManager = localBtManager;
@@ -75,6 +90,11 @@ public class CachedBluetoothDeviceManager {
                 return cachedDevice;
             }
         }
+        for (CachedBluetoothDevice notCachedDevice : mHearingAidDevicesNotAddedInCache) {
+            if (notCachedDevice.getDevice().equals(device)) {
+                return notCachedDevice;
+            }
+        }
         return null;
     }
 
@@ -89,14 +109,103 @@ public class CachedBluetoothDeviceManager {
             BluetoothDevice device) {
         CachedBluetoothDevice newDevice = new CachedBluetoothDevice(mContext, adapter,
             profileManager, device);
-        synchronized (mCachedDevices) {
-            mCachedDevices.add(newDevice);
-            mBtManager.getEventManager().dispatchDeviceAdded(newDevice);
+        if (profileManager.getHearingAidProfile() != null
+            && profileManager.getHearingAidProfile().getHiSyncId(newDevice.getDevice())
+                != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
+            newDevice.setHiSyncId(profileManager.getHearingAidProfile()
+                .getHiSyncId(newDevice.getDevice()));
         }
+        // Just add one of the hearing aids from a pair in the list that is shown in the UI.
+        if (isPairAddedInCache(newDevice.getHiSyncId())) {
+            synchronized (this) {
+                mHearingAidDevicesNotAddedInCache.add(newDevice);
+            }
+        } else {
+            synchronized (this) {
+                mCachedDevices.add(newDevice);
+                if (newDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID
+                    && !mCachedDevicesMapForHearingAids.containsKey(newDevice.getHiSyncId())) {
+                    mCachedDevicesMapForHearingAids.put(newDevice.getHiSyncId(), newDevice);
+                }
+                mBtManager.getEventManager().dispatchDeviceAdded(newDevice);
+            }
+        }
+
         return newDevice;
     }
 
     /**
+     * Returns true if the one of the two hearing aid devices is already cached for UI.
+     *
+     * @param long hiSyncId
+     * @return {@code True} if one of the two hearing aid devices is is already cached for UI.
+     */
+    private synchronized boolean isPairAddedInCache(long hiSyncId) {
+        if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID) {
+            return false;
+        }
+        if(mCachedDevicesMapForHearingAids.containsKey(hiSyncId)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns device summary of the pair of the hearing aid passed as the parameter.
+     *
+     * @param CachedBluetoothDevice device
+     * @return Device summary, or if the pair does not exist or if its not a hearing aid,
+     * then {@code null}.
+     */
+    public synchronized String getHearingAidPairDeviceSummary(CachedBluetoothDevice device) {
+        String pairDeviceSummary = null;
+        if (device.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
+            for (CachedBluetoothDevice hearingAidDevice : mHearingAidDevicesNotAddedInCache) {
+                if (hearingAidDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID
+                    && hearingAidDevice.getHiSyncId() == device.getHiSyncId()) {
+                    pairDeviceSummary = hearingAidDevice.getConnectionSummary();
+                }
+            }
+        }
+        return pairDeviceSummary;
+    }
+
+    /**
+     * Adds the 2nd hearing aid in a pair in a list that maintains the hearing aids that are
+     * not dispalyed in the UI.
+     *
+     * @param CachedBluetoothDevice device
+     */
+    public synchronized void addDeviceNotaddedInMap(CachedBluetoothDevice device) {
+        mHearingAidDevicesNotAddedInCache.add(device);
+    }
+
+    /**
+     * Updates the Hearing Aid devices; specifically the HiSyncId's. This routine is called when the
+     * Hearing Aid Service is connected and the HiSyncId's are now available.
+     * @param LocalBluetoothProfileManager profileManager
+     */
+    public synchronized void updateHearingAidsDevices(LocalBluetoothProfileManager profileManager) {
+        HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
+        if (profileProxy == null) {
+            log("updateHearingAidsDevices: getHearingAidProfile() is null");
+            return;
+        }
+        for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
+            if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
+                continue;
+            }
+
+            long newHiSyncId = profileProxy.getHiSyncId(cachedDevice.getDevice());
+
+            if (newHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
+                cachedDevice.setHiSyncId(newHiSyncId);
+                onHiSyncIdChanged(newHiSyncId);
+            }
+        }
+    }
+
+    /**
      * Attempts to get the name of a remote device, otherwise returns the address.
      *
      * @param device The remote device.
@@ -117,23 +226,29 @@ public class CachedBluetoothDeviceManager {
     }
 
     public synchronized void clearNonBondedDevices() {
-        for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
-            CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
-            if (cachedDevice.getBondState() == BluetoothDevice.BOND_NONE) {
-                mCachedDevices.remove(i);
-            }
-        }
+
+        mCachedDevicesMapForHearingAids.entrySet().removeIf(entries
+            -> entries.getValue().getBondState() == BluetoothDevice.BOND_NONE);
+
+        mCachedDevices.removeIf(cachedDevice
+            -> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE);
+
+        mHearingAidDevicesNotAddedInCache.removeIf(hearingAidDevice
+            -> hearingAidDevice.getBondState() == BluetoothDevice.BOND_NONE);
     }
 
     public synchronized void onScanningStateChanged(boolean started) {
         if (!started) return;
-
         // If starting a new scan, clear old visibility
         // Iterate in reverse order since devices may be removed.
         for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
             CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
             cachedDevice.setJustDiscovered(false);
         }
+        for (int i = mHearingAidDevicesNotAddedInCache.size() - 1; i >= 0; i--) {
+            CachedBluetoothDevice notCachedDevice = mHearingAidDevicesNotAddedInCache.get(i);
+            notCachedDevice.setJustDiscovered(false);
+        }
     }
 
     public synchronized void onBtClassChanged(BluetoothDevice device) {
@@ -159,6 +274,11 @@ public class CachedBluetoothDeviceManager {
                 if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
                     cachedDevice.setJustDiscovered(false);
                     mCachedDevices.remove(i);
+                    if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID
+                        && mCachedDevicesMapForHearingAids.containsKey(cachedDevice.getHiSyncId()))
+                    {
+                        mCachedDevicesMapForHearingAids.remove(cachedDevice.getHiSyncId());
+                    }
                 } else {
                     // For bonded devices, we need to clear the connection status so that
                     // when BT is enabled next time, device connection status shall be retrieved
@@ -166,6 +286,18 @@ public class CachedBluetoothDeviceManager {
                     cachedDevice.clearProfileConnectionState();
                 }
             }
+            for (int i = mHearingAidDevicesNotAddedInCache.size() - 1; i >= 0; i--) {
+                CachedBluetoothDevice notCachedDevice = mHearingAidDevicesNotAddedInCache.get(i);
+                if (notCachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
+                    notCachedDevice.setJustDiscovered(false);
+                    mHearingAidDevicesNotAddedInCache.remove(i);
+                } else {
+                    // For bonded devices, we need to clear the connection status so that
+                    // when BT is enabled next time, device connection status shall be retrieved
+                    // by making a binder call.
+                    notCachedDevice.clearProfileConnectionState();
+                }
+            }
         }
     }
 
@@ -177,6 +309,49 @@ public class CachedBluetoothDeviceManager {
         }
     }
 
+    public synchronized void onHiSyncIdChanged(long hiSyncId) {
+        int firstMatchedIndex = -1;
+
+        for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
+            CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
+            if (cachedDevice.getHiSyncId() == hiSyncId) {
+                if (firstMatchedIndex != -1) {
+                    /* Found the second one */
+                    mCachedDevices.remove(i);
+                    mHearingAidDevicesNotAddedInCache.add(cachedDevice);
+                    // 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
+                    // 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);
+                    break;
+                } else {
+                    mCachedDevicesMapForHearingAids.put(hiSyncId, cachedDevice);
+                    firstMatchedIndex = i;
+                }
+            }
+        }
+    }
+
+    public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) {
+        final long hiSyncId = device.getHiSyncId();
+
+        if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID) return;
+
+        for (int i = mHearingAidDevicesNotAddedInCache.size() - 1; i >= 0; i--) {
+            CachedBluetoothDevice cachedDevice = mHearingAidDevicesNotAddedInCache.get(i);
+            if (cachedDevice.getHiSyncId() == hiSyncId) {
+                // TODO: Look for more cleanups on unpairing the device.
+                mHearingAidDevicesNotAddedInCache.remove(i);
+                if (device == cachedDevice) continue;
+                cachedDevice.unpair();
+            }
+        }
+    }
+
     private void log(String msg) {
         if (DEBUG) {
             Log.d(TAG, msg);
index 6c5ecbf..da2ae7f 100644 (file)
@@ -67,12 +67,19 @@ public class HearingAidProfile implements LocalBluetoothProfile {
                 CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
                 // we may add a new device here, but generally this should not happen
                 if (device == null) {
-                    Log.w(TAG, "HearingAidProfile found new device: " + nextDevice);
+                    if (V) {
+                        Log.d(TAG, "HearingAidProfile found new device: " + nextDevice);
+                    }
                     device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
                 }
-                device.onProfileStateChanged(HearingAidProfile.this, BluetoothProfile.STATE_CONNECTED);
+                device.onProfileStateChanged(HearingAidProfile.this,
+                        BluetoothProfile.STATE_CONNECTED);
                 device.refresh();
             }
+
+            // Check current list of CachedDevices to see if any are Hearing Aid devices.
+            mDeviceManager.updateHearingAidsDevices(mProfileManager);
+
             mIsProfileReady=true;
         }
 
index 1e0cce9..11854b1 100644 (file)
@@ -354,6 +354,22 @@ public class LocalBluetoothProfileManager {
                     oldState == BluetoothProfile.STATE_CONNECTING) {
                 Log.i(TAG, "Failed to connect " + mProfile + " device");
             }
+
+            if (getHearingAidProfile() != null &&
+                mProfile instanceof HearingAidProfile &&
+                (newState == BluetoothProfile.STATE_CONNECTED)) {
+                // Check if the HiSyncID has being initialized
+                if (cachedDevice.getHiSyncId() == BluetoothHearingAid.HI_SYNC_ID_INVALID) {
+
+                    long newHiSyncId = getHearingAidProfile().getHiSyncId(cachedDevice.getDevice());
+
+                    if (newHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
+                        cachedDevice.setHiSyncId(newHiSyncId);
+                        mDeviceManager.onHiSyncIdChanged(newHiSyncId);
+                    }
+                }
+            }
+
             cachedDevice.onProfileStateChanged(mProfile, newState);
             cachedDevice.refresh();
         }
index 2f5eead..c3bd161 100644 (file)
@@ -48,10 +48,18 @@ import java.util.Collection;
 public class CachedBluetoothDeviceManagerTest {
     private final static String DEVICE_NAME_1 = "TestName_1";
     private final static String DEVICE_NAME_2 = "TestName_2";
+    private final static String DEVICE_NAME_3 = "TestName_3";
     private final static String DEVICE_ALIAS_1 = "TestAlias_1";
     private final static String DEVICE_ALIAS_2 = "TestAlias_2";
+    private final static String DEVICE_ALIAS_3 = "TestAlias_3";
     private final static String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11";
     private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22";
+    private final static String DEVICE_ADDRESS_3 = "AA:BB:CC:DD:EE:33";
+    private final static String DEVICE_SUMMARY_1 = "summary 1";
+    private final static String DEVICE_SUMMARY_2 = "summary 2";
+    private final static String DEVICE_SUMMARY_3 = "summary 3";
+    private final static long HISYNCID1 = 10;
+    private final static long HISYNCID2 = 11;
     private final BluetoothClass DEVICE_CLASS_1 =
         new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
     private final BluetoothClass DEVICE_CLASS_2 =
@@ -76,6 +84,11 @@ public class CachedBluetoothDeviceManagerTest {
     private BluetoothDevice mDevice1;
     @Mock
     private BluetoothDevice mDevice2;
+    @Mock
+    private BluetoothDevice mDevice3;
+    private CachedBluetoothDevice mCachedDevice1;
+    private CachedBluetoothDevice mCachedDevice2;
+    private CachedBluetoothDevice mCachedDevice3;
     private CachedBluetoothDeviceManager mCachedDeviceManager;
     private Context mContext;
     private String[] mActiveDeviceStringsArray;
@@ -90,12 +103,16 @@ public class CachedBluetoothDeviceManagerTest {
         mContext = RuntimeEnvironment.application;
         when(mDevice1.getAddress()).thenReturn(DEVICE_ADDRESS_1);
         when(mDevice2.getAddress()).thenReturn(DEVICE_ADDRESS_2);
+        when(mDevice3.getAddress()).thenReturn(DEVICE_ADDRESS_3);
         when(mDevice1.getName()).thenReturn(DEVICE_NAME_1);
         when(mDevice2.getName()).thenReturn(DEVICE_NAME_2);
+        when(mDevice3.getName()).thenReturn(DEVICE_NAME_3);
         when(mDevice1.getAliasName()).thenReturn(DEVICE_ALIAS_1);
         when(mDevice2.getAliasName()).thenReturn(DEVICE_ALIAS_2);
+        when(mDevice3.getAliasName()).thenReturn(DEVICE_ALIAS_3);
         when(mDevice1.getBluetoothClass()).thenReturn(DEVICE_CLASS_1);
         when(mDevice2.getBluetoothClass()).thenReturn(DEVICE_CLASS_2);
+        when(mDevice3.getBluetoothClass()).thenReturn(DEVICE_CLASS_2);
 
         when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
         when(mLocalAdapter.getBluetoothState()).thenReturn(BluetoothAdapter.STATE_ON);
@@ -104,6 +121,12 @@ public class CachedBluetoothDeviceManagerTest {
         when(mPanProfile.isProfileReady()).thenReturn(true);
         when(mHearingAidProfile.isProfileReady()).thenReturn(true);
         mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager);
+        mCachedDevice1 = spy(
+            new CachedBluetoothDevice(mContext, mLocalAdapter, mLocalProfileManager, mDevice1));
+        mCachedDevice2 = spy(
+            new CachedBluetoothDevice(mContext, mLocalAdapter, mLocalProfileManager, mDevice2));
+        mCachedDevice3 = spy(
+            new CachedBluetoothDevice(mContext, mLocalAdapter, mLocalProfileManager, mDevice3));
     }
 
     /**
@@ -188,6 +211,289 @@ public class CachedBluetoothDeviceManagerTest {
     }
 
     /**
+     * Test to verify clearNonBondedDevices() for hearing aids.
+     */
+    @Test
+    public void testClearNonBondedDevices_HearingAids_nonBondedHAsClearedFromCachedDevicesMap() {
+        when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+
+        mCachedDevice1.setHiSyncId(HISYNCID1);
+        mCachedDevice2.setHiSyncId(HISYNCID2);
+        mCachedDeviceManager.mCachedDevicesMapForHearingAids.put(HISYNCID1, mCachedDevice1);
+        mCachedDeviceManager.mCachedDevicesMapForHearingAids.put(HISYNCID2, mCachedDevice2);
+
+        mCachedDeviceManager.clearNonBondedDevices();
+
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values())
+            .doesNotContain(mCachedDevice2);
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values())
+            .contains(mCachedDevice1);
+    }
+
+    /**
+     * Test to verify onHiSyncIdChanged() for hearing aid devices with same HiSyncId.
+     */
+    @Test
+    public void testOnDeviceAdded_sameHiSyncId_populateInDifferentLists() {
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice1);
+        assertThat(cachedDevice1).isNotNull();
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice2);
+        assertThat(cachedDevice2).isNotNull();
+
+        // 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);
+
+        // Since both devices have the same hiSyncId, one should remain in mCachedDevices
+        // and the other should be removed from mCachedDevices and get added in
+        // mHearingAidDevicesNotAddedInCache. The one that is in mCachedDevices should also be
+        // added in mCachedDevicesMapForHearingAids.
+        assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).hasSize(1);
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1);
+        Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
+        assertThat(devices).contains(cachedDevice2);
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values())
+            .contains(cachedDevice2);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).contains(cachedDevice1);
+    }
+
+    /**
+     * Test to verify onHiSyncIdChanged() for hearing aid devices with different HiSyncId.
+     */
+    @Test
+    public void testOnDeviceAdded_differentHiSyncId_populateInSameList() {
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice1);
+        assertThat(cachedDevice1).isNotNull();
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice2);
+        assertThat(cachedDevice2).isNotNull();
+
+        // 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(HISYNCID2);
+        mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1);
+        mCachedDeviceManager.onHiSyncIdChanged(HISYNCID2);
+
+        // Since both devices do not have same hiSyncId, they should remain in mCachedDevices and
+        // also be added in mCachedDevicesMapForHearingAids.
+        assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(2);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty();
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(2);
+        Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
+        assertThat(devices).contains(cachedDevice2);
+        assertThat(devices).contains(cachedDevice1);
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values())
+            .contains(cachedDevice1);
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values())
+            .contains(cachedDevice2);
+    }
+
+    /**
+     * Test to verify OnDeviceUnpaired() for a paired hearing Aid device pair.
+     */
+    @Test
+    public void testOnDeviceUnpaired_bothHearingAidsPaired_removesItsPairFromList() {
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice1);
+        assertThat(cachedDevice1).isNotNull();
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice2);
+        assertThat(cachedDevice2).isNotNull();
+
+        cachedDevice1.setHiSyncId(HISYNCID1);
+        cachedDevice2.setHiSyncId(HISYNCID1);
+        mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1);
+
+        // Check if one device is in mCachedDevices and one in mHearingAidDevicesNotAddedInCache.
+        Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
+        assertThat(devices).contains(cachedDevice2);
+        assertThat(devices).doesNotContain(cachedDevice1);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).contains(cachedDevice1);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache)
+            .doesNotContain(cachedDevice2);
+
+        // Call onDeviceUnpaired for the one in mCachedDevices.
+        mCachedDeviceManager.onDeviceUnpaired(cachedDevice2);
+
+        // Check if its pair is removed from mHearingAidDevicesNotAddedInCache.
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache)
+            .doesNotContain(cachedDevice1);
+    }
+
+    /**
+     * Test to verify OnDeviceUnpaired() for paired hearing Aid devices which are not a pair.
+     */
+    @Test
+    public void testOnDeviceUnpaired_bothHearingAidsNotPaired_doesNotRemoveAnyDeviceFromList() {
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice1);
+        assertThat(cachedDevice1).isNotNull();
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice2);
+        assertThat(cachedDevice2).isNotNull();
+        CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice3);
+        assertThat(cachedDevice2).isNotNull();
+
+        cachedDevice1.setHiSyncId(HISYNCID1);
+        cachedDevice2.setHiSyncId(HISYNCID1);
+        cachedDevice3.setHiSyncId(HISYNCID2);
+        mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1);
+        mCachedDeviceManager.onHiSyncIdChanged(HISYNCID2);
+
+        // Check if one device is in mCachedDevices and one in mHearingAidDevicesNotAddedInCache.
+        Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
+        assertThat(devices).contains(cachedDevice2);
+        assertThat(devices).contains(cachedDevice3);
+        assertThat(devices).doesNotContain(cachedDevice1);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).contains(cachedDevice1);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache)
+            .doesNotContain(cachedDevice2);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache)
+            .doesNotContain(cachedDevice3);
+
+        // Call onDeviceUnpaired for the one in mCachedDevices with no pair.
+        mCachedDeviceManager.onDeviceUnpaired(cachedDevice3);
+
+        // Check if no list is changed.
+        devices = mCachedDeviceManager.getCachedDevicesCopy();
+        assertThat(devices).contains(cachedDevice2);
+        assertThat(devices).contains(cachedDevice3);
+        assertThat(devices).doesNotContain(cachedDevice1);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).contains(cachedDevice1);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache)
+            .doesNotContain(cachedDevice2);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache)
+            .doesNotContain(cachedDevice3);
+    }
+
+    /**
+     * Test to verify addDevice() for hearing aid devices with same HiSyncId.
+     */
+    @Test
+    public void testAddDevice_hearingAidDevicesWithSameHiSyncId_populateInDifferentLists() {
+        doAnswer((invocation) -> mHearingAidProfile).when(mLocalProfileManager)
+            .getHearingAidProfile();
+        doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1);
+        doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice2);
+
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice1);
+        assertThat(cachedDevice1).isNotNull();
+        // The first hearing aid device should be populated in mCachedDevice and
+        // mCachedDevicesMapForHearingAids.
+        assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty();
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1);
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values())
+            .contains(cachedDevice1);
+
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice2);
+        assertThat(cachedDevice2).isNotNull();
+        // The second hearing aid device should be populated in mHearingAidDevicesNotAddedInCache.
+        assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).hasSize(1);
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1);
+    }
+
+    /**
+     * Test to verify addDevice() for hearing aid devices with different HiSyncId.
+     */
+    @Test
+    public void testAddDevice_hearingAidDevicesWithDifferentHiSyncId_populateInSameList() {
+        doAnswer((invocation) -> mHearingAidProfile).when(mLocalProfileManager)
+            .getHearingAidProfile();
+        doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1);
+        doAnswer((invocation) -> HISYNCID2).when(mHearingAidProfile).getHiSyncId(mDevice2);
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice1);
+        assertThat(cachedDevice1).isNotNull();
+        // The first hearing aid device should be populated in mCachedDevice and
+        // mCachedDevicesMapForHearingAids.
+        assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty();
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1);
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values())
+            .contains(cachedDevice1);
+
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice2);
+        assertThat(cachedDevice2).isNotNull();
+        // The second hearing aid device should also be populated in mCachedDevice
+        // and mCachedDevicesMapForHearingAids as its not a pair of the first one.
+        assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(2);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty();
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(2);
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values())
+            .contains(cachedDevice1);
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values())
+            .contains(cachedDevice2);
+    }
+
+    /**
+     * Test to verify getHearingAidPairDeviceSummary() for hearing aid devices with same HiSyncId.
+     */
+    @Test
+    public void testGetHearingAidPairDeviceSummary_bothHearingAidsPaired_returnsSummaryOfPair() {
+        mCachedDevice1.setHiSyncId(HISYNCID1);
+        mCachedDevice2.setHiSyncId(HISYNCID1);
+        mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
+        mCachedDeviceManager.mHearingAidDevicesNotAddedInCache.add(mCachedDevice2);
+        doAnswer((invocation) -> DEVICE_SUMMARY_1).when(mCachedDevice1).getConnectionSummary();
+        doAnswer((invocation) -> DEVICE_SUMMARY_2).when(mCachedDevice2).getConnectionSummary();
+
+        assertThat(mCachedDeviceManager.getHearingAidPairDeviceSummary(mCachedDevice1))
+            .isEqualTo(DEVICE_SUMMARY_2);
+    }
+
+    /**
+     * Test to verify getHearingAidPairDeviceSummary() for hearing aid devices with different
+     * HiSyncId.
+     */
+    @Test
+    public void testGetHearingAidPairDeviceSummary_bothHearingAidsNotPaired_returnsNull() {
+        mCachedDevice1.setHiSyncId(HISYNCID1);
+        mCachedDevice2.setHiSyncId(HISYNCID2);
+        mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
+        mCachedDeviceManager.mHearingAidDevicesNotAddedInCache.add(mCachedDevice2);
+        doAnswer((invocation) -> DEVICE_SUMMARY_1).when(mCachedDevice1).getConnectionSummary();
+        doAnswer((invocation) -> DEVICE_SUMMARY_2).when(mCachedDevice2).getConnectionSummary();
+
+        assertThat(mCachedDeviceManager.getHearingAidPairDeviceSummary(mCachedDevice1))
+            .isEqualTo(null);
+    }
+
+    /**
+     * Test to verify updateHearingAidsDevices().
+     */
+    @Test
+    public void testUpdateHearingAidDevices_hiSyncIdAvailable_setsHiSyncId() {
+        doAnswer((invocation) -> mHearingAidProfile).when(mLocalProfileManager)
+            .getHearingAidProfile();
+        doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1);
+        mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
+        mCachedDeviceManager.updateHearingAidsDevices(mLocalProfileManager);
+
+        // Assert that the mCachedDevice1 has an updated HiSyncId.
+        assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1);
+    }
+
+    /**
      * Test to verify onBtClassChanged().
      */
     @Test