diff --git a/README.md b/README.md index e0411c6..e8fdbfc 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ In the `onLeScan()` method of your `BluetoothAdapter.LeScanCallback()` create a For example: -
+```
    private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
    
         @Override
@@ -51,7 +51,7 @@ For example:
 			});
 		}
 	};
-
+``` ### Device Properties @@ -83,7 +83,17 @@ Once you've created a BluetoothLe device, you can access the AdRecord store via They are also declared as constants in `AdRecord.java`. ### Fun with iBeacons -You can check if a device is an iBeacon by using `IBeaconUtils.isThisAnIBeacon(BluetootLeDevice device)`. Once you have confirmed that it is, you can create a new IBeaconDevice via the IBeaconDevice constructor. +You can check if a device is an iBeacon by using `BeaconUtils.getBeaconType(BluetootLeDevice device)`. Once you have confirmed that it is, you can create a new IBeaconDevice via the IBeaconDevice constructor. + +Example Flow: +``` + final BluetoothLeDevice device = ... // A generic BLE device + + if (BeaconUtils.getBeaconType(device) == BeaconType.IBEACON) { + final IBeaconDevice iBeacon = new IBeaconDevice(device); + // DO STUFF + } +``` An IBeaconDevice extends BluetoothLeDevice, so you still have access to the same methods as before. In addition you can do the following: @@ -114,6 +124,7 @@ You can also lookup values and convert them to human friendly strings: * Added some Estimote UUIDs * v1.0.0: * Migrated project to Android Studio/ gradle + * We now use the more generic `BeaconUtils.getBeaconType()` method instead of `IBeaconUtils.isThisAnIBeacon()` * Fix for [issue 5](https://github.com/alt236/Bluetooth-LE-Library---Android/issues/5) * Fix for [issue 9](https://github.com/alt236/Bluetooth-LE-Library---Android/issues/9) diff --git a/library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/BeaconType.java b/library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/BeaconType.java new file mode 100644 index 0000000..d843150 --- /dev/null +++ b/library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/BeaconType.java @@ -0,0 +1,9 @@ +package uk.co.alt236.bluetoothlelib.device.beacon; + +/** + * + */ +public enum BeaconType { + NOT_A_BEACON, + IBEACON +} diff --git a/library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/BeaconUtils.java b/library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/BeaconUtils.java new file mode 100644 index 0000000..9bafee9 --- /dev/null +++ b/library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/BeaconUtils.java @@ -0,0 +1,58 @@ +package uk.co.alt236.bluetoothlelib.device.beacon; + +import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice; +import uk.co.alt236.bluetoothlelib.device.adrecord.AdRecord; +import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconConstants; +import uk.co.alt236.bluetoothlelib.util.ByteUtils; + +/** + * + */ +public final class BeaconUtils { + + private BeaconUtils(){ + // TO AVOID INSTANTIATION + } + + /** + * Ascertains whether a Manufacturer Data byte array belongs to a known Beacon type; + * + * @param manufacturerData a Bluetooth LE device's raw manufacturerData. + * @return the {@link BeaconType} + */ + public static BeaconType getBeaconType(final byte[] manufacturerData) { + if (manufacturerData == null) { + return BeaconType.NOT_A_BEACON; + } + + if(isIBeacon(manufacturerData)){ + return BeaconType.IBEACON; + } else { + return BeaconType.NOT_A_BEACON; + } + } + + /** + * 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 the {@link BeaconType} + */ + public static BeaconType getBeaconType(final BluetoothLeDevice device) { + final int key = AdRecord.TYPE_MANUFACTURER_SPECIFIC_DATA; + return getBeaconType(device.getAdRecordStore().getRecordDataAsString(key).getBytes()); + } + + private static boolean isIBeacon(final byte[] manufacturerData){ + // An iBeacon record must be at least 25 chars long + if (!(manufacturerData.length >= 25)) { + return false; + } + + if (ByteUtils.doesArrayBeginWith(manufacturerData, IBeaconConstants.MANUFACTURER_DATA_IBEACON_PREFIX)) { + return true; + } + + return false; + } +} diff --git a/library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/ibeacon/IBeaconConstants.java b/library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/ibeacon/IBeaconConstants.java new file mode 100644 index 0000000..6c1914f --- /dev/null +++ b/library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/ibeacon/IBeaconConstants.java @@ -0,0 +1,9 @@ +package uk.co.alt236.bluetoothlelib.device.beacon.ibeacon; + +/** + * + */ +public class IBeaconConstants { + public static final byte[] MANUFACTURER_DATA_IBEACON_PREFIX = {0x4C, 0x00, 0x02, 0x15}; + +} diff --git a/library/src/main/java/uk/co/alt236/bluetoothlelib/device/IBeaconDevice.java b/library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/ibeacon/IBeaconDevice.java similarity index 92% rename from library/src/main/java/uk/co/alt236/bluetoothlelib/device/IBeaconDevice.java rename to library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/ibeacon/IBeaconDevice.java index d6dbdd5..abb2c96 100644 --- a/library/src/main/java/uk/co/alt236/bluetoothlelib/device/IBeaconDevice.java +++ b/library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/ibeacon/IBeaconDevice.java @@ -1,11 +1,11 @@ -package uk.co.alt236.bluetoothlelib.device; +package uk.co.alt236.bluetoothlelib.device.beacon.ibeacon; import android.bluetooth.BluetoothDevice; import android.os.Parcel; -import uk.co.alt236.bluetoothlelib.device.mfdata.IBeaconManufacturerData; -import uk.co.alt236.bluetoothlelib.util.IBeaconDistanceDescriptor; -import uk.co.alt236.bluetoothlelib.util.IBeaconUtils; +import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice; +import uk.co.alt236.bluetoothlelib.device.beacon.BeaconType; +import uk.co.alt236.bluetoothlelib.device.beacon.BeaconUtils; public class IBeaconDevice extends BluetoothLeDevice { @@ -139,7 +139,7 @@ public class IBeaconDevice extends BluetoothLeDevice { } private void validate() { - if (!IBeaconUtils.isThisAnIBeacon(this)) { + if (BeaconUtils.getBeaconType(this) != BeaconType.IBEACON) { throw new IllegalArgumentException("Device " + getDevice() + " is not an iBeacon."); } } diff --git a/library/src/main/java/uk/co/alt236/bluetoothlelib/util/IBeaconDistanceDescriptor.java b/library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/ibeacon/IBeaconDistanceDescriptor.java similarity index 60% rename from library/src/main/java/uk/co/alt236/bluetoothlelib/util/IBeaconDistanceDescriptor.java rename to library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/ibeacon/IBeaconDistanceDescriptor.java index 10d4a40..413e63e 100644 --- a/library/src/main/java/uk/co/alt236/bluetoothlelib/util/IBeaconDistanceDescriptor.java +++ b/library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/ibeacon/IBeaconDistanceDescriptor.java @@ -1,4 +1,4 @@ -package uk.co.alt236.bluetoothlelib.util; +package uk.co.alt236.bluetoothlelib.device.beacon.ibeacon; public enum IBeaconDistanceDescriptor { IMMEDIATE, 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/beacon/ibeacon/IBeaconManufacturerData.java similarity index 93% rename from library/src/main/java/uk/co/alt236/bluetoothlelib/device/mfdata/IBeaconManufacturerData.java rename to library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/ibeacon/IBeaconManufacturerData.java index ea7adbf..25b14ce 100644 --- a/library/src/main/java/uk/co/alt236/bluetoothlelib/device/mfdata/IBeaconManufacturerData.java +++ b/library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/ibeacon/IBeaconManufacturerData.java @@ -1,11 +1,12 @@ -package uk.co.alt236.bluetoothlelib.device.mfdata; +package uk.co.alt236.bluetoothlelib.device.beacon.ibeacon; import java.util.Arrays; import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice; import uk.co.alt236.bluetoothlelib.device.adrecord.AdRecord; +import uk.co.alt236.bluetoothlelib.device.beacon.BeaconType; +import uk.co.alt236.bluetoothlelib.device.beacon.BeaconUtils; import uk.co.alt236.bluetoothlelib.util.ByteUtils; -import uk.co.alt236.bluetoothlelib.util.IBeaconUtils; /** * Parses the Manufactured Data field of an iBeacon @@ -72,7 +73,7 @@ public final class IBeaconManufacturerData { public IBeaconManufacturerData(final byte[] manufacturerData) { mData = manufacturerData; - if (!IBeaconUtils.isThisAnIBeacon(manufacturerData)) { + if (BeaconUtils.getBeaconType(mData) != BeaconType.IBEACON) { throw new IllegalArgumentException( "Manufacturer record '" + Arrays.toString(manufacturerData) diff --git a/library/src/main/java/uk/co/alt236/bluetoothlelib/util/IBeaconUtils.java b/library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/ibeacon/IBeaconUtils.java similarity index 59% rename from library/src/main/java/uk/co/alt236/bluetoothlelib/util/IBeaconUtils.java rename to library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/ibeacon/IBeaconUtils.java index f38b995..1d8d552 100644 --- a/library/src/main/java/uk/co/alt236/bluetoothlelib/util/IBeaconUtils.java +++ b/library/src/main/java/uk/co/alt236/bluetoothlelib/device/beacon/ibeacon/IBeaconUtils.java @@ -1,14 +1,15 @@ -package uk.co.alt236.bluetoothlelib.util; +package uk.co.alt236.bluetoothlelib.device.beacon.ibeacon; -import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice; -import uk.co.alt236.bluetoothlelib.device.adrecord.AdRecord; +import uk.co.alt236.bluetoothlelib.util.ByteUtils; -public class IBeaconUtils { +final class IBeaconUtils { private static final double DISTANCE_THRESHOLD_WTF = 0.0; private static final double DISTANCE_THRESHOLD_IMMEDIATE = 0.5; private static final double DISTANCE_THRESHOLD_NEAR = 3.0; - private static final byte[] MANUFACTURER_DATA_IBEACON_PREFIX = {0x4C, 0x00, 0x02, 0x15}; + private IBeaconUtils(){ + // TO AVOID INSTANTIATION + } /** * Calculates the accuracy of an RSSI reading. @@ -75,38 +76,4 @@ public class IBeaconUtils { return IBeaconDistanceDescriptor.FAR; } - - /** - * Ascertains whether a Manufacturer Data byte array belongs to an iBeacon; - * - * @param manufacturerData a Bluetooth LE device's raw manufacturerData. - * @return true if the manufacturer data belong to an iBeacon - */ - public static boolean isThisAnIBeacon(final byte[] manufacturerData) { - if (manufacturerData == null) { - return false; - } - - // An iBeacon record must be at least 25 chars long - if (!(manufacturerData.length >= 25)) { - return false; - } - - if (ByteUtils.doesArrayBeginWith(manufacturerData, MANUFACTURER_DATA_IBEACON_PREFIX)) { - return true; - } - - 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) { - final int key = AdRecord.TYPE_MANUFACTURER_SPECIFIC_DATA; - return isThisAnIBeacon(device.getAdRecordStore().getRecordDataAsString(key).getBytes()); - } } diff --git a/library/src/test/java/uk/co/alt236/bluetoothlelib/device/beacon/BeaconUtilsTest.java b/library/src/test/java/uk/co/alt236/bluetoothlelib/device/beacon/BeaconUtilsTest.java new file mode 100644 index 0000000..840215f --- /dev/null +++ b/library/src/test/java/uk/co/alt236/bluetoothlelib/device/beacon/BeaconUtilsTest.java @@ -0,0 +1,25 @@ +package uk.co.alt236.bluetoothlelib.device.beacon; + +import junit.framework.TestCase; + +/** + * + */ +public class BeaconUtilsTest extends TestCase { + + public void testGetBeaconTypeInvalid() throws Exception { + assertEquals(BeaconType.NOT_A_BEACON, BeaconUtils.getBeaconType((byte[]) null)); + assertEquals(BeaconType.NOT_A_BEACON, BeaconUtils.getBeaconType(new byte[0])); + assertEquals(BeaconType.NOT_A_BEACON, BeaconUtils.getBeaconType(new byte[25])); + } + + public void testGetBeaconTypeIBeacon() throws Exception { + assertEquals(BeaconType.IBEACON, BeaconUtils.getBeaconType(new byte[]{ + 0x4C, 0x00, 0x02, 0x15, 0x00, // <- Magic iBeacon header + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00 + })); + } +} \ No newline at end of file diff --git a/library/src/test/java/uk/co/alt236/bluetoothlelib/util/IBeaconUtilsTest.java b/library/src/test/java/uk/co/alt236/bluetoothlelib/device/beacon/ibeacon/IBeaconUtilsTest.java similarity index 68% rename from library/src/test/java/uk/co/alt236/bluetoothlelib/util/IBeaconUtilsTest.java rename to library/src/test/java/uk/co/alt236/bluetoothlelib/device/beacon/ibeacon/IBeaconUtilsTest.java index be5ffdc..681064f 100644 --- a/library/src/test/java/uk/co/alt236/bluetoothlelib/util/IBeaconUtilsTest.java +++ b/library/src/test/java/uk/co/alt236/bluetoothlelib/device/beacon/ibeacon/IBeaconUtilsTest.java @@ -1,4 +1,4 @@ -package uk.co.alt236.bluetoothlelib.util; +package uk.co.alt236.bluetoothlelib.device.beacon.ibeacon; import junit.framework.TestCase; @@ -7,20 +7,6 @@ import junit.framework.TestCase; */ public class IBeaconUtilsTest extends TestCase { - public void testIsThisAnIBeacon() throws Exception { - assertFalse(IBeaconUtils.isThisAnIBeacon((byte[]) null)); - assertFalse(IBeaconUtils.isThisAnIBeacon(new byte[0])); - assertFalse(IBeaconUtils.isThisAnIBeacon(new byte[25])); - - assertTrue(IBeaconUtils.isThisAnIBeacon(new byte[]{ - 0x4C, 0x00, 0x02, 0x15, 0x00, // <- Magic iBeacon header - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00 - })); - } - public void testGetDistanceDescriptor() throws Exception { assertEquals(IBeaconDistanceDescriptor.UNKNOWN, IBeaconUtils.getDistanceDescriptor(-1)); diff --git a/sample_app/src/main/java/uk/co/alt236/btlescan/activities/DeviceDetailsActivity.java b/sample_app/src/main/java/uk/co/alt236/btlescan/activities/DeviceDetailsActivity.java index bb6abdb..1e3e10f 100644 --- a/sample_app/src/main/java/uk/co/alt236/btlescan/activities/DeviceDetailsActivity.java +++ b/sample_app/src/main/java/uk/co/alt236/btlescan/activities/DeviceDetailsActivity.java @@ -21,11 +21,12 @@ import butterknife.ButterKnife; import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice; import uk.co.alt236.bluetoothlelib.device.BluetoothService; import uk.co.alt236.bluetoothlelib.device.adrecord.AdRecord; -import uk.co.alt236.bluetoothlelib.device.mfdata.IBeaconManufacturerData; +import uk.co.alt236.bluetoothlelib.device.beacon.BeaconType; +import uk.co.alt236.bluetoothlelib.device.beacon.BeaconUtils; +import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconManufacturerData; import uk.co.alt236.bluetoothlelib.resolvers.CompanyIdentifierResolver; import uk.co.alt236.bluetoothlelib.util.AdRecordUtils; import uk.co.alt236.bluetoothlelib.util.ByteUtils; -import uk.co.alt236.bluetoothlelib.util.IBeaconUtils; import uk.co.alt236.btlescan.R; import uk.co.alt236.btlescan.util.TimeFormatter; @@ -218,7 +219,7 @@ public class DeviceDetailsActivity extends AppCompatActivity { } } - final boolean isIBeacon = IBeaconUtils.isThisAnIBeacon(device); + final boolean isIBeacon = BeaconUtils.getBeaconType(device) == BeaconType.IBEACON; if (isIBeacon) { final IBeaconManufacturerData iBeaconData = new IBeaconManufacturerData(device); appendHeader(adapter, getString(R.string.header_ibeacon_data)); diff --git a/sample_app/src/main/java/uk/co/alt236/btlescan/adapters/LeDeviceListAdapter.java b/sample_app/src/main/java/uk/co/alt236/btlescan/adapters/LeDeviceListAdapter.java index f7e8bf3..97cbdbc 100644 --- a/sample_app/src/main/java/uk/co/alt236/btlescan/adapters/LeDeviceListAdapter.java +++ b/sample_app/src/main/java/uk/co/alt236/btlescan/adapters/LeDeviceListAdapter.java @@ -9,8 +9,9 @@ import android.widget.ImageView; import android.widget.TextView; import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice; -import uk.co.alt236.bluetoothlelib.device.IBeaconDevice; -import uk.co.alt236.bluetoothlelib.util.IBeaconUtils; +import uk.co.alt236.bluetoothlelib.device.beacon.BeaconType; +import uk.co.alt236.bluetoothlelib.device.beacon.BeaconUtils; +import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconDevice; import uk.co.alt236.btlescan.R; import uk.co.alt236.btlescan.util.Constants; import uk.co.alt236.easycursor.objectcursor.EasyObjectCursor; @@ -76,7 +77,7 @@ public class LeDeviceListAdapter extends SimpleCursorAdapter { viewHolder.deviceName.setText(R.string.unknown_device); } - if (IBeaconUtils.isThisAnIBeacon(device)) { + if (BeaconUtils.getBeaconType(device) == BeaconType.IBEACON) { final IBeaconDevice iBeacon = new IBeaconDevice(device); final String accuracy = Constants.DOUBLE_TWO_DIGIT_ACCURACY.format(iBeacon.getAccuracy()); diff --git a/sample_app/src/main/java/uk/co/alt236/btlescan/containers/BluetoothLeDeviceStore.java b/sample_app/src/main/java/uk/co/alt236/btlescan/containers/BluetoothLeDeviceStore.java index ec3b9c1..8b99033 100644 --- a/sample_app/src/main/java/uk/co/alt236/btlescan/containers/BluetoothLeDeviceStore.java +++ b/sample_app/src/main/java/uk/co/alt236/btlescan/containers/BluetoothLeDeviceStore.java @@ -16,9 +16,10 @@ import java.util.Locale; import java.util.Map; import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice; -import uk.co.alt236.bluetoothlelib.device.IBeaconDevice; +import uk.co.alt236.bluetoothlelib.device.beacon.BeaconType; +import uk.co.alt236.bluetoothlelib.device.beacon.BeaconUtils; +import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconDevice; import uk.co.alt236.bluetoothlelib.util.ByteUtils; -import uk.co.alt236.bluetoothlelib.util.IBeaconUtils; import uk.co.alt236.btlescan.R; import uk.co.alt236.btlescan.util.CsvWriterHelper; import uk.co.alt236.btlescan.util.TimeFormatter; @@ -92,7 +93,7 @@ public class BluetoothLeDeviceStore { sb.append(CsvWriterHelper.addStuff(TimeFormatter.getIsoDateTime(device.getTimestamp()))); sb.append(CsvWriterHelper.addStuff(device.getRssi())); sb.append(CsvWriterHelper.addStuff(ByteUtils.byteArrayToHexString(device.getScanRecord()))); - final boolean isIBeacon = IBeaconUtils.isThisAnIBeacon(device); + final boolean isIBeacon = BeaconUtils.getBeaconType(device) == BeaconType.IBEACON; final String uuid; final String minor; final String major;