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

Commit e7a4b1f1 authored by Angela Wang's avatar Angela Wang
Browse files

Shows LE audio hearing aid device in a11y settings page (3/3)

Shows connected hearing aid in a11y settings page whether it's ASHA hearing aid or LE audio hearing aid. Makes sure preference summary is updated as expected when BluetoothHapClient profile connects or disconnects.

LE audio hearing aid will be a CSIP set. According to the CSIP spec, we will show the device is active without side information if there is only one device in the CSIP set and show the device is active left and right if both side of hearing aids are in the CSIP set.

Bug: 249235823
Test: make RunSettingsRoboTests ROBOTEST_FILTER=AccessibilityHearingAidPreferenceControllerTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=HearingAidPairingDialogFragmentTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=HearingAidUtilsTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=AvailableMediaBluetoothDeviceUpdaterTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=BluetoothDetailsPairOtherControllerTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=BluetoothDetailsRelatedToolsControllerTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=ConnectedBluetoothDeviceUpdaterTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=AvailableMediaDeviceGroupControllerTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=BluetoothDevicesSliceTest
Change-Id: Ifd66cbd81481a1eb94613d89dfd8df16b8c43ae8
parent 00b549f7
Loading
Loading
Loading
Loading
+76 −53
Original line number Diff line number Diff line
@@ -19,7 +19,9 @@ package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHapClient;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -41,6 +43,7 @@ import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.HapClientProfile;
import com.android.settingslib.bluetooth.HearingAidProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
@@ -48,9 +51,12 @@ import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.stream.Collectors;

/**
 * Controller that shows and updates the bluetooth device name
@@ -64,23 +70,7 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC
    private final BroadcastReceiver mHearingAidChangedReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
                final int state = intent.getIntExtra(BluetoothHearingAid.EXTRA_STATE,
                        BluetoothHearingAid.STATE_DISCONNECTED);
                if (state == BluetoothHearingAid.STATE_CONNECTED) {
            updateState(mHearingAidPreference);
                } else {
                    mHearingAidPreference
                            .setSummary(R.string.accessibility_hearingaid_not_connected_summary);
                }
            } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
                final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
                        BluetoothAdapter.ERROR);
                if (state != BluetoothAdapter.STATE_ON) {
                    mHearingAidPreference
                            .setSummary(R.string.accessibility_hearingaid_not_connected_summary);
                }
            }
        }
    };

@@ -107,18 +97,25 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC

    @Override
    public int getAvailabilityStatus() {
        return isHearingAidProfileSupported() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
        return isHearingAidSupported() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
    }

    @Override
    public void onStart() {
        IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
        filter.addAction(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED);
        filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
        mContext.registerReceiver(mHearingAidChangedReceiver, filter);
        mLocalBluetoothManager.getEventManager().registerCallback(this);
        // Can't get connected hearing aids when hearing aids related profiles are not ready. The
        // profiles will be ready after the services are connected. Needs to add listener and
        // updates the information when all hearing aids related services are connected.
        if (isAnyHearingAidRelatedProfilesNotReady()) {
            mProfileManager.addServiceListener(this);
        }
    }

    @Override
    public void onStop() {
@@ -143,6 +140,9 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC

    @Override
    public CharSequence getSummary() {
        if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
            return mContext.getText(R.string.accessibility_hearingaid_not_connected_summary);
        }
        final CachedBluetoothDevice device = getConnectedHearingAidDevice();
        if (device == null) {
            return mContext.getText(R.string.accessibility_hearingaid_not_connected_summary);
@@ -150,26 +150,39 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC

        final int connectedNum = getConnectedHearingAidDeviceNum();
        final CharSequence name = device.getName();
        final int side = device.getDeviceSide();
        final CachedBluetoothDevice subDevice = device.getSubDevice();
        if (connectedNum > 1) {
            return mContext.getString(R.string.accessibility_hearingaid_more_device_summary, name);
        }

        // Check if another side of LE audio hearing aid is connected as a pair
        final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
        if (memberDevices.stream().anyMatch(m -> m.isConnected())) {
            return mContext.getString(
                    R.string.accessibility_hearingaid_left_and_right_side_device_summary,
                    name);
        }

        // Check if another side of ASHA hearing aid is connected as a pair
        final CachedBluetoothDevice subDevice = device.getSubDevice();
        if (subDevice != null && subDevice.isConnected()) {
            return mContext.getString(
                    R.string.accessibility_hearingaid_left_and_right_side_device_summary, name);
        }
        if (side == HearingAidProfile.DeviceSide.SIDE_INVALID) {

        final int side = device.getDeviceSide();
        if (side == HearingAidProfile.DeviceSide.SIDE_LEFT) {
            return mContext.getString(
                    R.string.accessibility_hearingaid_left_side_device_summary, name);
        } else if (side == HearingAidProfile.DeviceSide.SIDE_RIGHT) {
            return mContext.getString(
                    R.string.accessibility_hearingaid_active_device_summary, name);
        }
        return (side == HearingAidProfile.DeviceSide.SIDE_LEFT)
                ? mContext.getString(
                        R.string.accessibility_hearingaid_left_side_device_summary, name)
                : mContext.getString(
                    R.string.accessibility_hearingaid_right_side_device_summary, name);
        }

        // Invalid side
        return mContext.getString(
                R.string.accessibility_hearingaid_active_device_summary, name);
    }

    @Override
    public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
        if (activeDevice == null) {
@@ -183,10 +196,7 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC

    @Override
    public void onServiceConnected() {
        // Every registered ProfileService will callback. So we need to use isProfileReady() to
        // check is the HearingAidService callback here, not other service.
        // When hearing aids service connected, updating the UI.
        if (mProfileManager.getHearingAidProfile().isProfileReady()) {
        if (!isAnyHearingAidRelatedProfilesNotReady()) {
            updateState(mHearingAidPreference);
            mProfileManager.removeServiceListener(this);
        }
@@ -203,38 +213,51 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC

    @VisibleForTesting
    CachedBluetoothDevice getConnectedHearingAidDevice() {
        if (!isHearingAidProfileSupported()) {
            return null;
        }

        final HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile();
        final List<BluetoothDevice> deviceList = hearingAidProfile.getConnectedDevices();
        for (BluetoothDevice obj : deviceList) {
            if (!mCachedDeviceManager.isSubDevice(obj)) {
                return mCachedDeviceManager.findDevice(obj);
            }
        }
        return null;
        final List<BluetoothDevice> deviceList = getConnectedHearingAidDeviceList();
        return deviceList.isEmpty() ? null : mCachedDeviceManager.findDevice(deviceList.get(0));
    }

    private int getConnectedHearingAidDeviceNum() {
        if (!isHearingAidProfileSupported()) {
            return 0;
        return getConnectedHearingAidDeviceList().size();
    }

    private List<BluetoothDevice> getConnectedHearingAidDeviceList() {
        if (!isHearingAidSupported()) {
            return new ArrayList<>();
        }
        final List<BluetoothDevice> deviceList = new ArrayList<>();
        final HapClientProfile hapClientProfile = mProfileManager.getHapClientProfile();
        if (hapClientProfile != null) {
            deviceList.addAll(hapClientProfile.getConnectedDevices());
        }
        final HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile();
        final List<BluetoothDevice> deviceList = hearingAidProfile.getConnectedDevices();
        return (int) deviceList.stream()
                .filter(device -> !mCachedDeviceManager.isSubDevice(device))
                .count();
        if (hearingAidProfile != null) {
            deviceList.addAll(hearingAidProfile.getConnectedDevices());
        }
        return deviceList.stream()
                .distinct()
                .filter(d -> !mCachedDeviceManager.isSubDevice(d)).collect(Collectors.toList());
    }

    private boolean isHearingAidProfileSupported() {
    private boolean isHearingAidSupported() {
        if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
            return false;
        }
        final List<Integer> supportedList = mBluetoothAdapter.getSupportedProfiles();
        return supportedList.contains(BluetoothProfile.HEARING_AID);
        return supportedList.contains(BluetoothProfile.HEARING_AID)
                || supportedList.contains(BluetoothProfile.HAP_CLIENT);
    }

    private boolean isAnyHearingAidRelatedProfilesNotReady() {
        HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile();
        if (hearingAidProfile != null && !hearingAidProfile.isProfileReady()) {
            return true;
        }
        HapClientProfile hapClientProfile = mProfileManager.getHapClientProfile();
        if (hapClientProfile != null && !hapClientProfile.isProfileReady()) {
            return true;
        }
        return false;
    }

    private LocalBluetoothManager getLocalBluetoothManager() {
+1 −1
Original line number Diff line number Diff line
@@ -40,7 +40,7 @@ public final class HearingAidUtils {
     */
    public static void launchHearingAidPairingDialog(FragmentManager fragmentManager,
            @NonNull CachedBluetoothDevice device) {
        if (device.isConnectedHearingAidDevice()
        if (device.isConnectedAshaHearingAidDevice()
                && device.getDeviceMode() == HearingAidProfile.DeviceMode.MODE_BINAURAL
                && device.getSubDevice() == null) {
            launchHearingAidPairingDialogInternal(fragmentManager, device);
+1 −1
Original line number Diff line number Diff line
@@ -72,7 +72,7 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater
            }
            // If device is Hearing Aid or LE Audio, it is compatible with HFP and A2DP.
            // It would show in Available Devices group.
            if (cachedDevice.isConnectedHearingAidDevice()
            if (cachedDevice.isConnectedAshaHearingAidDevice()
                    || cachedDevice.isConnectedLeAudioDevice()) {
                Log.d(TAG, "isFilterMatched() device : " +
                        cachedDevice.getName() + ", the profile is connected.");
+2 −2
Original line number Diff line number Diff line
@@ -107,12 +107,12 @@ public class BluetoothDetailsPairOtherController extends BluetoothDetailsControl
    }

    private boolean isOnlyOneSideConnected(CachedBluetoothDevice cachedDevice) {
        if (!cachedDevice.isConnectedHearingAidDevice()) {
        if (!cachedDevice.isConnectedAshaHearingAidDevice()) {
            return false;
        }

        final CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
        if (subDevice != null && subDevice.isConnectedHearingAidDevice()) {
        if (subDevice != null && subDevice.isConnectedAshaHearingAidDevice()) {
            return false;
        }

+1 −1
Original line number Diff line number Diff line
@@ -72,7 +72,7 @@ public class ConnectedBluetoothDeviceUpdater extends BluetoothDeviceUpdater {
            }
            // If device is Hearing Aid or LE Audio, it is compatible with HFP and A2DP.
            // It would not show in Connected Devices group.
            if (cachedDevice.isConnectedHearingAidDevice()
            if (cachedDevice.isConnectedAshaHearingAidDevice()
                    || cachedDevice.isConnectedLeAudioDevice()) {
                return false;
            }
Loading