blob: 9b881af27496145f9e7a9522e8e349b5555ee17f [file] [log] [blame]
/*
* 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.google.android.chre.test.bleconcurrency;
import static com.google.common.truth.Truth.assertThat;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.hardware.location.NanoAppBinary;
import com.google.android.chre.test.chqts.ContextHubChreApiTestExecutor;
import com.google.android.utils.chre.ChreApiTestUtil;
import com.google.common.collect.ImmutableList;
import com.google.protobuf.ByteString;
import org.junit.Assert;
import java.util.HexFormat;
import java.util.List;
import dev.chre.rpc.proto.ChreApiTest;
/**
* A class that can execute the CHRE BLE concurrency test.
*/
public class ContextHubBleConcurrencyTestExecutor extends ContextHubChreApiTestExecutor {
private static final String TAG = "ContextHubBleConcurrencyTestExecutor";
/**
* The delay to report results in milliseconds.
*/
private static final int REPORT_DELAY_MS = 0;
/**
* The RSSI threshold for the BLE scan filter.
*/
private static final int RSSI_THRESHOLD = -128;
/**
* The advertisement type for service data.
*/
private static final int CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16 = 0x16;
/**
* CHRE BLE capabilities and filter capabilities.
*/
private static final int CHRE_BLE_CAPABILITIES_SCAN = 1 << 0;
private static final int CHRE_BLE_FILTER_CAPABILITIES_SERVICE_DATA = 1 << 7;
private BluetoothLeScanner mBluetoothLeScanner = null;
private final ScanCallback mScanCallback = new ScanCallback() {
@Override
public void onBatchScanResults(List<ScanResult> results) {
// do nothing
}
@Override
public void onScanFailed(int errorCode) {
Assert.fail("Failed to start a BLE scan on the host");
}
@Override
public void onScanResult(int callbackType, ScanResult result) {
// do nothing
}
};
public ContextHubBleConcurrencyTestExecutor(NanoAppBinary nanoapp) {
super(nanoapp);
BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
assertThat(bluetoothManager).isNotNull();
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
if (bluetoothAdapter != null) {
mBluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
}
}
/**
* Runs the test.
*/
public void run() throws Exception {
if (doesNecessaryBleCapabilitiesExist()) {
testHostScanFirst();
Thread.sleep(1000);
testChreScanFirst();
}
}
/**
* Generates a BLE scan filter that filters only for the known Google beacons:
* Google Eddystone and Nearby Fastpair.
*/
private static ChreApiTest.ChreBleScanFilter getDefaultScanFilter() {
ChreApiTest.ChreBleGenericFilter eddystoneFilter =
ChreApiTest.ChreBleGenericFilter.newBuilder()
.setType(CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16)
.setLength(2)
.setData(ByteString.copyFrom(HexFormat.of().parseHex("FEAA")))
.setMask(ByteString.copyFrom(HexFormat.of().parseHex("FFFF")))
.build();
ChreApiTest.ChreBleGenericFilter nearbyFastpairFilter =
ChreApiTest.ChreBleGenericFilter.newBuilder()
.setType(CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16)
.setLength(2)
.setData(ByteString.copyFrom(HexFormat.of().parseHex("FE2C")))
.setMask(ByteString.copyFrom(HexFormat.of().parseHex("FFFF")))
.build();
return ChreApiTest.ChreBleScanFilter.newBuilder()
.setRssiThreshold(RSSI_THRESHOLD)
.setScanFilterCount(2)
.addScanFilters(eddystoneFilter)
.addScanFilters(nearbyFastpairFilter)
.build();
}
/**
* Generates a BLE scan filter that filters only for the known Google beacons:
* Google Eddystone and Nearby Fastpair. We specify the filter data in (little-endian) LE
* here as the CHRE code will take BE input and transform it to LE.
*/
private static List<ScanFilter> getDefaultScanFilterHost() {
assertThat(CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16)
.isEqualTo(ScanRecord.DATA_TYPE_SERVICE_DATA_16_BIT);
ScanFilter scanFilter = new ScanFilter.Builder()
.setAdvertisingDataTypeWithData(
ScanRecord.DATA_TYPE_SERVICE_DATA_16_BIT,
ByteString.copyFrom(HexFormat.of().parseHex("AAFE")).toByteArray(),
ByteString.copyFrom(HexFormat.of().parseHex("FFFF")).toByteArray())
.build();
ScanFilter scanFilter2 = new ScanFilter.Builder()
.setAdvertisingDataTypeWithData(
ScanRecord.DATA_TYPE_SERVICE_DATA_16_BIT,
ByteString.copyFrom(HexFormat.of().parseHex("2CFE")).toByteArray(),
ByteString.copyFrom(HexFormat.of().parseHex("FFFF")).toByteArray())
.build();
return ImmutableList.of(scanFilter, scanFilter2);
}
/**
* Starts a BLE scan and asserts it was started successfully in a synchronous manner.
* This waits for the event to be received and returns the status in the event.
*
* @param scanFilter The scan filter.
*/
private void chreBleStartScanSync(ChreApiTest.ChreBleScanFilter scanFilter) throws Exception {
ChreApiTest.ChreBleStartScanAsyncInput.Builder inputBuilder =
ChreApiTest.ChreBleStartScanAsyncInput.newBuilder()
.setMode(ChreApiTest.ChreBleScanMode.CHRE_BLE_SCAN_MODE_FOREGROUND)
.setReportDelayMs(REPORT_DELAY_MS)
.setHasFilter(scanFilter != null);
if (scanFilter != null) {
inputBuilder.setFilter(scanFilter);
}
ChreApiTestUtil util = new ChreApiTestUtil();
List<ChreApiTest.GeneralSyncMessage> response =
util.callServerStreamingRpcMethodSync(getRpcClient(),
"chre.rpc.ChreApiTestService.ChreBleStartScanSync",
inputBuilder.build());
assertThat(response).isNotEmpty();
for (ChreApiTest.GeneralSyncMessage status: response) {
assertThat(status.getStatus()).isTrue();
}
}
/**
* Stops a BLE scan and asserts it was started successfully in a synchronous manner.
* This waits for the event to be received and returns the status in the event.
*/
private void chreBleStopScanSync() throws Exception {
ChreApiTestUtil util = new ChreApiTestUtil();
List<ChreApiTest.GeneralSyncMessage> response =
util.callServerStreamingRpcMethodSync(getRpcClient(),
"chre.rpc.ChreApiTestService.ChreBleStopScanSync");
assertThat(response).isNotEmpty();
for (ChreApiTest.GeneralSyncMessage status: response) {
assertThat(status.getStatus()).isTrue();
}
}
/**
* Returns true if the required BLE capabilities and filter capabilities exist,
* otherwise returns false.
*/
private boolean doesNecessaryBleCapabilitiesExist() throws Exception {
if (mBluetoothLeScanner == null) {
return false;
}
ChreApiTest.Capabilities capabilitiesResponse =
ChreApiTestUtil.callUnaryRpcMethodSync(getRpcClient(),
"chre.rpc.ChreApiTestService.ChreBleGetCapabilities");
int capabilities = capabilitiesResponse.getCapabilities();
if ((capabilities & CHRE_BLE_CAPABILITIES_SCAN) != 0) {
ChreApiTest.Capabilities filterCapabilitiesResponse =
ChreApiTestUtil.callUnaryRpcMethodSync(getRpcClient(),
"chre.rpc.ChreApiTestService.ChreBleGetFilterCapabilities");
int filterCapabilities = filterCapabilitiesResponse.getCapabilities();
return (filterCapabilities & CHRE_BLE_FILTER_CAPABILITIES_SERVICE_DATA) != 0;
}
return false;
}
/**
* Starts a BLE scan on the host side with known Google beacon filters.
*/
private void startBleScanOnHost() {
ScanSettings scanSettings = new ScanSettings.Builder()
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build();
mBluetoothLeScanner.startScan(getDefaultScanFilterHost(),
scanSettings, mScanCallback);
}
/**
* Stops a BLE scan on the host side.
*/
private void stopBleScanOnHost() {
mBluetoothLeScanner.stopScan(mScanCallback);
}
/**
* Tests with the host starting scanning first.
*/
private void testHostScanFirst() throws Exception {
startBleScanOnHost();
chreBleStartScanSync(getDefaultScanFilter());
Thread.sleep(1000);
chreBleStopScanSync();
stopBleScanOnHost();
}
/**
* Tests with CHRE starting scanning first.
*/
private void testChreScanFirst() throws Exception {
chreBleStartScanSync(getDefaultScanFilter());
startBleScanOnHost();
Thread.sleep(1000);
stopBleScanOnHost();
chreBleStopScanSync();
}
}