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

Commit 031c5f03 authored by jasonwshsu's avatar jasonwshsu
Browse files

[Pair hearing devices] Add pair hearing device functionality

* Add setFilter(List<ScanFilter>) in DeviceListPreferenceFragment to enable BluetoothLeScanner

Bug: 237625815
Test: make RunSettingsRoboTests ROBOTEST_FILTER=DeviceListPreferenceFragmentTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=HearingDevicePairingDetailTest
Change-Id: I13495cad7260789845fad9a7e77e96b692a5cbd0
parent 723c385c
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -4557,7 +4557,7 @@
    <!-- Title for the pair hearing device page. [CHAR LIMIT=25] -->
    <string name="accessibility_hearing_device_pairing_page_title">Pair hearing device</string>
    <!-- Title for the preference category containing the list of the available hearing during and after bluetooth scanning devices. [CHAR LIMIT=30] -->
    <string name="accessibility_found_hearing_devices">Available devices</string>
    <string name="accessibility_found_hearing_devices">Available hearing devices</string>
    <!-- Title for the preference category containing the all bluetooth devices during and after bluetooth scanning devices. Used when people can not find their hearing device in hearing device pairing list. [CHAR LIMIT=45] -->
    <string name="accessibility_found_all_devices">Don\u2019t see your hearing device?</string>
    <!-- Title for listing all bluetooth devices preference in the accessibility page. [CHAR LIMIT=40] -->
+3 −1
Original line number Diff line number Diff line
@@ -29,8 +29,10 @@
        android:title="@string/bluetooth_pairing_pref_title"
        android:icon="@drawable/ic_add_24dp"
        android:summary="@string/connected_device_add_device_summary"
        android:fragment="com.android.settings.accessibility.HearingDevicePairingDetail"
        settings:userRestriction="no_config_bluetooth"
        settings:useAdminDisabledSummary="true" />
        settings:useAdminDisabledSummary="true"
        settings:controller="com.android.settings.connecteddevice.AddDevicePreferenceController"/>

    <PreferenceCategory
        android:key="previously_connected_hearing_devices"
+42 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2023 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:settings="http://schemas.android.com/apk/res-auto"
    android:title="@string/bluetooth_pairing_pref_title">

    <com.android.settings.bluetooth.BluetoothProgressCategory
        android:key="available_hearing_devices"
        android:title="@string/accessibility_found_hearing_devices" />

    <PreferenceCategory
        android:key="device_control_category"
        android:title="@string/accessibility_found_all_devices">
        <com.android.settingslib.RestrictedPreference
            android:key="add_bt_devices"
            android:title="@string/accessibility_list_all_devices_title"
            android:fragment="com.android.settings.bluetooth.BluetoothPairingDetail"
            settings:userRestriction="no_config_bluetooth"
            settings:useAdminDisabledSummary="true" />
    </PreferenceCategory>

    <com.android.settings.accessibility.AccessibilityFooterPreference
        android:key="hearing_device_footer"
        android:title="@string/accessibility_hearing_device_footer_summary"
        android:selectable="false"
        settings:searchable="false" />

</PreferenceScreen>
 No newline at end of file
+82 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.accessibility;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.le.ScanFilter;

import androidx.annotation.VisibleForTesting;

import com.android.settings.R;
import com.android.settings.bluetooth.BluetoothDevicePairingDetailBase;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;

import java.util.Collections;

/**
 * HearingDevicePairingDetail is a page to scan hearing devices. This page shows scanning icons and
 * pairing them.
 */
public class HearingDevicePairingDetail extends BluetoothDevicePairingDetailBase {

    private static final String TAG = "HearingDevicePairingDetail";
    @VisibleForTesting
    static final String KEY_AVAILABLE_HEARING_DEVICES = "available_hearing_devices";

    public HearingDevicePairingDetail() {
        super();
        final ScanFilter filter = new ScanFilter.Builder()
                .setServiceData(BluetoothUuid.HEARING_AID, new byte[]{0}, new byte[]{0})
                .build();
        setFilter(Collections.singletonList(filter));
    }

    @Override
    public void onStart() {
        super.onStart();
        mAvailableDevicesCategory.setProgress(mBluetoothAdapter.isEnabled());
    }

    @Override
    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
        super.onDeviceBondStateChanged(cachedDevice, bondState);

        mAvailableDevicesCategory.setProgress(bondState == BluetoothDevice.BOND_NONE);
    }

    @Override
    public int getMetricsCategory() {
        // TODO(b/262839191): To be updated settings_enums.proto
        return 0;
    }

    @Override
    protected int getPreferenceScreenResId() {
        return R.xml.hearing_device_pairing_detail;
    }

    @Override
    protected String getLogTag() {
        return TAG;
    }

    @Override
    public String getDeviceListKey() {
        return KEY_AVAILABLE_HEARING_DEVICES;
    }
}
+90 −43
Original line number Diff line number Diff line
@@ -18,6 +18,11 @@ package com.android.settings.bluetooth;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.os.Bundle;
import android.os.SystemProperties;
import android.text.BidiFormatter;
@@ -33,6 +38,7 @@ import com.android.settings.dashboard.RestrictedDashboardFragment;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;

import java.util.ArrayList;
@@ -59,37 +65,51 @@ public abstract class DeviceListPreferenceFragment extends
            "persist.bluetooth.showdeviceswithoutnames";

    private BluetoothDeviceFilter.Filter mFilter;
    private List<ScanFilter> mLeScanFilters;
    private ScanCallback mScanCallback;

    @VisibleForTesting
    boolean mScanEnabled;
    protected boolean mScanEnabled;

    BluetoothDevice mSelectedDevice;
    protected BluetoothDevice mSelectedDevice;

    BluetoothAdapter mBluetoothAdapter;
    LocalBluetoothManager mLocalManager;
    protected BluetoothAdapter mBluetoothAdapter;
    protected LocalBluetoothManager mLocalManager;
    protected CachedBluetoothDeviceManager mCachedDeviceManager;

    @VisibleForTesting
    PreferenceGroup mDeviceListGroup;
    protected PreferenceGroup mDeviceListGroup;

    final HashMap<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap =
    protected final HashMap<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap =
            new HashMap<>();
    final List<BluetoothDevice> mSelectedList = new ArrayList<>();
    protected final List<BluetoothDevice> mSelectedList = new ArrayList<>();

    boolean mShowDevicesWithoutNames;
    protected boolean mShowDevicesWithoutNames;

    DeviceListPreferenceFragment(String restrictedKey) {
    public DeviceListPreferenceFragment(String restrictedKey) {
        super(restrictedKey);
        mFilter = BluetoothDeviceFilter.ALL_FILTER;
    }

    final void setFilter(BluetoothDeviceFilter.Filter filter) {
    protected final void setFilter(BluetoothDeviceFilter.Filter filter) {
        mFilter = filter;
    }

    final void setFilter(int filterType) {
    protected final void setFilter(int filterType) {
        mFilter = BluetoothDeviceFilter.getFilter(filterType);
    }

    /**
     * Sets the bluetooth device scanning filter with {@link ScanFilter}s. It will change to start
     * {@link BluetoothLeScanner} which will scan BLE device only.
     *
     * @param leScanFilters list of settings to filter scan result
     */
    protected void setFilter(List<ScanFilter> leScanFilters) {
        mFilter = null;
        mLeScanFilters = leScanFilters;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
@@ -100,6 +120,7 @@ public abstract class DeviceListPreferenceFragment extends
            return;
        }
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        mCachedDeviceManager = mLocalManager.getCachedDeviceManager();
        mShowDevicesWithoutNames = SystemProperties.getBoolean(
                BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY, false);

@@ -109,7 +130,7 @@ public abstract class DeviceListPreferenceFragment extends
    }

    /** find and update preference that already existed in preference screen */
    abstract void initPreferencesFromPreferenceScreen();
    protected abstract void initPreferencesFromPreferenceScreen();

    @Override
    public void onStart() {
@@ -139,7 +160,7 @@ public abstract class DeviceListPreferenceFragment extends

    void addCachedDevices() {
        Collection<CachedBluetoothDevice> cachedDevices =
                mLocalManager.getCachedDeviceManager().getCachedDevicesCopy();
                mCachedDeviceManager.getCachedDevicesCopy();
        for (CachedBluetoothDevice cachedDevice : cachedDevices) {
            onDeviceAdded(cachedDevice);
        }
@@ -164,7 +185,7 @@ public abstract class DeviceListPreferenceFragment extends
        return super.onPreferenceTreeClick(preference);
    }

    void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
    protected void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
        btPreference.onClicked();
    }

@@ -177,7 +198,8 @@ public abstract class DeviceListPreferenceFragment extends
        // Prevent updates while the list shows one of the state messages
        if (mBluetoothAdapter.getState() != BluetoothAdapter.STATE_ON) return;

        if (mFilter.matches(cachedDevice.getDevice())) {
        if (mLeScanFilters != null
                || (mFilter != null && mFilter.matches(cachedDevice.getDevice()))) {
            createDevicePreference(cachedDevice);
        }
    }
@@ -227,7 +249,7 @@ public abstract class DeviceListPreferenceFragment extends
    }

    @VisibleForTesting
    void enableScanning() {
    protected void enableScanning() {
        // BluetoothAdapter already handles repeated scan requests
        if (!mScanEnabled) {
            startScanning();
@@ -236,7 +258,7 @@ public abstract class DeviceListPreferenceFragment extends
    }

    @VisibleForTesting
    void disableScanning() {
    protected void disableScanning() {
        if (mScanEnabled) {
            stopScanning();
            mScanEnabled = false;
@@ -250,31 +272,6 @@ public abstract class DeviceListPreferenceFragment extends
        }
    }

    /**
     * Add bluetooth device preferences to {@code preferenceGroup} which satisfy the {@code filter}
     *
     * This method will also (1) set the title for {@code preferenceGroup} and (2) change the
     * default preferenceGroup and filter
     * @param preferenceGroup
     * @param titleId
     * @param filter
     * @param addCachedDevices
     */
    public void addDeviceCategory(PreferenceGroup preferenceGroup, int titleId,
            BluetoothDeviceFilter.Filter filter, boolean addCachedDevices) {
        cacheRemoveAllPrefs(preferenceGroup);
        preferenceGroup.setTitle(titleId);
        mDeviceListGroup = preferenceGroup;
        if (addCachedDevices) {
            // Don't show bonded devices when screen turned back on
            setFilter(BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER);
            addCachedDevices();
        }
        setFilter(filter);
        preferenceGroup.setEnabled(true);
        removeCachedPrefs(preferenceGroup);
    }

    /**
     * Return the key of the {@link PreferenceGroup} that contains the bluetooth devices
     */
@@ -284,15 +281,65 @@ public abstract class DeviceListPreferenceFragment extends
        return mShowDevicesWithoutNames;
    }

    @VisibleForTesting
    void startScanning() {
        if (mFilter != null) {
            startClassicScanning();
        } else if (mLeScanFilters != null) {
            startLeScanning();
        }

    }

    @VisibleForTesting
    void stopScanning() {
        if (mFilter != null) {
            stopClassicScanning();
        } else if (mLeScanFilters != null) {
            stopLeScanning();
        }
    }

    private void startClassicScanning() {
        if (!mBluetoothAdapter.isDiscovering()) {
            mBluetoothAdapter.startDiscovery();
        }
    }

    void stopScanning() {
    private void stopClassicScanning() {
        if (mBluetoothAdapter.isDiscovering()) {
            mBluetoothAdapter.cancelDiscovery();
        }
    }

    private void startLeScanning() {
        final BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
        final ScanSettings settings = new ScanSettings.Builder()
                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                .build();
        mScanCallback = new ScanCallback() {
            @Override
            public void onScanResult(int callbackType, ScanResult result) {
                final BluetoothDevice device = result.getDevice();
                CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(device);
                if (cachedDevice == null) {
                    cachedDevice = mCachedDeviceManager.addDevice(device);
                }
                onDeviceAdded(cachedDevice);
            }

            @Override
            public void onScanFailed(int errorCode) {
                Log.w(TAG, "BLE Scan failed with error code " + errorCode);
            }
        };
        scanner.startScan(mLeScanFilters, settings, mScanCallback);
    }

    private void stopLeScanning() {
        final BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
        if (scanner != null) {
            scanner.stopScan(mScanCallback);
        }
    }
}
Loading