Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5c62ebba authored by timhypeng's avatar timhypeng Committed by Hugh Chen
Browse files

Make HearingAid code more generic

-group hearingAid code into HearingAidDeviceManager
-add subDevice in CachedBluetoothDevice to store second hearingAid device
-add test case

Bug: 112735753
Test: make -j42 RunSettingsLibRoboTests
Change-Id: I6f554076b3e4e525959aba84e735b15f0e55d9d3
parent 86d0d9d8
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -171,7 +171,6 @@ public class BluetoothEventManager {
                callback.onProfileConnectionStateChanged(device, state, bluetoothProfile);
            }
        }
        mDeviceManager.onProfileConnectionStateChanged(device, state, bluetoothProfile);
    }

    private void dispatchConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+33 −6
Original line number Diff line number Diff line
@@ -29,6 +29,8 @@ import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.VisibleForTesting;

import com.android.settingslib.R;

import java.util.ArrayList;
@@ -36,8 +38,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;

import androidx.annotation.VisibleForTesting;

/**
 * CachedBluetoothDevice represents a remote Bluetooth device. It contains
 * attributes of the device (such as the address, name, RSSI, etc.) and
@@ -54,11 +54,12 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
    private final Context mContext;
    private final BluetoothAdapter mLocalAdapter;
    private final LocalBluetoothProfileManager mProfileManager;
    private final BluetoothDevice mDevice;
    BluetoothDevice mDevice;
    private long mHiSyncId;
    // Need this since there is no method for getting RSSI
    private short mRssi;

    short mRssi;
    // mProfiles and mRemovedProfiles does not do swap() between main and sub device. It is
    // because current sub device is only for HearingAid and its profile is the same.
    private final List<LocalBluetoothProfile> mProfiles =
            Collections.synchronizedList(new ArrayList<>());

@@ -69,7 +70,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
    // Device supports PANU but not NAP: remove PanProfile after device disconnects from NAP
    private boolean mLocalNapRoleConnected;

    private boolean mJustDiscovered;
    boolean mJustDiscovered;

    private int mMessageRejectionCount;

@@ -92,6 +93,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
    private boolean mIsActiveDeviceA2dp = false;
    private boolean mIsActiveDeviceHeadset = false;
    private boolean mIsActiveDeviceHearingAid = false;
    // Group second device for Hearing Aid
    private CachedBluetoothDevice mSubDevice;

    CachedBluetoothDevice(Context context, LocalBluetoothProfileManager profileManager,
            BluetoothDevice device) {
@@ -1064,4 +1067,28 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
        return hearingAidProfile != null && hearingAidProfile.getConnectionStatus(mDevice) ==
                BluetoothProfile.STATE_CONNECTED;
    }

    public CachedBluetoothDevice getSubDevice() {
        return mSubDevice;
    }

    public void setSubDevice(CachedBluetoothDevice subDevice) {
        mSubDevice = subDevice;
    }

    public void switchSubDeviceContent() {
        // Backup from main device
        BluetoothDevice tmpDevice = mDevice;
        short tmpRssi = mRssi;
        boolean tmpJustDiscovered = mJustDiscovered;
        // Set main device from sub device
        mDevice = mSubDevice.mDevice;
        mRssi = mSubDevice.mRssi;
        mJustDiscovered = mSubDevice.mJustDiscovered;
        // Set sub device from backup
        mSubDevice.mDevice = tmpDevice;
        mSubDevice.mRssi = tmpRssi;
        mSubDevice.mJustDiscovered = tmpJustDiscovered;
        fetchActiveDevices();
    }
}
+56 −243
Original line number Diff line number Diff line
@@ -18,8 +18,6 @@ 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;

@@ -27,12 +25,8 @@ import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices.
@@ -45,20 +39,14 @@ public class CachedBluetoothDeviceManager {
    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.
    final List<CachedBluetoothDevice> mCachedDevices = new ArrayList<CachedBluetoothDevice>();
    @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>();
    HearingAidDeviceManager mHearingAidDeviceManager;

    CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) {
        mContext = context;
        mBtManager = localBtManager;
        mHearingAidDeviceManager = new HearingAidDeviceManager(localBtManager, mCachedDevices);
    }

    public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() {
@@ -92,12 +80,13 @@ public class CachedBluetoothDeviceManager {
            if (cachedDevice.getDevice().equals(device)) {
                return cachedDevice;
            }
        }
        for (CachedBluetoothDevice notCachedDevice : mHearingAidDevicesNotAddedInCache) {
            if (notCachedDevice.getDevice().equals(device)) {
                return notCachedDevice;
            // Check sub devices if it exists
            CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
            if (subDevice != null && subDevice.getDevice().equals(device)) {
                return subDevice;
            }
        }

        return null;
    }

@@ -111,24 +100,10 @@ public class CachedBluetoothDeviceManager {
        LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
        CachedBluetoothDevice newDevice = new CachedBluetoothDevice(mContext, profileManager,
                device);
        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 {
        mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice);
        synchronized (this) {
            if (!mHearingAidDeviceManager.setSubDeviceIfNeeded(newDevice)) {
                mCachedDevices.add(newDevice);
                if (newDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID
                    && !mCachedDevicesMapForHearingAids.containsKey(newDevice.getHiSyncId())) {
                    mCachedDevicesMapForHearingAids.put(newDevice.getHiSyncId(), newDevice);
                }
                mBtManager.getEventManager().dispatchDeviceAdded(newDevice);
            }
        }
@@ -136,50 +111,19 @@ public class CachedBluetoothDeviceManager {
        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,
     * @return Device summary, or if the pair does not exist or if it is 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();
                }
            }
    public synchronized String getSubDeviceSummary(CachedBluetoothDevice device) {
        CachedBluetoothDevice subDevice = device.getSubDevice();
        if (subDevice != null && subDevice.isConnected()) {
            return subDevice.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);
        return null;
    }

    /**
@@ -187,28 +131,8 @@ public class CachedBluetoothDeviceManager {
     * 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;
        }
        final Set<Long> syncIdChangedSet = new HashSet<Long>();
        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);
                syncIdChangedSet.add(newHiSyncId);
            }
        }
        for (Long syncId : syncIdChangedSet) {
            onHiSyncIdChanged(syncId);
        }
    public synchronized void updateHearingAidsDevices() {
        mHearingAidDeviceManager.updateHearingAidsDevices();
    }

    /**
@@ -232,15 +156,21 @@ public class CachedBluetoothDeviceManager {
    }

    public synchronized void clearNonBondedDevices() {

        mCachedDevicesMapForHearingAids.entrySet().removeIf(entries
            -> entries.getValue().getBondState() == BluetoothDevice.BOND_NONE);

        clearNonBondedSubDevices();
        mCachedDevices.removeIf(cachedDevice
            -> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE);
    }

        mHearingAidDevicesNotAddedInCache.removeIf(hearingAidDevice
            -> hearingAidDevice.getBondState() == BluetoothDevice.BOND_NONE);
    private void clearNonBondedSubDevices() {
        for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
            CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
            CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
            if (subDevice != null
                    && subDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
                // Sub device exists and it is not bonded
                cachedDevice.setSubDevice(null);
            }
        }
    }

    public synchronized void onScanningStateChanged(boolean started) {
@@ -250,10 +180,10 @@ public class CachedBluetoothDeviceManager {
        for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
            CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
            cachedDevice.setJustDiscovered(false);
            final CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
            if (subDevice != null) {
                subDevice.setJustDiscovered(false);
            }
        for (int i = mHearingAidDevicesNotAddedInCache.size() - 1; i >= 0; i--) {
            CachedBluetoothDevice notCachedDevice = mHearingAidDevicesNotAddedInCache.get(i);
            notCachedDevice.setJustDiscovered(false);
        }
    }

@@ -277,21 +207,15 @@ public class CachedBluetoothDeviceManager {
        if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) {
            for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
                CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
                CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
                if (subDevice != null) {
                    if (subDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
                        cachedDevice.setSubDevice(null);
                    }
                }
                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());
                    }
                }
            }
            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);
                }
            }
        }
@@ -305,134 +229,23 @@ 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 */
                    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 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. 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);
                    firstMatchedIndex = i;
                }
            }
        }
    }

    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 boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice
            cachedDevice, int state) {
        return mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice,
                state);
    }

    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;
                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();
        CachedBluetoothDevice mainDevice = mHearingAidDeviceManager.findMainDevice(device);
        CachedBluetoothDevice subDevice = device.getSubDevice();
        if (subDevice != null) {
            // Main device is unpaired, to unpair sub device
            subDevice.unpair();
            device.setSubDevice(null);
        } else if (mainDevice != null) {
            // Sub device unpaired, to unpair main device
            mainDevice.unpair();
            mainDevice.setSubDevice(null);
        }
    }

+223 −0

File added.

Preview size limit exceeded, changes collapsed.

+1 −1
Original line number Diff line number Diff line
@@ -71,7 +71,7 @@ public class HearingAidProfile implements LocalBluetoothProfile {
            }

            // Check current list of CachedDevices to see if any are Hearing Aid devices.
            mDeviceManager.updateHearingAidsDevices(mProfileManager);
            mDeviceManager.updateHearingAidsDevices();

            mIsProfileReady=true;
        }
Loading