Merge "Add icon drawable and Util class"
diff --git a/nearby/service/java/com/android/server/nearby/common/ble/testing/FastPairTestData.java b/nearby/service/java/com/android/server/nearby/common/ble/testing/FastPairTestData.java
new file mode 100644
index 0000000..f27899f
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/ble/testing/FastPairTestData.java
@@ -0,0 +1,141 @@
+/*
+ * 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.testing;
+
+import com.android.server.nearby.util.ArrayUtils;
+import com.android.server.nearby.util.Hex;
+
+/**
+ * Test class to provide example to unit test.
+ */
+public class FastPairTestData {
+ private static final byte[] FAST_PAIR_RECORD_BIG_ENDIAN =
+ Hex.stringToBytes("02011E020AF006162CFEAABBCC");
+
+ /**
+ * A Fast Pair frame, Note: The service UUID is FE2C, but in the
+ * packet it's 2CFE, since the core Bluetooth data types are little-endian.
+ *
+ * <p>However, the model ID is big-endian (multi-byte values in our spec are now big-endian, aka
+ * network byte order).
+ *
+ * @see {http://go/fast-pair-service-data}
+ */
+ public static byte[] getFastPairRecord() {
+ return FAST_PAIR_RECORD_BIG_ENDIAN;
+ }
+
+ /** A Fast Pair frame, with a shared account key. */
+ public static final byte[] FAST_PAIR_SHARED_ACCOUNT_KEY_RECORD =
+ Hex.stringToBytes("02011E020AF00C162CFE007011223344556677");
+
+ /** Model ID in {@link #getFastPairRecord()}. */
+ public static final byte[] FAST_PAIR_MODEL_ID = Hex.stringToBytes("AABBCC");
+
+ /** @see #getFastPairRecord() */
+ public static byte[] newFastPairRecord(byte header, byte[] modelId) {
+ return newFastPairRecord(
+ modelId.length == 3 ? modelId : ArrayUtils.concatByteArrays(new byte[] {header},
+ modelId));
+ }
+
+ /** @see #getFastPairRecord() */
+ public static byte[] newFastPairRecord(byte[] serviceData) {
+ int length = /* length of type and service UUID = */ 3 + serviceData.length;
+ return Hex.stringToBytes(
+ String.format("02011E020AF0%02X162CFE%s", length,
+ Hex.bytesToStringUppercase(serviceData)));
+ }
+
+ // This is an example of advertising data with AD types
+ public static byte[] adv_1 = {
+ 0x02, // Length of this Data
+ 0x01, // <<Flags>>
+ 0x01, // LE Limited Discoverable Mode
+ 0x0A, // Length of this Data
+ 0x09, // <<Complete local name>>
+ 'P', 'e', 'd', 'o', 'm', 'e', 't', 'e', 'r'
+ };
+
+ // This is an example of advertising data with positive TX Power
+ // Level.
+ public static byte[] adv_2 = {
+ 0x02, // Length of this Data
+ 0x0a, // <<TX Power Level>>
+ 127 // Level = 127
+ };
+
+ // Example data including a service data block
+ public static byte[] sd1 = {
+ 0x02, // Length of this Data
+ 0x01, // <<Flags>>
+ 0x04, // BR/EDR Not Supported.
+ 0x03, // Length of this Data
+ 0x02, // <<Incomplete List of 16-bit Service UUIDs>>
+ 0x04,
+ 0x18, // TX Power Service UUID
+ 0x1e, // Length of this Data
+ (byte) 0x16, // <<Service Specific Data>>
+ // Service UUID
+ (byte) 0xe0,
+ 0x00,
+ // gBeacon Header
+ 0x15,
+ // Running time ENCRYPT
+ (byte) 0xd2,
+ 0x77,
+ 0x01,
+ 0x00,
+ // Scan Freq ENCRYPT
+ 0x32,
+ 0x05,
+ // Time in slow mode
+ 0x00,
+ 0x00,
+ // Time in fast mode
+ 0x7f,
+ 0x17,
+ // Subset of UID
+ 0x56,
+ 0x00,
+ // ID Mask
+ (byte) 0xd4,
+ 0x7c,
+ 0x18,
+ // RFU (reserved)
+ 0x00,
+ // GUID = decimal 1297482358
+ 0x76,
+ 0x02,
+ 0x56,
+ 0x4d,
+ 0x00,
+ // Ranging Payload Header
+ 0x24,
+ // MAC of scanning address
+ (byte) 0xa4,
+ (byte) 0xbb,
+ // NORM RX RSSI -67dBm
+ (byte) 0xb0,
+ // NORM TX POWER -77dBm, so actual TX POWER = -36dBm
+ (byte) 0xb3,
+ // Note based on the values aboves PATH LOSS = (-36) - (-67) = 31dBm
+ // Below zero padding added to test it is handled correctly
+ 0x00
+ };
+
+}
diff --git a/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java b/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
index 40fefaf..03484a0 100644
--- a/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
@@ -56,7 +56,7 @@
sInstance = new FastPairDataProvider(context);
}
if (sInstance.mProxyFastPairDataProvider == null) {
- Log.wtf(TAG, "no proxy fast pair data provider found");
+ Log.w(TAG, "no proxy fast pair data provider found");
} else {
sInstance.mProxyFastPairDataProvider.register();
}
diff --git a/nearby/service/java/com/android/server/nearby/util/ArrayUtils.java b/nearby/service/java/com/android/server/nearby/util/ArrayUtils.java
new file mode 100644
index 0000000..599843c
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/util/ArrayUtils.java
@@ -0,0 +1,48 @@
+/*
+ * 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.util;
+
+import java.util.Arrays;
+
+/**
+ * ArrayUtils class that help manipulate array.
+ */
+public class ArrayUtils {
+ /** Concatenate N arrays of bytes into a single array. */
+ public static byte[] concatByteArrays(byte[]... arrays) {
+ // Degenerate case - no input provided.
+ if (arrays.length == 0) {
+ return new byte[0];
+ }
+
+ // Compute the total size.
+ int totalSize = 0;
+ for (int i = 0; i < arrays.length; i++) {
+ totalSize += arrays[i].length;
+ }
+
+ // Copy the arrays into the new array.
+ byte[] result = Arrays.copyOf(arrays[0], totalSize);
+ int pos = arrays[0].length;
+ for (int i = 1; i < arrays.length; i++) {
+ byte[] current = arrays[i];
+ System.arraycopy(current, 0, result, pos, current.length);
+ pos += current.length;
+ }
+ return result;
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/util/Hex.java b/nearby/service/java/com/android/server/nearby/util/Hex.java
index 446204e..1d1d855 100644
--- a/nearby/service/java/com/android/server/nearby/util/Hex.java
+++ b/nearby/service/java/com/android/server/nearby/util/Hex.java
@@ -20,6 +20,11 @@
* Hex class that contains hex related functions.
*/
public class Hex {
+
+ private static final char[] HEX_UPPERCASE = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+ };
+
private static final char[] HEX_LOWERCASE = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
@@ -37,4 +42,41 @@
}
return new String(hexChars);
}
+
+ /**
+ * Encodes the byte array to string.
+ */
+ public static String bytesToStringUppercase(byte[] bytes) {
+ return bytesToStringUppercase(bytes, false /* zeroTerminated */);
+ }
+
+ /** Encodes a byte array as a hexadecimal representation of bytes. */
+ public static String bytesToStringUppercase(byte[] bytes, boolean zeroTerminated) {
+ int length = bytes.length;
+ StringBuilder out = new StringBuilder(length * 2);
+ for (int i = 0; i < length; i++) {
+ if (zeroTerminated && i == length - 1 && (bytes[i] & 0xff) == 0) {
+ break;
+ }
+ out.append(HEX_UPPERCASE[(bytes[i] & 0xf0) >>> 4]);
+ out.append(HEX_UPPERCASE[bytes[i] & 0x0f]);
+ }
+ return out.toString();
+ }
+ /**
+ * Converts string to byte array.
+ */
+ public static byte[] stringToBytes(String hex) throws IllegalArgumentException {
+ int length = hex.length();
+ if (length % 2 != 0) {
+ throw new IllegalArgumentException("Hex string has odd number of characters");
+ }
+ byte[] out = new byte[length / 2];
+ for (int i = 0; i < length; i += 2) {
+ // Byte.parseByte() doesn't work here because it expects a hex value in -128, 127, and
+ // our hex values are in 0, 255.
+ out[i / 2] = (byte) Integer.parseInt(hex.substring(i, i + 2), 16);
+ }
+ return out;
+ }
}
diff --git a/nearby/tests/cts/fastpair/Android.bp b/nearby/tests/cts/fastpair/Android.bp
index 56dc682..9cbd7d0 100644
--- a/nearby/tests/cts/fastpair/Android.bp
+++ b/nearby/tests/cts/fastpair/Android.bp
@@ -39,5 +39,6 @@
],
platform_apis: true,
sdk_version: "module_current",
- min_sdk_version: "32",
+ min_sdk_version: "30",
+ target_sdk_version: "32",
}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/CredentialElementTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/CredentialElementTest.java
index 19a4a40..aacb6d8 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/CredentialElementTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/CredentialElementTest.java
@@ -25,6 +25,7 @@
import androidx.annotation.RequiresApi;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -38,6 +39,7 @@
private static final byte[] VALUE = new byte[]{1, 2, 3, 4};
@Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
public void testBuilder() {
CredentialElement element = new CredentialElement(KEY, VALUE);
@@ -46,6 +48,7 @@
}
@Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
public void testWriteParcel() {
CredentialElement element = new CredentialElement(KEY, VALUE);
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/DataElementTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/DataElementTest.java
index eb03a0d..ec6e89a 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/DataElementTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/DataElementTest.java
@@ -24,6 +24,7 @@
import androidx.annotation.RequiresApi;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,6 +40,7 @@
private static final byte[] VALUE = new byte[]{1, 1, 1, 1};
@Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
public void testBuilder() {
DataElement dataElement = new DataElement(KEY, VALUE);
@@ -47,6 +49,7 @@
}
@Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
public void testWriteParcel() {
DataElement dataElement = new DataElement(KEY, VALUE);
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
index f32ef12..938eab2 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
@@ -35,6 +35,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
import org.junit.Before;
import org.junit.Test;
@@ -66,6 +67,7 @@
}
@Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
public void test_startAndStopScan() {
ScanRequest scanRequest = new ScanRequest.Builder()
.setScanType(ScanRequest.SCAN_TYPE_FAST_PAIR)
@@ -92,6 +94,7 @@
}
@Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
public void testStartStopBroadcast() {
PrivateCredential credential = new PrivateCredential.Builder(SECRETE_ID, AUTHENTICITY_KEY)
.setIdentityType(IDENTITY_TYPE_PRIVATE)
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceBroadcastRequestTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceBroadcastRequestTest.java
index a37cc67..67d5aa4 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceBroadcastRequestTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceBroadcastRequestTest.java
@@ -30,6 +30,7 @@
import androidx.annotation.RequiresApi;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
import org.junit.Before;
import org.junit.Test;
@@ -74,6 +75,7 @@
}
@Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
public void testBuilder() {
PresenceBroadcastRequest broadcastRequest = mBuilder.build();
@@ -90,6 +92,7 @@
}
@Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
public void testWriteParcel() {
PresenceBroadcastRequest broadcastRequest = mBuilder.build();
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceDeviceTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceDeviceTest.java
index a1b282d..e0d9200 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceDeviceTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceDeviceTest.java
@@ -26,6 +26,7 @@
import androidx.annotation.RequiresApi;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -52,6 +53,7 @@
private static final long DISCOVERY_MILLIS = 100L;
@Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
public void testBuilder() {
PresenceDevice device = new PresenceDevice.Builder()
.setDeviceType(DEVICE_TYPE)
@@ -83,6 +85,7 @@
}
@Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
public void testWriteParcel() {
PresenceDevice device = new PresenceDevice.Builder()
.setDeviceId(DEVICE_ID)
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceScanFilterTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceScanFilterTest.java
index 308be9e..6b7c43b 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceScanFilterTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceScanFilterTest.java
@@ -29,6 +29,7 @@
import androidx.annotation.RequiresApi;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -63,6 +64,7 @@
.addExtendedProperty(new DataElement(KEY, VALUE));
@Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
public void testBuilder() {
PresenceScanFilter filter = mBuilder.build();
@@ -75,6 +77,7 @@
}
@Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
public void testWriteParcel() {
PresenceScanFilter filter = mBuilder.build();
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PrivateCredentialTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PrivateCredentialTest.java
index 5242999..1cd6d9c 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PrivateCredentialTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PrivateCredentialTest.java
@@ -28,6 +28,7 @@
import androidx.annotation.RequiresApi;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
import org.junit.Before;
import org.junit.Test;
@@ -60,6 +61,7 @@
}
@Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
public void testBuilder() {
PrivateCredential credential = mBuilder.build();
@@ -76,6 +78,7 @@
}
@Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
public void testWriteParcel() {
PrivateCredential credential = mBuilder.build();
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PublicCredentialTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PublicCredentialTest.java
index f750951..7756669 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PublicCredentialTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PublicCredentialTest.java
@@ -29,6 +29,7 @@
import androidx.annotation.RequiresApi;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
import org.junit.Before;
import org.junit.Test;
@@ -63,6 +64,7 @@
}
@Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
public void testBuilder() {
PublicCredential credential = mBuilder.build();
@@ -78,6 +80,7 @@
}
@Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
public void testWriteParcel() {
PublicCredential credential = mBuilder.build();
diff --git a/nearby/tests/unit/Android.bp b/nearby/tests/unit/Android.bp
index 9b5b14c..55ec645 100644
--- a/nearby/tests/unit/Android.bp
+++ b/nearby/tests/unit/Android.bp
@@ -43,6 +43,7 @@
"platform-test-annotations",
"service-nearby",
"truth-prebuilt",
+ "Robolectric_all-target",
],
test_suites: [
"general-tests",
diff --git a/nearby/tests/unit/AndroidTest.xml b/nearby/tests/unit/AndroidTest.xml
index 9124a07..fdf665d 100644
--- a/nearby/tests/unit/AndroidTest.xml
+++ b/nearby/tests/unit/AndroidTest.xml
@@ -22,7 +22,7 @@
<option name="test-suite-tag" value="apct" />
<option name="test-tag" value="NearbyUnitTests" />
<option name="config-descriptor:metadata" key="mainline-param"
- value="com.google.android.tethering.apex" />
+ value="com.google.android.tethering.next.apex" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.nearby.test" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleFilterTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleFilterTest.java
new file mode 100644
index 0000000..1d3653b
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleFilterTest.java
@@ -0,0 +1,476 @@
+/*
+ * 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.BluetoothDevice;
+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 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 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 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();
+ }
+
+ 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 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);
+ }
+ }
+
+ // 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.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()));
+ }
+
+ 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();
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java
index f4aff1d..5da98e2 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java
@@ -1,4 +1,20 @@
/*
+ * 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.
+ */
+
+/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleSightingTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleSightingTest.java
new file mode 100644
index 0000000..d356d8e
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleSightingTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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 java.nio.charset.StandardCharsets.UTF_8;
+
+import android.bluetooth.BluetoothDevice;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.shadows.ShadowBluetoothDevice;
+
+import java.util.concurrent.TimeUnit;
+
+
+@RunWith(AndroidJUnit4.class)
+public class BleSightingTest {
+ private static final String DEVICE_NAME = "device1";
+ private static final String OTHER_DEVICE_NAME = "device2";
+ private static final long TIME_EPOCH_MILLIS = 123456;
+ private static final long OTHER_TIME_EPOCH_MILLIS = 456789;
+ private static final int RSSI = 1;
+ private static final int OTHER_RSSI = 2;
+
+ private final BluetoothDevice mBluetoothDevice1 =
+ ShadowBluetoothDevice.newInstance("00:11:22:33:44:55");
+ private final BluetoothDevice mBluetoothDevice2 =
+ ShadowBluetoothDevice.newInstance("AA:BB:CC:DD:EE:FF");
+
+
+ @Test
+ public void testEquals() {
+ BleSighting sighting = buildBleSighting(mBluetoothDevice1, DEVICE_NAME,
+ TIME_EPOCH_MILLIS, RSSI);
+ BleSighting sighting2 =
+ buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+ assertThat(sighting.equals(sighting2)).isTrue();
+ assertThat(sighting2.equals(sighting)).isTrue();
+ assertThat(sighting.hashCode()).isEqualTo(sighting2.hashCode());
+
+ // Transitive property.
+ BleSighting sighting3 =
+ buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+ assertThat(sighting2.equals(sighting3)).isTrue();
+ assertThat(sighting.equals(sighting3)).isTrue();
+
+ // Set different values for each field, one at a time.
+ sighting2 = buildBleSighting(mBluetoothDevice2, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+ assertSightingsNotEquals(sighting, sighting2);
+
+ sighting2 = buildBleSighting(mBluetoothDevice1, OTHER_DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+ assertSightingsNotEquals(sighting, sighting2);
+
+ sighting2 = buildBleSighting(mBluetoothDevice1, DEVICE_NAME, OTHER_TIME_EPOCH_MILLIS, RSSI);
+ assertSightingsNotEquals(sighting, sighting2);
+
+ sighting2 = buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, OTHER_RSSI);
+ assertSightingsNotEquals(sighting, sighting2);
+ }
+
+
+ /** Builds a BleSighting instance which will correctly match filters by device name. */
+ private static BleSighting buildBleSighting(
+ BluetoothDevice bluetoothDevice, String deviceName, long timeEpochMillis, int rssi) {
+ byte[] nameBytes = deviceName.getBytes(UTF_8);
+ byte[] bleRecordBytes = new byte[nameBytes.length + 2];
+ bleRecordBytes[0] = (byte) (nameBytes.length + 1);
+ bleRecordBytes[1] = 0x09; // Value of private BleRecord.DATA_TYPE_LOCAL_NAME_COMPLETE;
+ System.arraycopy(nameBytes, 0, bleRecordBytes, 2, nameBytes.length);
+
+ return new BleSighting(
+ bluetoothDevice, bleRecordBytes, rssi,
+ TimeUnit.MILLISECONDS.toNanos(timeEpochMillis));
+ }
+
+ private static void assertSightingsNotEquals(BleSighting sighting1, BleSighting sighting2) {
+ assertThat(sighting1.equals(sighting2)).isFalse();
+ assertThat(sighting1.hashCode()).isNotEqualTo(sighting2.hashCode());
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/FastPairDecoderTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/FastPairDecoderTest.java
new file mode 100644
index 0000000..1ad04f8
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/FastPairDecoderTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.decode;
+
+import static com.android.server.nearby.common.ble.BleRecord.parseFromBytes;
+import static com.android.server.nearby.common.ble.testing.FastPairTestData.FAST_PAIR_MODEL_ID;
+import static com.android.server.nearby.common.ble.testing.FastPairTestData.getFastPairRecord;
+import static com.android.server.nearby.common.ble.testing.FastPairTestData.newFastPairRecord;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.nearby.common.ble.BleRecord;
+import com.android.server.nearby.util.Hex;
+
+import com.google.common.primitives.Bytes;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class FastPairDecoderTest {
+ private static final String LONG_MODEL_ID = "1122334455667788";
+ private final FastPairDecoder mDecoder = new FastPairDecoder();
+ // Bits 3-6 are model ID length bits = 0b1000 = 8
+ private static final byte LONG_MODEL_ID_HEADER = 0b00010000;
+ private static final String PADDED_LONG_MODEL_ID = "00001111";
+ // Bits 3-6 are model ID length bits = 0b0100 = 4
+ private static final byte PADDED_LONG_MODEL_ID_HEADER = 0b00001000;
+ private static final String TRIMMED_LONG_MODEL_ID = "001111";
+ private static final byte MODEL_ID_HEADER = 0b00000110;
+ private static final String MODEL_ID = "112233";
+ private static final byte BLOOM_FILTER_HEADER = 0b01100000;
+ private static final String BLOOM_FILTER = "112233445566";
+ private static final byte BLOOM_FILTER_SALT_HEADER = 0b00010001;
+ private static final String BLOOM_FILTER_SALT = "01";
+ private static final byte RANDOM_RESOLVABLE_DATA_HEADER = 0b01000110;
+ private static final String RANDOM_RESOLVABLE_DATA = "11223344";
+ private static final byte BLOOM_FILTER_NO_NOTIFICATION_HEADER = 0b01100010;
+
+
+ @Test
+ public void getModelId() {
+ assertThat(mDecoder.getBeaconIdBytes(parseFromBytes(getFastPairRecord())))
+ .isEqualTo(FAST_PAIR_MODEL_ID);
+ FastPairServiceData fastPairServiceData1 =
+ new FastPairServiceData(LONG_MODEL_ID_HEADER,
+ LONG_MODEL_ID);
+ assertThat(
+ mDecoder.getBeaconIdBytes(
+ newBleRecord(fastPairServiceData1.createServiceData())))
+ .isEqualTo(Hex.stringToBytes(LONG_MODEL_ID));
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(PADDED_LONG_MODEL_ID_HEADER,
+ PADDED_LONG_MODEL_ID);
+ assertThat(
+ mDecoder.getBeaconIdBytes(
+ newBleRecord(fastPairServiceData.createServiceData())))
+ .isEqualTo(Hex.stringToBytes(TRIMMED_LONG_MODEL_ID));
+ }
+
+ @Test
+ public void getBloomFilter() {
+ FastPairServiceData fastPairServiceData = new FastPairServiceData(MODEL_ID_HEADER,
+ MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+ fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+ assertThat(FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+ .isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
+ }
+
+ @Test
+ public void getBloomFilter_smallModelId() {
+ FastPairServiceData fastPairServiceData = new FastPairServiceData(null, MODEL_ID);
+ assertThat(FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+ .isNull();
+ }
+
+ @Test
+ public void getBloomFilterSalt_modelIdAndMultipleExtraFields() {
+ FastPairServiceData fastPairServiceData = new FastPairServiceData(MODEL_ID_HEADER,
+ MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_SALT_HEADER);
+ fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+ fastPairServiceData.mExtraFields.add(BLOOM_FILTER_SALT);
+ assertThat(
+ FastPairDecoder.getBloomFilterSalt(fastPairServiceData.createServiceData()))
+ .isEqualTo(Hex.stringToBytes(BLOOM_FILTER_SALT));
+ }
+
+ @Test
+ public void getRandomResolvableData_whenContainConnectionState() {
+ FastPairServiceData fastPairServiceData = new FastPairServiceData(MODEL_ID_HEADER,
+ MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add(RANDOM_RESOLVABLE_DATA_HEADER);
+ fastPairServiceData.mExtraFields.add(RANDOM_RESOLVABLE_DATA);
+ assertThat(
+ FastPairDecoder.getRandomResolvableData(fastPairServiceData
+ .createServiceData()))
+ .isEqualTo(Hex.stringToBytes(RANDOM_RESOLVABLE_DATA));
+ }
+
+ @Test
+ public void getBloomFilterNoNotification() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_NO_NOTIFICATION_HEADER);
+ fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+ assertThat(FastPairDecoder.getBloomFilterNoNotification(fastPairServiceData
+ .createServiceData())).isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
+ }
+
+ private static BleRecord newBleRecord(byte[] serviceDataBytes) {
+ return parseFromBytes(newFastPairRecord(serviceDataBytes));
+ }
+ class FastPairServiceData {
+ private Byte mHeader;
+ private String mModelId;
+ List<Byte> mExtraFieldHeaders = new ArrayList<>();
+ List<String> mExtraFields = new ArrayList<>();
+
+ FastPairServiceData(Byte header, String modelId) {
+ this.mHeader = header;
+ this.mModelId = modelId;
+ }
+ private byte[] createServiceData() {
+ if (mExtraFieldHeaders.size() != mExtraFields.size()) {
+ throw new RuntimeException("Number of headers and extra fields must match.");
+ }
+ byte[] serviceData =
+ Bytes.concat(
+ mHeader == null ? new byte[0] : new byte[] {mHeader},
+ mModelId == null ? new byte[0] : Hex.stringToBytes(mModelId));
+ for (int i = 0; i < mExtraFieldHeaders.size(); i++) {
+ serviceData =
+ Bytes.concat(
+ serviceData,
+ mExtraFieldHeaders.get(i) != null
+ ? new byte[] {mExtraFieldHeaders.get(i)}
+ : new byte[0],
+ mExtraFields.get(i) != null
+ ? Hex.stringToBytes(mExtraFields.get(i))
+ : new byte[0]);
+ }
+ return serviceData;
+ }
+ }
+
+
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java
new file mode 100644
index 0000000..2926015
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RangingUtilsTest {
+ // relative error to be used in comparing doubles
+ private static final double DELTA = 1e-5;
+
+ @Test
+ public void distanceFromRssi_getCorrectValue() {
+ // Distance expected to be 1.0 meters based on an RSSI/TxPower of -41dBm
+ // Using params: int rssi (dBm), int calibratedTxPower (dBm)
+ double distance = RangingUtils.distanceFromRssi(-41, -41);
+ assertThat(distance).isWithin(DELTA).of(1.0);
+
+ double distance2 = RangingUtils.distanceFromRssi(-70, -50);
+ assertThat(distance2).isWithin(DELTA).of(10.0);
+
+ // testing that the double values are not casted to integers
+ double distance3 = RangingUtils.distanceFromRssi(-67, -77);
+ assertThat(distance3).isWithin(DELTA).of(0.31622776601683794);
+
+ double distance4 = RangingUtils.distanceFromRssi(-50, -70);
+ assertThat(distance4).isWithin(DELTA).of(0.1);
+ }
+
+ @Test
+ public void testRssiFromDistance() {
+ // RSSI expected at 1 meter based on the calibrated tx field of -41dBm
+ // Using params: distance (m), int calibratedTxPower (dBm),
+ int rssi = RangingUtils.rssiFromDistance(1.0, -41);
+
+ assertThat(rssi).isEqualTo(-41);
+ }
+}