Loading android/app/src/com/android/bluetooth/le_scan/ScanFilterQueue.java +11 −7 Original line number Diff line number Diff line Loading @@ -256,8 +256,8 @@ import java.util.UUID; serviceDataMask = new byte[serviceData.length]; Arrays.fill(serviceDataMask, (byte) 0xFF); } serviceData = concatenate(serviceDataUuid, serviceData); serviceDataMask = concatenate(serviceDataUuid, serviceDataMask); serviceData = concatenate(serviceDataUuid, serviceData, false); serviceDataMask = concatenate(serviceDataUuid, serviceDataMask, true); if (serviceData != null && serviceDataMask != null) { addServiceData(serviceData, serviceDataMask); } Loading Loading @@ -293,19 +293,23 @@ import java.util.UUID; } } private byte[] concatenate(ParcelUuid serviceDataUuid, byte[] serviceData) { private byte[] concatenate(ParcelUuid serviceDataUuid, byte[] serviceData, boolean isMask) { byte[] uuid = BluetoothUuid.uuidToBytes(serviceDataUuid); int dataLen = uuid.length + (serviceData == null ? 0 : serviceData.length); int dataLen = uuid.length + serviceData.length; // If data is too long, don't add it to hardware scan filter. if (dataLen > MAX_LEN_PER_FIELD) { return null; } byte[] concatenated = new byte[dataLen]; if (isMask) { // For the UUID portion of the mask fill it with 0xFF to indicate that all bits of the // UUID need to match the service data filter. Arrays.fill(concatenated, 0, uuid.length, (byte) 0xFF); } else { System.arraycopy(uuid, 0, concatenated, 0, uuid.length); if (serviceData != null) { System.arraycopy(serviceData, 0, concatenated, uuid.length, serviceData.length); } System.arraycopy(serviceData, 0, concatenated, uuid.length, serviceData.length); return concatenated; } } android/app/tests/unit/src/com/android/bluetooth/le_scan/ScanFilterQueueTest.java +104 −0 Original line number Diff line number Diff line Loading @@ -18,12 +18,15 @@ package com.android.bluetooth.le_scan; import static com.google.common.truth.Truth.assertThat; import android.bluetooth.BluetoothUuid; import android.bluetooth.le.ScanFilter; import android.os.ParcelUuid; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.google.common.primitives.Bytes; import org.junit.Test; import org.junit.runner.RunWith; Loading @@ -33,6 +36,14 @@ import java.util.UUID; @SmallTest @RunWith(AndroidJUnit4.class) public class ScanFilterQueueTest { private static final String TEST_UUID_STRING = "00001805-0000-1000-8000-00805f9b34fb"; private static final String UNMATCHED_UUID_STRING = "00001815-0000-1000-8000-00805f9b34fb"; private static final byte[] TEST_SERVICE_DATA = new byte[] {(byte) 0x18, (byte) 0x0F}; private static final byte[] PARTIALLY_MATCHED_SERVICE_DATA = new byte[] {(byte) 0x08, (byte) 0x0F, (byte) 0xAB, (byte) 0xCD}; private static final byte[] UNMATCHED_SERVICE_DATA = new byte[] {(byte) 0x08, (byte) 0x0E}; private static final byte[] PARTIAL_SERVICE_DATA_MASK = new byte[] {(byte) 0x00, (byte) 0xFF}; private static final byte[] FULL_SERVICE_DATA_MASK = new byte[] {(byte) 0xFF, (byte) 0xFF}; @Test public void scanFilterQueueParams() { Loading Loading @@ -195,4 +206,97 @@ public class ScanFilterQueueTest { int numOfEntries = 7; assertThat(queue.toArray().length).isEqualTo(numOfEntries); } @Test public void serviceDataFilterNoMask1() { ScanFilter filter = new ScanFilter.Builder() .setServiceData(ParcelUuid.fromString(TEST_UUID_STRING), TEST_SERVICE_DATA) .build(); testServiceDataFilter(filter, false); } @Test public void serviceDataFilterWithFullMask() { ScanFilter filter = new ScanFilter.Builder() .setServiceData( ParcelUuid.fromString(TEST_UUID_STRING), TEST_SERVICE_DATA, FULL_SERVICE_DATA_MASK) .build(); testServiceDataFilter(filter, false); } @Test public void serviceDataFilterWithPartialMask() { ScanFilter filter = new ScanFilter.Builder() .setServiceData( ParcelUuid.fromString(TEST_UUID_STRING), TEST_SERVICE_DATA, PARTIAL_SERVICE_DATA_MASK) .build(); testServiceDataFilter(filter, true); } private void testServiceDataFilter(ScanFilter filter, boolean partialServiceDataMatchResult) { ScanFilterQueue queue = new ScanFilterQueue(); queue.addScanFilter(filter); ScanFilterQueue.Entry entry = queue.pop(); assertThat(entry.type).isEqualTo(ScanFilterQueue.TYPE_SERVICE_DATA); assertThat(entry.data) .isEqualTo( Bytes.concat( BluetoothUuid.uuidToBytes(ParcelUuid.fromString(TEST_UUID_STRING)), TEST_SERVICE_DATA)); assertThat( serviceDataMatches( entry.data, Bytes.concat( BluetoothUuid.uuidToBytes( ParcelUuid.fromString(TEST_UUID_STRING)), TEST_SERVICE_DATA), entry.data_mask)) .isTrue(); assertThat( serviceDataMatches( entry.data, Bytes.concat( BluetoothUuid.uuidToBytes( ParcelUuid.fromString(UNMATCHED_UUID_STRING)), TEST_SERVICE_DATA), entry.data_mask)) .isFalse(); assertThat( serviceDataMatches( entry.data, Bytes.concat( BluetoothUuid.uuidToBytes( ParcelUuid.fromString(TEST_UUID_STRING)), UNMATCHED_SERVICE_DATA), entry.data_mask)) .isFalse(); assertThat( serviceDataMatches( entry.data, Bytes.concat( BluetoothUuid.uuidToBytes( ParcelUuid.fromString(TEST_UUID_STRING)), PARTIALLY_MATCHED_SERVICE_DATA), entry.data_mask)) .isEqualTo(partialServiceDataMatchResult); } private boolean serviceDataMatches(byte[] filterData, byte[] resultData, byte[] mask) { if (filterData.length > resultData.length || filterData.length != mask.length) { return false; } for (int i = 0; i < filterData.length; i++) { if ((filterData[i] & mask[i]) != (resultData[i] & mask[i])) { return false; } } return true; } } Loading
android/app/src/com/android/bluetooth/le_scan/ScanFilterQueue.java +11 −7 Original line number Diff line number Diff line Loading @@ -256,8 +256,8 @@ import java.util.UUID; serviceDataMask = new byte[serviceData.length]; Arrays.fill(serviceDataMask, (byte) 0xFF); } serviceData = concatenate(serviceDataUuid, serviceData); serviceDataMask = concatenate(serviceDataUuid, serviceDataMask); serviceData = concatenate(serviceDataUuid, serviceData, false); serviceDataMask = concatenate(serviceDataUuid, serviceDataMask, true); if (serviceData != null && serviceDataMask != null) { addServiceData(serviceData, serviceDataMask); } Loading Loading @@ -293,19 +293,23 @@ import java.util.UUID; } } private byte[] concatenate(ParcelUuid serviceDataUuid, byte[] serviceData) { private byte[] concatenate(ParcelUuid serviceDataUuid, byte[] serviceData, boolean isMask) { byte[] uuid = BluetoothUuid.uuidToBytes(serviceDataUuid); int dataLen = uuid.length + (serviceData == null ? 0 : serviceData.length); int dataLen = uuid.length + serviceData.length; // If data is too long, don't add it to hardware scan filter. if (dataLen > MAX_LEN_PER_FIELD) { return null; } byte[] concatenated = new byte[dataLen]; if (isMask) { // For the UUID portion of the mask fill it with 0xFF to indicate that all bits of the // UUID need to match the service data filter. Arrays.fill(concatenated, 0, uuid.length, (byte) 0xFF); } else { System.arraycopy(uuid, 0, concatenated, 0, uuid.length); if (serviceData != null) { System.arraycopy(serviceData, 0, concatenated, uuid.length, serviceData.length); } System.arraycopy(serviceData, 0, concatenated, uuid.length, serviceData.length); return concatenated; } }
android/app/tests/unit/src/com/android/bluetooth/le_scan/ScanFilterQueueTest.java +104 −0 Original line number Diff line number Diff line Loading @@ -18,12 +18,15 @@ package com.android.bluetooth.le_scan; import static com.google.common.truth.Truth.assertThat; import android.bluetooth.BluetoothUuid; import android.bluetooth.le.ScanFilter; import android.os.ParcelUuid; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.google.common.primitives.Bytes; import org.junit.Test; import org.junit.runner.RunWith; Loading @@ -33,6 +36,14 @@ import java.util.UUID; @SmallTest @RunWith(AndroidJUnit4.class) public class ScanFilterQueueTest { private static final String TEST_UUID_STRING = "00001805-0000-1000-8000-00805f9b34fb"; private static final String UNMATCHED_UUID_STRING = "00001815-0000-1000-8000-00805f9b34fb"; private static final byte[] TEST_SERVICE_DATA = new byte[] {(byte) 0x18, (byte) 0x0F}; private static final byte[] PARTIALLY_MATCHED_SERVICE_DATA = new byte[] {(byte) 0x08, (byte) 0x0F, (byte) 0xAB, (byte) 0xCD}; private static final byte[] UNMATCHED_SERVICE_DATA = new byte[] {(byte) 0x08, (byte) 0x0E}; private static final byte[] PARTIAL_SERVICE_DATA_MASK = new byte[] {(byte) 0x00, (byte) 0xFF}; private static final byte[] FULL_SERVICE_DATA_MASK = new byte[] {(byte) 0xFF, (byte) 0xFF}; @Test public void scanFilterQueueParams() { Loading Loading @@ -195,4 +206,97 @@ public class ScanFilterQueueTest { int numOfEntries = 7; assertThat(queue.toArray().length).isEqualTo(numOfEntries); } @Test public void serviceDataFilterNoMask1() { ScanFilter filter = new ScanFilter.Builder() .setServiceData(ParcelUuid.fromString(TEST_UUID_STRING), TEST_SERVICE_DATA) .build(); testServiceDataFilter(filter, false); } @Test public void serviceDataFilterWithFullMask() { ScanFilter filter = new ScanFilter.Builder() .setServiceData( ParcelUuid.fromString(TEST_UUID_STRING), TEST_SERVICE_DATA, FULL_SERVICE_DATA_MASK) .build(); testServiceDataFilter(filter, false); } @Test public void serviceDataFilterWithPartialMask() { ScanFilter filter = new ScanFilter.Builder() .setServiceData( ParcelUuid.fromString(TEST_UUID_STRING), TEST_SERVICE_DATA, PARTIAL_SERVICE_DATA_MASK) .build(); testServiceDataFilter(filter, true); } private void testServiceDataFilter(ScanFilter filter, boolean partialServiceDataMatchResult) { ScanFilterQueue queue = new ScanFilterQueue(); queue.addScanFilter(filter); ScanFilterQueue.Entry entry = queue.pop(); assertThat(entry.type).isEqualTo(ScanFilterQueue.TYPE_SERVICE_DATA); assertThat(entry.data) .isEqualTo( Bytes.concat( BluetoothUuid.uuidToBytes(ParcelUuid.fromString(TEST_UUID_STRING)), TEST_SERVICE_DATA)); assertThat( serviceDataMatches( entry.data, Bytes.concat( BluetoothUuid.uuidToBytes( ParcelUuid.fromString(TEST_UUID_STRING)), TEST_SERVICE_DATA), entry.data_mask)) .isTrue(); assertThat( serviceDataMatches( entry.data, Bytes.concat( BluetoothUuid.uuidToBytes( ParcelUuid.fromString(UNMATCHED_UUID_STRING)), TEST_SERVICE_DATA), entry.data_mask)) .isFalse(); assertThat( serviceDataMatches( entry.data, Bytes.concat( BluetoothUuid.uuidToBytes( ParcelUuid.fromString(TEST_UUID_STRING)), UNMATCHED_SERVICE_DATA), entry.data_mask)) .isFalse(); assertThat( serviceDataMatches( entry.data, Bytes.concat( BluetoothUuid.uuidToBytes( ParcelUuid.fromString(TEST_UUID_STRING)), PARTIALLY_MATCHED_SERVICE_DATA), entry.data_mask)) .isEqualTo(partialServiceDataMatchResult); } private boolean serviceDataMatches(byte[] filterData, byte[] resultData, byte[] mask) { if (filterData.length > resultData.length || filterData.length != mask.length) { return false; } for (int i = 0; i < filterData.length; i++) { if ((filterData[i] & mask[i]) != (resultData[i] & mask[i])) { return false; } } return true; } }