blob: c4a972943282d32c8d1970d1feace6db04a57ee3 [file] [log] [blame]
/*
* Copyright (C) 2022 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.server.nearby.common.ble;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAssignedNumbers;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.ScanFilter;
import android.os.Parcel;
import android.os.ParcelUuid;
import android.util.SparseArray;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.server.nearby.common.ble.testing.FastPairTestData;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@RunWith(AndroidJUnit4.class)
public class BleFilterTest {
public static final ParcelUuid EDDYSTONE_SERVICE_DATA_PARCELUUID =
ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB");
private final BleFilter mEddystoneFilter = createEddystoneFilter();
private final BleFilter mEddystoneUidFilter = createEddystoneUidFilter();
private final BleFilter mEddystoneUrlFilter = createEddystoneUrlFilter();
private final BleFilter mEddystoneEidFilter = createEddystoneEidFilter();
private final BleFilter mIBeaconWithoutUuidFilter = createIBeaconWithoutUuidFilter();
private final BleFilter mIBeaconWithUuidFilter = createIBeaconWithUuidFilter();
private final BleFilter mChromecastFilter =
new BleFilter.Builder().setServiceUuid(
new ParcelUuid(UUID.fromString("0000FEA0-0000-1000-8000-00805F9B34FB")))
.build();
private final BleFilter mEddystoneWithDeviceNameFilter =
new BleFilter.Builder()
.setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID)
.setDeviceName("BERT")
.build();
private final BleFilter mEddystoneWithDeviceAddressFilter =
new BleFilter.Builder()
.setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID)
.setDeviceAddress("00:11:22:33:AA:BB")
.build();
private final BleFilter mServiceUuidWithMaskFilter1 =
new BleFilter.Builder()
.setServiceUuid(
new ParcelUuid(UUID.fromString("0000FEA0-0000-1000-8000-00805F9B34FB")),
new ParcelUuid(UUID.fromString("0000000-0000-000-FFFF-FFFFFFFFFFFF")))
.build();
private final BleFilter mServiceUuidWithMaskFilter2 =
new BleFilter.Builder()
.setServiceUuid(
new ParcelUuid(UUID.fromString("0000FEA0-0000-1000-8000-00805F9B34FB")),
new ParcelUuid(UUID.fromString("FFFFFFF-FFFF-FFF-FFFF-FFFFFFFFFFFF")))
.build();
private final BleFilter mSmartSetupFilter =
new BleFilter.Builder()
.setManufacturerData(
BluetoothAssignedNumbers.GOOGLE,
new byte[] {0x00, 0x10},
new byte[] {0x00, (byte) 0xFF})
.build();
private final BleFilter mWearFilter =
new BleFilter.Builder()
.setManufacturerData(
BluetoothAssignedNumbers.GOOGLE,
new byte[] {0x00, 0x00, 0x00},
new byte[] {0x00, 0x00, (byte) 0xFF})
.build();
private final BleFilter mFakeSmartSetupSubsetFilter =
new BleFilter.Builder()
.setManufacturerData(
BluetoothAssignedNumbers.GOOGLE,
new byte[] {0x00, 0x10, 0x50},
new byte[] {0x00, (byte) 0xFF, (byte) 0xFF})
.build();
private final BleFilter mFakeSmartSetupNotSubsetFilter =
new BleFilter.Builder()
.setManufacturerData(
BluetoothAssignedNumbers.GOOGLE,
new byte[] {0x00, 0x10, 0x50},
new byte[] {0x00, (byte) 0x00, (byte) 0xFF})
.build();
private final BleFilter mFakeFilter1 =
new BleFilter.Builder()
.setServiceData(
ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"),
new byte[] {0x51, 0x64},
new byte[] {0x00, (byte) 0xFF})
.build();
private final BleFilter mFakeFilter2 =
new BleFilter.Builder()
.setServiceData(
ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"),
new byte[] {0x51, 0x64, 0x34},
new byte[] {0x00, (byte) 0xFF, (byte) 0xFF})
.build();
private ParcelUuid mServiceDataUuid;
private BleSighting mBleSighting;
private BleFilter.Builder mFilterBuilder;
@Before
public void setUp() throws Exception {
// This is the service data UUID in TestData.sd1.
// Can't be static because of Robolectric.
mServiceDataUuid = ParcelUuid.fromString("000000E0-0000-1000-8000-00805F9B34FB");
byte[] bleRecordBytes =
new byte[]{
0x02,
0x01,
0x1a, // advertising flags
0x05,
0x02,
0x0b,
0x11,
0x0a,
0x11, // 16 bit service uuids
0x04,
0x09,
0x50,
0x65,
0x64, // setName
0x02,
0x0A,
(byte) 0xec, // tx power level
0x05,
0x16,
0x0b,
0x11,
0x50,
0x64, // service data
0x05,
(byte) 0xff,
(byte) 0xe0,
0x00,
0x02,
0x15, // manufacturer specific data
0x03,
0x50,
0x01,
0x02, // an unknown data type won't cause trouble
};
mBleSighting = new BleSighting(null /* device */, bleRecordBytes,
-10, 1397545200000000L);
mFilterBuilder = new BleFilter.Builder();
}
@Test
public void setNameFilter() {
BleFilter filter = mFilterBuilder.setDeviceName("Ped").build();
assertThat(filter.matches(mBleSighting)).isTrue();
filter = mFilterBuilder.setDeviceName("Pem").build();
assertThat(filter.matches(mBleSighting)).isFalse();
}
@Test
public void setServiceUuidFilter() {
BleFilter filter =
mFilterBuilder.setServiceUuid(
ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"))
.build();
assertThat(filter.matches(mBleSighting)).isTrue();
filter =
mFilterBuilder.setServiceUuid(
ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"))
.build();
assertThat(filter.matches(mBleSighting)).isFalse();
filter =
mFilterBuilder
.setServiceUuid(
ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"),
ParcelUuid.fromString("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF"))
.build();
assertThat(filter.matches(mBleSighting)).isTrue();
}
@Test
public void setServiceDataFilter() {
byte[] setServiceData = new byte[]{0x50, 0x64};
ParcelUuid serviceDataUuid = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB");
BleFilter filter = mFilterBuilder.setServiceData(serviceDataUuid, setServiceData).build();
assertThat(filter.matches(mBleSighting)).isTrue();
byte[] emptyData = new byte[0];
filter = mFilterBuilder.setServiceData(serviceDataUuid, emptyData).build();
assertThat(filter.matches(mBleSighting)).isTrue();
byte[] prefixData = new byte[]{0x50};
filter = mFilterBuilder.setServiceData(serviceDataUuid, prefixData).build();
assertThat(filter.matches(mBleSighting)).isTrue();
byte[] nonMatchData = new byte[]{0x51, 0x64};
byte[] mask = new byte[]{(byte) 0x00, (byte) 0xFF};
filter = mFilterBuilder.setServiceData(serviceDataUuid, nonMatchData, mask).build();
assertThat(filter.matches(mBleSighting)).isTrue();
filter = mFilterBuilder.setServiceData(serviceDataUuid, nonMatchData).build();
assertThat(filter.matches(mBleSighting)).isFalse();
}
@Test
public void manufacturerSpecificData() {
byte[] setManufacturerData = new byte[]{0x02, 0x15};
int manufacturerId = 0xE0;
BleFilter filter =
mFilterBuilder.setManufacturerData(manufacturerId, setManufacturerData).build();
assertThat(filter.matches(mBleSighting)).isTrue();
byte[] emptyData = new byte[0];
filter = mFilterBuilder.setManufacturerData(manufacturerId, emptyData).build();
assertThat(filter.matches(mBleSighting)).isTrue();
byte[] prefixData = new byte[]{0x02};
filter = mFilterBuilder.setManufacturerData(manufacturerId, prefixData).build();
assertThat(filter.matches(mBleSighting)).isTrue();
// Data and mask are nullable. Check that we still match when they're null.
filter = mFilterBuilder.setManufacturerData(manufacturerId,
null /* data */).build();
assertThat(filter.matches(mBleSighting)).isTrue();
filter = mFilterBuilder.setManufacturerData(manufacturerId,
null /* data */, null /* mask */).build();
assertThat(filter.matches(mBleSighting)).isTrue();
// Test data mask
byte[] nonMatchData = new byte[]{0x02, 0x14};
filter = mFilterBuilder.setManufacturerData(manufacturerId, nonMatchData).build();
assertThat(filter.matches(mBleSighting)).isFalse();
byte[] mask = new byte[]{(byte) 0xFF, (byte) 0x00};
filter = mFilterBuilder.setManufacturerData(manufacturerId, nonMatchData, mask).build();
assertThat(filter.matches(mBleSighting)).isTrue();
}
@Test
public void manufacturerDataNotInBleRecord() {
byte[] bleRecord = FastPairTestData.adv_2;
// Verify manufacturer with no data
byte[] data = {(byte) 0xe0, (byte) 0x00};
BleFilter filter = mFilterBuilder.setManufacturerData(0x00e0, data).build();
assertThat(matches(filter, null, 0, bleRecord)).isFalse();
}
@Test
public void manufacturerDataMaskNotInBleRecord() {
byte[] bleRecord = FastPairTestData.adv_2;
// Verify matching partial manufacturer with data and mask
byte[] data = {(byte) 0x15};
byte[] mask = {(byte) 0xff};
BleFilter filter = mFilterBuilder
.setManufacturerData(0x00e0, data, mask).build();
assertThat(matches(filter, null, 0, bleRecord)).isFalse();
}
@Test
public void serviceData() throws Exception {
byte[] bleRecord = FastPairTestData.sd1;
byte[] serviceData = {(byte) 0x15};
// Verify manufacturer 2-byte UUID with no data
BleFilter filter = mFilterBuilder.setServiceData(mServiceDataUuid, serviceData).build();
assertMatches(filter, null, 0, bleRecord);
}
@Test
public void serviceDataNoMatch() {
byte[] bleRecord = FastPairTestData.sd1;
byte[] serviceData = {(byte) 0xe1, (byte) 0x00};
// Verify manufacturer 2-byte UUID with no data
BleFilter filter = mFilterBuilder.setServiceData(mServiceDataUuid, serviceData).build();
assertThat(matches(filter, null, 0, bleRecord)).isFalse();
}
@Test
public void serviceDataUuidNotInBleRecord() {
byte[] bleRecord = FastPairTestData.eir_1;
byte[] serviceData = {(byte) 0xe0, (byte) 0x00};
// Verify Service Data with 2-byte UUID, no data, and NOT in scan record
BleFilter filter = mFilterBuilder.setServiceData(mServiceDataUuid, serviceData).build();
assertThat(matches(filter, null, 0, bleRecord)).isFalse();
}
@Test
public void serviceDataMask() {
byte[] bleRecord = FastPairTestData.sd1;
BleFilter filter;
// Verify matching partial manufacturer with data and mask
byte[] serviceData1 = {(byte) 0x15};
byte[] mask1 = {(byte) 0xff};
filter = mFilterBuilder.setServiceData(mServiceDataUuid, serviceData1, mask1).build();
assertMatches(filter, null, 0, bleRecord);
}
@Test
public void serviceDataMaskNoMatch() {
byte[] bleRecord = FastPairTestData.sd1;
BleFilter filter;
// Verify non-matching partial manufacturer with data and mask
byte[] serviceData2 = {(byte) 0xe0, (byte) 0x00, (byte) 0x10};
byte[] mask2 = {(byte) 0xff, (byte) 0xff, (byte) 0xff};
filter = mFilterBuilder.setServiceData(mServiceDataUuid, serviceData2, mask2).build();
assertThat(matches(filter, null, 0, bleRecord)).isFalse();
}
@Test(expected = IllegalArgumentException.class)
public void serviceDataMaskWithDifferentLength() {
// Different lengths for data and mask.
byte[] serviceData = {(byte) 0xe0, (byte) 0x00, (byte) 0x10};
byte[] mask = {(byte) 0xff, (byte) 0xff};
//expected.expect(IllegalArgumentException.class);
mFilterBuilder.setServiceData(mServiceDataUuid, serviceData, mask).build();
}
@Test
public void serviceDataMaskNotInBleRecord() {
byte[] bleRecord = FastPairTestData.eir_1;
BleFilter filter;
// Verify matching partial manufacturer with data and mask
byte[] serviceData1 = {(byte) 0xe0, (byte) 0x00, (byte) 0x15};
byte[] mask1 = {(byte) 0xff, (byte) 0xff, (byte) 0xff};
filter = mFilterBuilder.setServiceData(mServiceDataUuid, serviceData1, mask1).build();
assertThat(matches(filter, null, 0, bleRecord)).isFalse();
}
@Test
public void deviceNameTest() {
// Verify the name filter matches
byte[] bleRecord = FastPairTestData.adv_1;
BleFilter filter = mFilterBuilder.setDeviceName("Pedometer").build();
assertMatches(filter, null, 0, bleRecord);
}
@Test
public void deviceNameNoMatch() {
// Verify the name filter does not match
byte[] bleRecord = FastPairTestData.adv_1;
BleFilter filter = mFilterBuilder.setDeviceName("Foo").build();
assertThat(matches(filter, null, 0, bleRecord)).isFalse();
}
@Test
public void deviceNameNotInBleRecord() {
// Verify the name filter does not match
byte[] bleRecord = FastPairTestData.eir_1;
BleFilter filter = mFilterBuilder.setDeviceName("Pedometer").build();
assertThat(matches(filter, null, 0, bleRecord)).isFalse();
}
@Test
public void serviceUuid() {
byte[] bleRecord = FastPairTestData.eddystone_header_and_uuid;
ParcelUuid uuid = ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB");
BleFilter filter = mFilterBuilder.setServiceUuid(uuid).build();
assertMatches(filter, null, 0, bleRecord);
}
@Test
public void serviceUuidNoMatch() {
// Verify the name filter does not match
byte[] bleRecord = FastPairTestData.eddystone_header_and_uuid;
ParcelUuid uuid = ParcelUuid.fromString("00001804-0000-1000-8000-000000000000");
BleFilter filter = mFilterBuilder.setServiceUuid(uuid).build();
assertThat(matches(filter, null, 0, bleRecord)).isFalse();
}
@Test
public void serviceUuidNotInBleRecord() {
// Verify the name filter does not match
byte[] bleRecord = FastPairTestData.eir_1;
ParcelUuid uuid = ParcelUuid.fromString("00001804-0000-1000-8000-000000000000");
BleFilter filter = mFilterBuilder.setServiceUuid(uuid).build();
assertThat(matches(filter, null, 0, bleRecord)).isFalse();
}
@Test
public void serviceUuidMask() {
byte[] bleRecord = FastPairTestData.eddystone_header_and_uuid;
ParcelUuid uuid = ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB");
ParcelUuid mask = ParcelUuid.fromString("00000000-0000-0000-0000-FFFFFFFFFFFF");
BleFilter filter = mFilterBuilder.setServiceUuid(uuid, mask).build();
assertMatches(filter, null, 0, bleRecord);
}
@Test
public void macAddress() {
byte[] bleRecord = FastPairTestData.eddystone_header_and_uuid;
String macAddress = "00:11:22:33:AA:BB";
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
BluetoothDevice device = adapter.getRemoteDevice(macAddress);
BleFilter filter = mFilterBuilder.setDeviceAddress(macAddress).build();
assertMatches(filter, device, 0, bleRecord);
}
@Test
public void macAddressNoMatch() {
byte[] bleRecord = FastPairTestData.eddystone_header_and_uuid;
String macAddress = "00:11:22:33:AA:00";
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
BluetoothDevice device = adapter.getRemoteDevice("00:11:22:33:AA:BB");
BleFilter filter = mFilterBuilder.setDeviceAddress(macAddress).build();
assertThat(matches(filter, device, 0, bleRecord)).isFalse();
}
@Test
public void eddystoneIsSuperset() {
// Verify eddystone subtypes pass.
assertThat(mEddystoneFilter.isSuperset(mEddystoneFilter)).isTrue();
assertThat(mEddystoneUidFilter.isSuperset(mEddystoneUidFilter)).isTrue();
assertThat(mEddystoneFilter.isSuperset(mEddystoneUidFilter)).isTrue();
assertThat(mEddystoneFilter.isSuperset(mEddystoneEidFilter)).isTrue();
assertThat(mEddystoneFilter.isSuperset(mEddystoneUrlFilter)).isTrue();
// Non-eddystone beacon filters should never be supersets.
assertThat(mEddystoneFilter.isSuperset(mIBeaconWithoutUuidFilter)).isFalse();
assertThat(mEddystoneFilter.isSuperset(mWearFilter)).isFalse();
assertThat(mEddystoneFilter.isSuperset(mSmartSetupFilter)).isFalse();
assertThat(mEddystoneFilter.isSuperset(mChromecastFilter)).isFalse();
assertThat(mEddystoneFilter.isSuperset(mFakeFilter1)).isFalse();
assertThat(mEddystoneFilter.isSuperset(mFakeFilter2)).isFalse();
assertThat(mEddystoneUidFilter.isSuperset(mWearFilter)).isFalse();
assertThat(mEddystoneUidFilter.isSuperset(mSmartSetupFilter)).isFalse();
assertThat(mEddystoneUidFilter.isSuperset(mChromecastFilter)).isFalse();
assertThat(mEddystoneUidFilter.isSuperset(mFakeFilter1)).isFalse();
assertThat(mEddystoneUidFilter.isSuperset(mFakeFilter2)).isFalse();
}
@Test
public void iBeaconIsSuperset() {
// Verify that an iBeacon filter is a superset of itself and any filters that specify UUIDs.
assertThat(mIBeaconWithoutUuidFilter.isSuperset(mIBeaconWithoutUuidFilter)).isTrue();
assertThat(mIBeaconWithoutUuidFilter.isSuperset(mIBeaconWithUuidFilter)).isTrue();
// Non-iBeacon filters should never be supersets.
assertThat(mIBeaconWithoutUuidFilter.isSuperset(mEddystoneEidFilter)).isFalse();
assertThat(mIBeaconWithoutUuidFilter.isSuperset(mEddystoneUrlFilter)).isFalse();
assertThat(mIBeaconWithoutUuidFilter.isSuperset(mEddystoneUidFilter)).isFalse();
assertThat(mIBeaconWithoutUuidFilter.isSuperset(mWearFilter)).isFalse();
assertThat(mIBeaconWithoutUuidFilter.isSuperset(mSmartSetupFilter)).isFalse();
assertThat(mIBeaconWithoutUuidFilter.isSuperset(mChromecastFilter)).isFalse();
assertThat(mIBeaconWithoutUuidFilter.isSuperset(mFakeFilter1)).isFalse();
assertThat(mIBeaconWithoutUuidFilter.isSuperset(mFakeFilter2)).isFalse();
}
@Test
public void mixedFilterIsSuperset() {
// Compare service data vs manufacturer data filters to verify we detect supersets
// correctly in filters that aren't for iBeacon and Eddystone.
assertThat(mWearFilter.isSuperset(mIBeaconWithoutUuidFilter)).isFalse();
assertThat(mSmartSetupFilter.isSuperset(mIBeaconWithoutUuidFilter)).isFalse();
assertThat(mChromecastFilter.isSuperset(mIBeaconWithoutUuidFilter)).isFalse();
assertThat(mWearFilter.isSuperset(mEddystoneFilter)).isFalse();
assertThat(mSmartSetupFilter.isSuperset(mEddystoneFilter)).isFalse();
assertThat(mChromecastFilter.isSuperset(mEddystoneFilter)).isFalse();
assertThat(mWearFilter.isSuperset(mEddystoneUidFilter)).isFalse();
assertThat(mSmartSetupFilter.isSuperset(mEddystoneUidFilter)).isFalse();
assertThat(mChromecastFilter.isSuperset(mEddystoneUidFilter)).isFalse();
assertThat(mWearFilter.isSuperset(mEddystoneEidFilter)).isFalse();
assertThat(mSmartSetupFilter.isSuperset(mEddystoneEidFilter)).isFalse();
assertThat(mChromecastFilter.isSuperset(mEddystoneEidFilter)).isFalse();
assertThat(mWearFilter.isSuperset(mEddystoneUrlFilter)).isFalse();
assertThat(mSmartSetupFilter.isSuperset(mEddystoneUrlFilter)).isFalse();
assertThat(mChromecastFilter.isSuperset(mEddystoneUrlFilter)).isFalse();
assertThat(mWearFilter.isSuperset(mIBeaconWithUuidFilter)).isFalse();
assertThat(mSmartSetupFilter.isSuperset(mIBeaconWithUuidFilter)).isFalse();
assertThat(mChromecastFilter.isSuperset(mIBeaconWithUuidFilter)).isFalse();
assertThat(mWearFilter.isSuperset(mChromecastFilter)).isFalse();
assertThat(mSmartSetupFilter.isSuperset(mChromecastFilter)).isFalse();
assertThat(mSmartSetupFilter.isSuperset(mWearFilter)).isFalse();
assertThat(mChromecastFilter.isSuperset(mWearFilter)).isFalse();
assertThat(mFakeFilter1.isSuperset(mFakeFilter2)).isTrue();
assertThat(mFakeFilter2.isSuperset(mFakeFilter1)).isFalse();
assertThat(mSmartSetupFilter.isSuperset(mFakeSmartSetupSubsetFilter)).isTrue();
assertThat(mSmartSetupFilter.isSuperset(mFakeSmartSetupNotSubsetFilter)).isFalse();
assertThat(mEddystoneFilter.isSuperset(mEddystoneWithDeviceNameFilter)).isTrue();
assertThat(mEddystoneFilter.isSuperset(mEddystoneWithDeviceAddressFilter)).isTrue();
assertThat(mEddystoneWithDeviceAddressFilter.isSuperset(mEddystoneFilter)).isFalse();
assertThat(mChromecastFilter.isSuperset(mServiceUuidWithMaskFilter1)).isTrue();
assertThat(mServiceUuidWithMaskFilter2.isSuperset(mServiceUuidWithMaskFilter1)).isFalse();
assertThat(mServiceUuidWithMaskFilter1.isSuperset(mServiceUuidWithMaskFilter2)).isTrue();
assertThat(mEddystoneFilter.isSuperset(mServiceUuidWithMaskFilter1)).isFalse();
}
@Test
public void toOsFilter_getTheSameFilterParameter() {
BleFilter nearbyFilter = createTestFilter();
ScanFilter osFilter = nearbyFilter.toOsFilter();
assertFilterValuesEqual(nearbyFilter, osFilter);
}
@Test
public void describeContents() {
BleFilter nearbyFilter = createTestFilter();
assertThat(nearbyFilter.describeContents()).isEqualTo(0);
}
@Test
public void testHashCode() {
BleFilter nearbyFilter = createTestFilter();
BleFilter compareFilter = new BleFilter("BERT", "00:11:22:33:AA:BB",
new ParcelUuid(UUID.fromString("0000FEA0-0000-1000-8000-00805F9B34FB")),
new ParcelUuid(UUID.fromString("FFFFFFF-FFFF-FFF-FFFF-FFFFFFFFFFFF")),
ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"),
new byte[] {0x51, 0x64}, new byte[] {0x00, (byte) 0xFF},
BluetoothAssignedNumbers.GOOGLE, new byte[] {0x00, 0x10},
new byte[] {0x00, (byte) 0xFF});
assertThat(nearbyFilter.hashCode()).isEqualTo(compareFilter.hashCode());
}
@Test
public void testToString() {
BleFilter nearbyFilter = createTestFilter();
assertThat(nearbyFilter.toString()).isEqualTo("BleFilter [deviceName=BERT,"
+ " deviceAddress=00:11:22:33:AA:BB, uuid=0000fea0-0000-1000-8000-00805f9b34fb,"
+ " uuidMask=0fffffff-ffff-0fff-ffff-ffffffffffff,"
+ " serviceDataUuid=0000110b-0000-1000-8000-00805f9b34fb,"
+ " serviceData=[81, 100], serviceDataMask=[0, -1],"
+ " manufacturerId=224, manufacturerData=[0, 16], manufacturerDataMask=[0, -1]]");
}
@Test
public void testParcel() {
BleFilter nearbyFilter = createTestFilter();
Parcel parcel = Parcel.obtain();
nearbyFilter.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
BleFilter compareFilter = BleFilter.CREATOR.createFromParcel(
parcel);
parcel.recycle();
assertThat(compareFilter.getDeviceName()).isEqualTo("BERT");
}
@Test
public void testCreatorNewArray() {
BleFilter[] nearbyFilters = BleFilter.CREATOR.newArray(2);
assertThat(nearbyFilters.length).isEqualTo(2);
}
private static boolean matches(
BleFilter filter, BluetoothDevice device, int rssi, byte[] bleRecord) {
return filter.matches(new BleSighting(device,
bleRecord, rssi, 0 /* timestampNanos */));
}
private static void assertFilterValuesEqual(BleFilter nearbyFilter, ScanFilter osFilter) {
assertThat(osFilter.getDeviceAddress()).isEqualTo(nearbyFilter.getDeviceAddress());
assertThat(osFilter.getDeviceName()).isEqualTo(nearbyFilter.getDeviceName());
assertThat(osFilter.getManufacturerData()).isEqualTo(nearbyFilter.getManufacturerData());
assertThat(osFilter.getManufacturerDataMask())
.isEqualTo(nearbyFilter.getManufacturerDataMask());
assertThat(osFilter.getManufacturerId()).isEqualTo(nearbyFilter.getManufacturerId());
assertThat(osFilter.getServiceData()).isEqualTo(nearbyFilter.getServiceData());
assertThat(osFilter.getServiceDataMask()).isEqualTo(nearbyFilter.getServiceDataMask());
assertThat(osFilter.getServiceDataUuid()).isEqualTo(nearbyFilter.getServiceDataUuid());
assertThat(osFilter.getServiceUuid()).isEqualTo(nearbyFilter.getServiceUuid());
assertThat(osFilter.getServiceUuidMask()).isEqualTo(nearbyFilter.getServiceUuidMask());
}
private static void assertMatches(
BleFilter filter, BluetoothDevice device, int rssi, byte[] bleRecordBytes) {
// Device match.
if (filter.getDeviceAddress() != null
&& (device == null || !filter.getDeviceAddress().equals(device.getAddress()))) {
fail("Filter specified a device address ("
+ filter.getDeviceAddress()
+ ") which doesn't match the actual value: ["
+ (device == null ? "null device" : device.getAddress())
+ "]");
}
// BLE record is null but there exist filters on it.
BleRecord bleRecord = BleRecord.parseFromBytes(bleRecordBytes);
if (bleRecord == null
&& (filter.getDeviceName() != null
|| filter.getServiceUuid() != null
|| filter.getManufacturerData() != null
|| filter.getServiceData() != null)) {
fail(
"The bleRecordBytes given parsed to a null bleRecord, but the filter"
+ "has a non-null field which depends on the scan record");
}
// Local name match.
if (filter.getDeviceName() != null
&& !filter.getDeviceName().equals(bleRecord.getDeviceName())) {
fail(
"The filter's device name ("
+ filter.getDeviceName()
+ ") doesn't match the scan record device name ("
+ bleRecord.getDeviceName()
+ ")");
}
// UUID match.
if (filter.getServiceUuid() != null
&& !matchesServiceUuids(filter.getServiceUuid(),
filter.getServiceUuidMask(), bleRecord.getServiceUuids())) {
fail("The filter specifies a service UUID "
+ "but it doesn't match what's in the scan record");
}
// Service data match
if (filter.getServiceDataUuid() != null
&& !BleFilter.matchesPartialData(
filter.getServiceData(),
filter.getServiceDataMask(),
bleRecord.getServiceData(filter.getServiceDataUuid()))) {
fail(
"The filter's service data doesn't match what's in the scan record.\n"
+ "Service data: "
+ byteString(filter.getServiceData())
+ "\n"
+ "Service data UUID: "
+ filter.getServiceDataUuid().toString()
+ "\n"
+ "Service data mask: "
+ byteString(filter.getServiceDataMask())
+ "\n"
+ "Scan record service data: "
+ byteString(bleRecord.getServiceData(filter.getServiceDataUuid()))
+ "\n"
+ "Scan record data map:\n"
+ byteString(bleRecord.getServiceData()));
}
// Manufacturer data match.
if (filter.getManufacturerId() >= 0
&& !BleFilter.matchesPartialData(
filter.getManufacturerData(),
filter.getManufacturerDataMask(),
bleRecord.getManufacturerSpecificData(filter.getManufacturerId()))) {
fail(
"The filter's manufacturer data doesn't match what's in the scan record.\n"
+ "Manufacturer ID: "
+ filter.getManufacturerId()
+ "\n"
+ "Manufacturer data: "
+ byteString(filter.getManufacturerData())
+ "\n"
+ "Manufacturer data mask: "
+ byteString(filter.getManufacturerDataMask())
+ "\n"
+ "Scan record manufacturer-specific data: "
+ byteString(bleRecord.getManufacturerSpecificData(
filter.getManufacturerId()))
+ "\n"
+ "Manufacturer data array:\n"
+ byteString(bleRecord.getManufacturerSpecificData()));
}
// All filters match.
assertThat(
matches(filter, device, rssi, bleRecordBytes)).isTrue();
}
private static String byteString(byte[] bytes) {
if (bytes == null) {
return "[null]";
} else {
final char[] hexArray = "0123456789ABCDEF".toCharArray();
char[] hexChars = new char[bytes.length * 2];
for (int i = 0; i < bytes.length; i++) {
int v = bytes[i] & 0xFF;
hexChars[i * 2] = hexArray[v >>> 4];
hexChars[i * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
}
private static String byteString(Map<ParcelUuid, byte[]> bytesMap) {
StringBuilder builder = new StringBuilder();
for (Map.Entry<ParcelUuid, byte[]> entry : bytesMap.entrySet()) {
builder.append(builder.toString().isEmpty() ? " " : "\n ");
builder.append(entry.getKey().toString());
builder.append(" --> ");
builder.append(byteString(entry.getValue()));
}
return builder.toString();
}
private static String byteString(SparseArray<byte[]> bytesArray) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < bytesArray.size(); i++) {
builder.append(builder.toString().isEmpty() ? " " : "\n ");
builder.append(byteString(bytesArray.valueAt(i)));
}
return builder.toString();
}
private static BleFilter createTestFilter() {
BleFilter.Builder builder = new BleFilter.Builder();
builder
.setServiceUuid(
new ParcelUuid(UUID.fromString("0000FEA0-0000-1000-8000-00805F9B34FB")),
new ParcelUuid(UUID.fromString("FFFFFFF-FFFF-FFF-FFFF-FFFFFFFFFFFF")))
.setDeviceAddress("00:11:22:33:AA:BB")
.setDeviceName("BERT")
.setManufacturerData(
BluetoothAssignedNumbers.GOOGLE,
new byte[] {0x00, 0x10},
new byte[] {0x00, (byte) 0xFF})
.setServiceData(
ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"),
new byte[] {0x51, 0x64},
new byte[] {0x00, (byte) 0xFF});
return builder.build();
}
// ref to beacon.decode.BeaconFilterBuilder.eddystoneFilter()
private static BleFilter createEddystoneFilter() {
return new BleFilter.Builder().setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID).build();
}
// ref to beacon.decode.BeaconFilterBuilder.eddystoneUidFilter()
private static BleFilter createEddystoneUidFilter() {
return new BleFilter.Builder()
.setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID)
.setServiceData(
EDDYSTONE_SERVICE_DATA_PARCELUUID, new byte[] {(short) 0x00},
new byte[] {(byte) 0xf0})
.build();
}
// ref to beacon.decode.BeaconFilterBuilder.eddystoneUrlFilter()
private static BleFilter createEddystoneUrlFilter() {
return new BleFilter.Builder()
.setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID)
.setServiceData(
EDDYSTONE_SERVICE_DATA_PARCELUUID,
new byte[] {(short) 0x10}, new byte[] {(byte) 0xf0})
.build();
}
// ref to beacon.decode.BeaconFilterBuilder.eddystoneEidFilter()
private static BleFilter createEddystoneEidFilter() {
return new BleFilter.Builder()
.setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID)
.setServiceData(
EDDYSTONE_SERVICE_DATA_PARCELUUID,
new byte[] {(short) 0x30}, new byte[] {(byte) 0xf0})
.build();
}
// ref to beacon.decode.BeaconFilterBuilder.iBeaconWithoutUuidFilter()
private static BleFilter createIBeaconWithoutUuidFilter() {
byte[] data = {(byte) 0x02, (byte) 0x15};
byte[] mask = {(byte) 0xff, (byte) 0xff};
return new BleFilter.Builder().setManufacturerData((short) 0x004C, data, mask).build();
}
// ref to beacon.decode.BeaconFilterBuilder.iBeaconWithUuidFilter()
private static BleFilter createIBeaconWithUuidFilter() {
byte[] data = getFilterData(ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB"));
byte[] mask = getFilterMask(ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB"));
return new BleFilter.Builder().setManufacturerData((short) 0x004C, data, mask).build();
}
// Ref to beacon.decode.AppleBeaconDecoder.getFilterData
private static byte[] getFilterData(ParcelUuid uuid) {
byte[] data = new byte[18];
data[0] = (byte) 0x02;
data[1] = (byte) 0x15;
// Check if UUID is needed in data
if (uuid != null) {
// Convert UUID to array in big endian order
byte[] uuidBytes = uuidToByteArray(uuid);
for (int i = 0; i < 16; i++) {
// Adding uuid bytes in big-endian order to match iBeacon format
data[i + 2] = uuidBytes[i];
}
}
return data;
}
// Ref to beacon.decode.AppleBeaconDecoder.getFilterMask
private static byte[] getFilterMask(ParcelUuid uuid) {
byte[] mask = new byte[18];
mask[0] = (byte) 0xff;
mask[1] = (byte) 0xff;
// Check if UUID is needed in data
if (uuid != null) {
for (int i = 0; i < 16; i++) {
mask[i + 2] = (byte) 0xff;
}
}
return mask;
}
// Ref to beacon.decode.AppleBeaconDecoder.uuidToByteArray
private static byte[] uuidToByteArray(ParcelUuid uuid) {
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(uuid.getUuid().getMostSignificantBits());
bb.putLong(uuid.getUuid().getLeastSignificantBits());
return bb.array();
}
private static boolean matchesServiceUuids(
ParcelUuid uuid, ParcelUuid parcelUuidMask, List<ParcelUuid> uuids) {
if (uuid == null) {
return true;
}
for (ParcelUuid parcelUuid : uuids) {
UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid();
if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) {
return true;
}
}
return false;
}
// Check if the uuid pattern matches the particular service uuid.
private static boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) {
if (mask == null) {
return uuid.equals(data);
}
if ((uuid.getLeastSignificantBits() & mask.getLeastSignificantBits())
!= (data.getLeastSignificantBits() & mask.getLeastSignificantBits())) {
return false;
}
return ((uuid.getMostSignificantBits() & mask.getMostSignificantBits())
== (data.getMostSignificantBits() & mask.getMostSignificantBits()));
}
}