From 70169dc0b7e2c91083e7c44438a18321b1dac41e Mon Sep 17 00:00:00 2001 From: Alexandros Schillings Date: Fri, 3 Jul 2015 17:50:24 +0100 Subject: [PATCH] More tests and general code cleanup --- .idea/runConfigurations/Run_Android_Tests.xml | 27 +++++++++ .idea/runConfigurations/Run_JUnit_Tests.xml | 23 ++++++++ .idea/runConfigurations/Run_Sample_App.xml | 26 +++++++++ .../mfdata/IBeaconManufacturerData.java | 52 +++++++---------- .../bluetoothlelib/util/AdRecordUtils.java | 6 +- .../alt236/bluetoothlelib/util/ByteUtils.java | 4 ++ .../bluetoothlelib/util/IBeaconUtils.java | 51 +++++++++++----- .../util/AdRecordUtilsTest.java | 58 +++++++++++++++++++ 8 files changed, 200 insertions(+), 47 deletions(-) create mode 100644 .idea/runConfigurations/Run_Android_Tests.xml create mode 100644 .idea/runConfigurations/Run_JUnit_Tests.xml create mode 100644 .idea/runConfigurations/Run_Sample_App.xml create mode 100644 library/src/test/java/uk/co/alt236/bluetoothlelib/util/AdRecordUtilsTest.java diff --git a/.idea/runConfigurations/Run_Android_Tests.xml b/.idea/runConfigurations/Run_Android_Tests.xml new file mode 100644 index 0000000..1f32fb7 --- /dev/null +++ b/.idea/runConfigurations/Run_Android_Tests.xml @@ -0,0 +1,27 @@ + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Run_JUnit_Tests.xml b/.idea/runConfigurations/Run_JUnit_Tests.xml new file mode 100644 index 0000000..09ab733 --- /dev/null +++ b/.idea/runConfigurations/Run_JUnit_Tests.xml @@ -0,0 +1,23 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Run_Sample_App.xml b/.idea/runConfigurations/Run_Sample_App.xml new file mode 100644 index 0000000..8873b95 --- /dev/null +++ b/.idea/runConfigurations/Run_Sample_App.xml @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/library/src/main/java/uk/co/alt236/bluetoothlelib/device/mfdata/IBeaconManufacturerData.java b/library/src/main/java/uk/co/alt236/bluetoothlelib/device/mfdata/IBeaconManufacturerData.java index fe338cd..ea7adbf 100644 --- a/library/src/main/java/uk/co/alt236/bluetoothlelib/device/mfdata/IBeaconManufacturerData.java +++ b/library/src/main/java/uk/co/alt236/bluetoothlelib/device/mfdata/IBeaconManufacturerData.java @@ -5,6 +5,7 @@ import java.util.Arrays; import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice; import uk.co.alt236.bluetoothlelib.device.adrecord.AdRecord; import uk.co.alt236.bluetoothlelib.util.ByteUtils; +import uk.co.alt236.bluetoothlelib.util.IBeaconUtils; /** * Parses the Manufactured Data field of an iBeacon @@ -52,6 +53,12 @@ public final class IBeaconManufacturerData { private final int mMinor; private final String mUUID; + /** + * Instantiates a new iBeacon manufacturer data object. + * + * @param device a {@link BluetoothLeDevice} + * @throws IllegalArgumentException if the data is not from an iBeacon. + */ public IBeaconManufacturerData(final BluetoothLeDevice device) { this(device.getAdRecordStore().getRecord(AdRecord.TYPE_MANUFACTURER_SPECIFIC_DATA).getData()); } @@ -59,22 +66,28 @@ public final class IBeaconManufacturerData { /** * Instantiates a new iBeacon manufacturer data object. * - * @param data the {@link uk.co.alt236.bluetoothlelib.device.adrecord.AdRecord#TYPE_MANUFACTURER_SPECIFIC_DATA} data array - * @throws IndexOutOfBoundsException if the data array is shorter than expected + * @param manufacturerData the {@link AdRecord#TYPE_MANUFACTURER_SPECIFIC_DATA} data array + * @throws IllegalArgumentException if the data is not from an iBeacon. */ - public IBeaconManufacturerData(final byte[] data) { - mData = data; + public IBeaconManufacturerData(final byte[] manufacturerData) { + mData = manufacturerData; + + if (!IBeaconUtils.isThisAnIBeacon(manufacturerData)) { + throw new IllegalArgumentException( + "Manufacturer record '" + + Arrays.toString(manufacturerData) + + "' is not from an iBeacon."); + } final byte[] intArray = Arrays.copyOfRange(mData, 0, 2); ByteUtils.invertArray(intArray); mCompanyIdentidier = ByteUtils.getIntFrom2ByteArray(intArray); - mIBeaconAdvertisment = ByteUtils.getIntFrom2ByteArray(Arrays.copyOfRange(mData, 2, 4)); - mUUID = calculateUUIDString(Arrays.copyOfRange(mData, 4, 20)); + mUUID = IBeaconUtils.calculateUuidString(Arrays.copyOfRange(mData, 4, 20)); mMajor = ByteUtils.getIntFrom2ByteArray(Arrays.copyOfRange(mData, 20, 22)); mMinor = ByteUtils.getIntFrom2ByteArray(Arrays.copyOfRange(mData, 22, 24)); - mCalibratedTxPower = data[24]; + mCalibratedTxPower = mData[24]; } /** @@ -125,29 +138,4 @@ public final class IBeaconManufacturerData { public String getUUID() { return mUUID; } - - private static String calculateUUIDString(final byte[] uuid) { - final StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < uuid.length; i++) { - if (i == 4) { - sb.append('-'); - } - if (i == 6) { - sb.append('-'); - } - if (i == 8) { - sb.append('-'); - } - if (i == 10) { - sb.append('-'); - } - - sb.append( - Integer.toHexString(ByteUtils.getIntFromByte(uuid[i]))); - } - - - return sb.toString(); - } } diff --git a/library/src/main/java/uk/co/alt236/bluetoothlelib/util/AdRecordUtils.java b/library/src/main/java/uk/co/alt236/bluetoothlelib/util/AdRecordUtils.java index cc87d8a..c872f82 100644 --- a/library/src/main/java/uk/co/alt236/bluetoothlelib/util/AdRecordUtils.java +++ b/library/src/main/java/uk/co/alt236/bluetoothlelib/util/AdRecordUtils.java @@ -12,7 +12,11 @@ import java.util.Map; import uk.co.alt236.bluetoothlelib.device.adrecord.AdRecord; -public class AdRecordUtils { +public final class AdRecordUtils { + + private AdRecordUtils(){ + // TO AVOID INSTANTIATION + } public static String getRecordDataAsString(final AdRecord nameRecord) { if (nameRecord == null) { diff --git a/library/src/main/java/uk/co/alt236/bluetoothlelib/util/ByteUtils.java b/library/src/main/java/uk/co/alt236/bluetoothlelib/util/ByteUtils.java index 5d4aed0..53f009f 100644 --- a/library/src/main/java/uk/co/alt236/bluetoothlelib/util/ByteUtils.java +++ b/library/src/main/java/uk/co/alt236/bluetoothlelib/util/ByteUtils.java @@ -9,6 +9,10 @@ public class ByteUtils { */ private static final String HEXES = "0123456789ABCDEF"; + private ByteUtils(){ + // TO AVOID INSTANTIATION + } + /** * Gets a pretty representation of a Byte Array as a HEX String. *

diff --git a/library/src/main/java/uk/co/alt236/bluetoothlelib/util/IBeaconUtils.java b/library/src/main/java/uk/co/alt236/bluetoothlelib/util/IBeaconUtils.java index b992e4a..084f15a 100644 --- a/library/src/main/java/uk/co/alt236/bluetoothlelib/util/IBeaconUtils.java +++ b/library/src/main/java/uk/co/alt236/bluetoothlelib/util/IBeaconUtils.java @@ -13,7 +13,7 @@ public class IBeaconUtils { /** * Calculates the accuracy of an RSSI reading. *

- * The code was taken from {@linktourl http://stackoverflow.com/questions/20416218/understanding-ibeacon-distancing} + * The code was taken from * * @param txPower the calibrated TX power of an iBeacon * @param rssi the RSSI value of the iBeacon @@ -28,11 +28,35 @@ public class IBeaconUtils { if (ratio < 1.0) { return Math.pow(ratio, 10); } else { - final double accuracy = (0.89976) * Math.pow(ratio, 7.7095) + 0.111; - return accuracy; + return (0.89976) * Math.pow(ratio, 7.7095) + 0.111; } } + public static String calculateUuidString(final byte[] uuid) { + final StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < uuid.length; i++) { + if (i == 4) { + sb.append('-'); + } + if (i == 6) { + sb.append('-'); + } + if (i == 8) { + sb.append('-'); + } + if (i == 10) { + sb.append('-'); + } + + final int intFromByte = ByteUtils.getIntFromByte(uuid[i]); + sb.append(Integer.toHexString(intFromByte)); + } + + + return sb.toString(); + } + public static IBeaconDistanceDescriptor getDistanceDescriptor(final double accuracy) { if (accuracy < DISTANCE_THRESHOLD_WTF) { return IBeaconDistanceDescriptor.UNKNOWN; @@ -49,17 +73,6 @@ public class IBeaconUtils { return IBeaconDistanceDescriptor.FAR; } - /** - * Ascertains whether a {@link uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice} is an iBeacon; - * - * @param device a {@link uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice} device. - * @return true if the device is an iBeacon, false otherwise - */ - public static boolean isThisAnIBeacon(final BluetoothLeDevice device) { - return isThisAnIBeacon( - device.getAdRecordStore().getRecordDataAsString(AdRecord.TYPE_MANUFACTURER_SPECIFIC_DATA).getBytes()); - } - /** * Ascertains whether a Manufacturer Data byte array belongs to an iBeacon; * @@ -83,4 +96,14 @@ public class IBeaconUtils { return false; } + /** + * Ascertains whether a {@link uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice} is an iBeacon; + * + * @param device a {@link uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice} device. + * @return true if the device is an iBeacon, false otherwise + */ + public static boolean isThisAnIBeacon(final BluetoothLeDevice device) { + return isThisAnIBeacon( + device.getAdRecordStore().getRecordDataAsString(AdRecord.TYPE_MANUFACTURER_SPECIFIC_DATA).getBytes()); + } } diff --git a/library/src/test/java/uk/co/alt236/bluetoothlelib/util/AdRecordUtilsTest.java b/library/src/test/java/uk/co/alt236/bluetoothlelib/util/AdRecordUtilsTest.java new file mode 100644 index 0000000..455bb6e --- /dev/null +++ b/library/src/test/java/uk/co/alt236/bluetoothlelib/util/AdRecordUtilsTest.java @@ -0,0 +1,58 @@ +package uk.co.alt236.bluetoothlelib.util; + +import junit.framework.TestCase; + +import java.util.List; +import java.util.Map; + +import uk.co.alt236.bluetoothlelib.device.adrecord.AdRecord; + +/** + * + */ +public class AdRecordUtilsTest extends TestCase { + private static final byte[] NON_IBEACON = + {2, 1, 26, 11, -1, 76, 0, 9, 6, 3, -32, -64, -88, + 1, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + public void testParseScanRecordAsList() throws Exception { + final List adRecords = AdRecordUtils.parseScanRecordAsList(NON_IBEACON); + assertNotNull(adRecords); + assertEquals(2, adRecords.size()); + + int type = AdRecord.TYPE_FLAGS; + assertEquals(type, adRecords.get(0).getType()); + assertEquals(2, adRecords.get(0).getLength()); + + type = AdRecord.TYPE_MANUFACTURER_SPECIFIC_DATA; + assertEquals(type, adRecords.get(1).getType()); + assertEquals(11, adRecords.get(1).getLength()); + } + + public void testParseScanRecordAsMap() throws Exception { + final Map adRecords = AdRecordUtils.parseScanRecordAsMap(NON_IBEACON); + assertNotNull(adRecords); + assertEquals(2, adRecords.size()); + + int type = AdRecord.TYPE_FLAGS; + assertEquals(type, adRecords.get(type).getType()); + assertEquals(2, adRecords.get(type).getLength()); + + type = AdRecord.TYPE_MANUFACTURER_SPECIFIC_DATA; + assertEquals(type, adRecords.get(type).getType()); + assertEquals(11, adRecords.get(type).getLength()); + } + + public void testParseScanRecordAsSparseArray() throws Exception { + // + // Cannot be tested here as it relies on Android code... + // +// final SparseArray adRecords = AdRecordUtils.parseScanRecordAsSparseArray(NON_IBEACON); +// assertNotNull(adRecords); +// assertEquals(2, adRecords.size()); +// assertEquals(AdRecord.TYPE_FLAGS, adRecords.get(AdRecord.TYPE_FLAGS).getType()); +// assertEquals(AdRecord.TYPE_MANUFACTURER_SPECIFIC_DATA, adRecords.get(AdRecord.TYPE_MANUFACTURER_SPECIFIC_DATA).getType()); + } +} \ No newline at end of file