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

Commit 86ab16a0 authored by Katherine Lai's avatar Katherine Lai
Browse files

Update ScanManager to cache registered MSFT patterns

Some chipsets fail to register the same AdvMon pattern. Cache
the registered patterns so we only register them once.

Bug: 373753239
Bug: 365787977
Flag: com.android.bluetooth.flags.le_scan_msft_support
Test: Manually examine snoop log
Test: atest BluetoothInstrumentationTests:ScanManagerTest
Test: atest BluetoothInstrumentationTests:MsftAdvMonitorMergedPatternListTest
Change-Id: I1f5af35a8ff4812f0e9ee388c1152a73bbd6788b
parent df807e50
Loading
Loading
Loading
Loading
+30 −7
Original line number Diff line number Diff line
@@ -20,16 +20,18 @@ import android.bluetooth.le.ScanFilter;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;

/** Helper class used to manage MSFT Advertisement Monitors. */
class MsftAdvMonitor {
    /* Only pattern filtering is supported currently */
    /* Only pattern and address filtering are supported currently */
    // private static final int MSFT_CONDITION_TYPE_ALL = 0x00;
    private static final int MSFT_CONDITION_TYPE_PATTERNS = 0x01;
    // private static final int MSFT_CONDITION_TYPE_UUID = 0x02;
    // private static final int MSFT_CONDITION_TYPE_IRK = 0x03;
    // private static final int MSFT_CONDITION_TYPE_ADDRESS = 0x04;
    private static final int MSFT_CONDITION_TYPE_ADDRESS = 0x04;

    // Hardcoded values taken from CrOS defaults
    private static final byte RSSI_THRESHOLD_HIGH = (byte) 0xBF; // 191
@@ -50,6 +52,25 @@ class MsftAdvMonitor {
        public byte ad_type;
        public byte start_byte;
        public byte[] pattern;

        @Override
        public boolean equals(Object o) {
            if (o == this) {
                return true;
            }
            if (!(o instanceof Pattern other)) {
                return false;
            }

            return other.ad_type == this.ad_type
                    && other.start_byte == this.start_byte
                    && Arrays.equals(other.pattern, this.pattern);
        }

        @Override
        public int hashCode() {
            return Objects.hash(ad_type, start_byte, Arrays.hashCode(pattern));
        }
    }

    static class Address {
@@ -70,6 +91,13 @@ class MsftAdvMonitor {
        mMonitor.rssi_sampling_period = RSSI_SAMPLING_PERIOD;
        mMonitor.condition_type = MSFT_CONDITION_TYPE_PATTERNS;

        if (filter.getDeviceAddress() != null) {
            mMonitor.condition_type = MSFT_CONDITION_TYPE_ADDRESS;
            mAddress.addr_type = (byte) filter.getAddressType();
            mAddress.bd_addr = filter.getDeviceAddress();
            return;
        }

        if (filter.getServiceDataUuid() != null && dataMaskIsEmpty(filter.getServiceDataMask())) {
            Pattern pattern = new Pattern();
            pattern.ad_type = (byte) 0x16; // Bluetooth Core Spec Part A, Section 1
@@ -93,11 +121,6 @@ class MsftAdvMonitor {
            pattern.pattern = filter.getAdvertisingData();
            mPatterns.add(pattern);
        }

        if (filter.getDeviceAddress() != null) {
            mAddress.addr_type = (byte) filter.getAddressType();
            mAddress.bd_addr = filter.getDeviceAddress();
        }
    }

    Monitor getMonitor() {
+75 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.bluetooth.le_scan;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Helper class to keep track of MSFT patterns, their filter index, and number of monitors
 * registered with that pattern. Some chipsets don't support multiple monitors with the same
 * pattern. To solve that and to generally ease their task, we merge monitors with the same pattern,
 * so those monitors will only be sent once.
 */
class MsftAdvMonitorMergedPatternList {
    static class MsftAdvMonitorMergedPattern {
        private final MsftAdvMonitor.Pattern[] mPatterns;
        private final int mFilterIndex;
        private int mCount = 0;

        MsftAdvMonitorMergedPattern(MsftAdvMonitor.Pattern[] pattern, int filterIndex) {
            mPatterns = pattern;
            mFilterIndex = filterIndex;
        }
    }

    List<MsftAdvMonitorMergedPattern> mMergedPatterns = new ArrayList<>();

    // Two patterns are considered equal if they have the exact same pattern
    // in the same order. Therefore A+B and B+A are considered different, as
    // well as A and A+A. This shouldn't causes issues but could be optimized.
    // Returns merged pattern or null if not found.
    private MsftAdvMonitorMergedPattern getMergedPattern(MsftAdvMonitor.Pattern[] pattern) {
        return mMergedPatterns.stream()
                .filter(mergedPattern -> Arrays.equals(mergedPattern.mPatterns, pattern))
                .findFirst()
                .orElse(null);
    }

    // If pattern doesn't exist, creates new entry with given index.
    // If pattern exists, increases count and returns filter index.
    int add(int filterIndex, MsftAdvMonitor.Pattern[] pattern) {
        MsftAdvMonitorMergedPattern mergedPattern = (getMergedPattern(pattern));
        if (mergedPattern == null) {
            mergedPattern = new MsftAdvMonitorMergedPattern(pattern, filterIndex);
            mMergedPatterns.add(mergedPattern);
        }

        mergedPattern.mCount++;
        return mergedPattern.mFilterIndex;
    }

    // If pattern exists, decreases count. If count is 0, removes entry.
    // Returns true if there are no more instances of the given filter index
    boolean remove(int filterIndex) {
        mMergedPatterns.stream()
                .filter(pattern -> pattern.mFilterIndex == filterIndex)
                .forEach(pattern -> pattern.mCount--);
        return mMergedPatterns.removeIf(pattern -> pattern.mCount == 0);
    }
}
+48 −13
Original line number Diff line number Diff line
@@ -1011,6 +1011,9 @@ public class ScanManager {
        private final boolean mIsMsftSupported;
        // Whether or not MSFT-based scanning is currently enabled in the controller
        private boolean scanEnabledMsft = false;
        // List of merged MSFT patterns
        private final MsftAdvMonitorMergedPatternList mMsftAdvMonitorMergedPatternList =
                new MsftAdvMonitorMergedPatternList();

        ScanNative(TransitionalScanHelper scanHelper) {
            mNativeInterface = ScanObjectsFactory.getInstance().getScanNativeInterface();
@@ -1981,9 +1984,11 @@ public class ScanManager {

            Deque<Integer> clientFilterIndices = new ArrayDeque<>();
            for (ScanFilter filter : client.filters) {
                int filterIndex = mFilterIndexStack.pop();
                MsftAdvMonitor monitor = new MsftAdvMonitor(filter);

                if (monitor.getAddress().bd_addr != null) {
                    int filterIndex = mFilterIndexStack.pop();

                    resetCountDownLatch();
                    mNativeInterface.gattClientMsftAdvMonitorAdd(
                            monitor.getMonitor(),
@@ -1994,6 +1999,34 @@ public class ScanManager {

                    clientFilterIndices.add(filterIndex);
                }

                if (monitor.getPatterns().length == 0) {
                    Log.d(
                            TAG,
                            "No MSFT pattern or address was translated from client filter: "
                                    + filter);
                    continue;
                }

                // Some chipsets don't support multiple monitors with the same pattern. Skip
                // creating a new monitor if the pattern has alreaady been registered
                int filterIndex = mFilterIndexStack.pop();
                int existingFilterIndex =
                        mMsftAdvMonitorMergedPatternList.add(filterIndex, monitor.getPatterns());
                if (filterIndex == existingFilterIndex) {
                    resetCountDownLatch();
                    mNativeInterface.gattClientMsftAdvMonitorAdd(
                            monitor.getMonitor(),
                            monitor.getPatterns(),
                            monitor.getAddress(),
                            filterIndex);
                    waitForCallback();
                } else {
                    mFilterIndexStack.add(filterIndex);
                }

                clientFilterIndices.add(existingFilterIndex);
            }
            mClientFilterIndexMap.put(client.scannerId, clientFilterIndices);

            updateScanMsft();
@@ -2002,11 +2035,13 @@ public class ScanManager {
        private void removeFiltersMsft(ScanClient client) {
            Deque<Integer> clientFilterIndices = mClientFilterIndexMap.remove(client.scannerId);
            if (clientFilterIndices != null) {
                mFilterIndexStack.addAll(clientFilterIndices);
                for (int filterIndex : clientFilterIndices) {
                    if (mMsftAdvMonitorMergedPatternList.remove(filterIndex)) {
                        resetCountDownLatch();
                        mNativeInterface.gattClientMsftAdvMonitorRemove(filterIndex);
                        waitForCallback();
                        mFilterIndexStack.add(filterIndex);
                    }
                }
            }

+74 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.bluetooth.le_scan;

import static com.google.common.truth.Truth.assertThat;

import android.bluetooth.le.ScanFilter;
import android.os.ParcelUuid;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.util.UUID;

/** Test cases for {@link MsftAdvMonitorMergedPatternList}. */
@RunWith(JUnit4.class)
public final class MsftAdvMonitorMergedPatternListTest {
    private static final ParcelUuid SERVICE_DATA_UUID =
            new ParcelUuid(UUID.fromString("01234567-890A-BCDE-F123-4567890ABCDE"));
    private static final byte[] SERVICE_DATA = new byte[] {0x01, 0x02, 0x03};

    private static final ParcelUuid ANOTHER_SERVICE_DATA_UUID =
            new ParcelUuid(UUID.fromString("12345678-90AB-CDEF-1234-567890ABCDEF"));

    @Test
    public void testAddandRemove() {
        MsftAdvMonitorMergedPatternList patternList = new MsftAdvMonitorMergedPatternList();
        int filterIndex = 0;
        int addedFilterIndex = filterIndex;

        // Ensure returned filter index is the same as passed filter index
        MsftAdvMonitor monitor =
                new MsftAdvMonitor(
                        new ScanFilter.Builder()
                                .setServiceData(SERVICE_DATA_UUID, SERVICE_DATA)
                                .build());
        assertThat(patternList.add(filterIndex, monitor.getPatterns())).isEqualTo(filterIndex);

        // Add a different pattern and ensure returned filter index is the same as passed filter
        // index
        filterIndex++;
        MsftAdvMonitor anotherMonitor =
                new MsftAdvMonitor(
                        new ScanFilter.Builder()
                                .setServiceData(ANOTHER_SERVICE_DATA_UUID, SERVICE_DATA)
                                .build());
        assertThat(patternList.add(filterIndex, anotherMonitor.getPatterns()))
                .isEqualTo(filterIndex);

        // Add the same first pattern with different filter index and confirm previous filter index
        // was returned
        filterIndex++;
        assertThat(patternList.add(filterIndex, monitor.getPatterns())).isEqualTo(addedFilterIndex);

        // Only removing the last filter index should result in successful removal
        assertThat(patternList.remove(addedFilterIndex)).isEqualTo(false);
        assertThat(patternList.remove(addedFilterIndex)).isEqualTo(true);
    }
}
+51 −9
Original line number Diff line number Diff line
@@ -67,6 +67,7 @@ import android.os.BatteryStatsManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.Message;
import android.os.ParcelUuid;
import android.os.SystemProperties;
import android.os.WorkSource;
import android.os.test.TestLooper;
@@ -110,6 +111,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

/** Test cases for {@link ScanManager}. */
@SmallTest
@@ -283,13 +285,12 @@ public class ScanManagerTest {

    private ScanClient createScanClient(
            boolean isFiltered,
            boolean isEmptyFilter,
            int scanMode,
            boolean isBatch,
            boolean isAutoBatch,
            int appUid,
            AppScanStats appScanStats) {
        List<ScanFilter> scanFilterList = createScanFilterList(isFiltered, isEmptyFilter);
            AppScanStats appScanStats,
            List<ScanFilter> scanFilterList) {
        ScanSettings scanSettings = createScanSettings(scanMode, isBatch, isAutoBatch);

        mClientId = mClientId + 1;
@@ -300,6 +301,19 @@ public class ScanManagerTest {
        return client;
    }

    private ScanClient createScanClient(
            boolean isFiltered,
            boolean isEmptyFilter,
            int scanMode,
            boolean isBatch,
            boolean isAutoBatch,
            int appUid,
            AppScanStats appScanStats) {
        List<ScanFilter> scanFilterList = createScanFilterList(isFiltered, isEmptyFilter);
        return createScanClient(
                isFiltered, scanMode, isBatch, isAutoBatch, appUid, appScanStats, scanFilterList);
    }

    private ScanClient createScanClient(boolean isFiltered, int scanMode) {
        return createScanClient(
                isFiltered,
@@ -1883,7 +1897,9 @@ public class ScanManagerTest {
        doReturn(false).when(mBluetoothAdapterProxy).isOffloadedScanFilteringSupported();

        final boolean isFiltered = true;
        final boolean isEmptyFilter = false;
        final ParcelUuid serviceUuid =
                new ParcelUuid(UUID.fromString("12345678-90AB-CDEF-1234-567890ABCDEF"));
        final byte[] serviceData = new byte[] {0x01, 0x02, 0x03};

        boolean isMsftEnabled = SystemProperties.getBoolean(MSFT_HCI_EXT_ENABLED, false);
        SystemProperties.set(MSFT_HCI_EXT_ENABLED, Boolean.toString(true));
@@ -1900,19 +1916,45 @@ public class ScanManagerTest {

            // Turn on screen
            sendMessageWaitForProcessed(createScreenOnOffMessage(true));
            // Create scan client
            ScanClient client = createScanClient(isFiltered, isEmptyFilter, SCAN_MODE_LOW_POWER);
            // Create scan client with service data
            List<ScanFilter> scanFilterList =
                    List.of(
                            new ScanFilter.Builder()
                                    .setServiceData(serviceUuid, serviceData)
                                    .build());
            ScanClient client =
                    createScanClient(
                            isFiltered,
                            SCAN_MODE_LOW_POWER,
                            false,
                            false,
                            Binder.getCallingUid(),
                            mMockAppScanStats,
                            scanFilterList);
            // Start scan
            sendMessageWaitForProcessed(createStartStopScanMessage(true, client));

            // Verify MSFT APIs
            verify(mScanNativeInterface, atLeastOnce())
            // Create another scan client with the same service data
            ScanClient anotherClient =
                    createScanClient(
                            isFiltered,
                            SCAN_MODE_LOW_POWER,
                            false,
                            false,
                            Binder.getCallingUid(),
                            mMockAppScanStats,
                            scanFilterList);
            // Start scan
            sendMessageWaitForProcessed(createStartStopScanMessage(true, anotherClient));

            // Verify MSFT APIs are only called once
            verify(mScanNativeInterface)
                    .gattClientMsftAdvMonitorAdd(
                            any(MsftAdvMonitor.Monitor.class),
                            any(MsftAdvMonitor.Pattern[].class),
                            any(MsftAdvMonitor.Address.class),
                            anyInt());
            verify(mScanNativeInterface, atLeastOnce()).gattClientMsftAdvMonitorEnable(eq(true));
            verify(mScanNativeInterface).gattClientMsftAdvMonitorEnable(eq(true));
        } finally {
            SystemProperties.set(MSFT_HCI_EXT_ENABLED, Boolean.toString(isMsftEnabled));
        }