diff --git a/src/uk/co/alt236/btlescan/activities/DetailsActivity.java b/src/uk/co/alt236/btlescan/activities/DetailsActivity.java index 29d1985..7b090e0 100644 --- a/src/uk/co/alt236/btlescan/activities/DetailsActivity.java +++ b/src/uk/co/alt236/btlescan/activities/DetailsActivity.java @@ -5,8 +5,10 @@ import java.util.Collection; import uk.co.alt236.btlescan.R; import uk.co.alt236.btlescan.containers.AdRecord; import uk.co.alt236.btlescan.containers.BluetoothLeDevice; +import uk.co.alt236.btlescan.containers.ManufacturerDataIBeacon; import uk.co.alt236.btlescan.util.AdRecordUtils; import uk.co.alt236.btlescan.util.ByteUtils; +import uk.co.alt236.btlescan.util.IBeaconUtils; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; @@ -61,11 +63,38 @@ public class DetailsActivity extends Activity{ append(sb, AdRecordUtils.getRecordDataAsString(record), null); append(sb, "", null); } + + append(sb, "Additional", null); + append(sb, "-----------------", null); + final boolean isIBeacon = IBeaconUtils.isThisAnIBeacon(device); + append(sb, "Is iBeacon", isIBeacon); + + if(isIBeacon){ + final ManufacturerDataIBeacon iBeaconData = new ManufacturerDataIBeacon(device); + append(sb, "Company ID", iBeaconData.getCompanyIdentifier()); + append(sb, "iBeacon Advertisment", iBeaconData.getIBeaconAdvertisement()); + append(sb, "UUID", iBeaconData.getUUID()); + append(sb, "Major", iBeaconData.getMajor()); + append(sb, "Minor", iBeaconData.getMinor()); + append(sb, "TX Power", iBeaconData.getCalibratedTxPower()); + } } mTvDetails.setText(sb.toString()); } + private void append(StringBuilder sb, String label, boolean value) { + append(sb, label, String.valueOf(value)); + } + + private void append(StringBuilder sb, String label, int value) { + append(sb, label, String.valueOf(value)); + } + + private void append(StringBuilder sb, String label, long value) { + append(sb, label, String.valueOf(value)); + } + private static void append(StringBuilder sb, byte[] value){ append(sb, ByteUtils.byteArrayToHexString(value), null); } diff --git a/src/uk/co/alt236/btlescan/containers/ManufacturerDataIBeacon.java b/src/uk/co/alt236/btlescan/containers/ManufacturerDataIBeacon.java index db5078a..8dc8af2 100644 --- a/src/uk/co/alt236/btlescan/containers/ManufacturerDataIBeacon.java +++ b/src/uk/co/alt236/btlescan/containers/ManufacturerDataIBeacon.java @@ -1,41 +1,88 @@ package uk.co.alt236.btlescan.containers; +import java.util.Arrays; + +import uk.co.alt236.btlescan.util.ByteUtils; + public final class ManufacturerDataIBeacon { -// 0 FF # Manufacturer specific data AD type -// 1 4C # Byte 1 of Company identifier code -// 2 00 # Byte 0 of Company identifier code (0x004C == Apple) -// 3 02 # Byte 0 of iBeacon advertisement indicator -// 4 15 # Byte 1 of iBeacon advertisement indicator -// 5 e2 # -// 6 c5 ## -// 7 6d ### -// 8 b5 #### -// 9 df ##### -// 10 fb ###### -// 11 48 ####### -// 12 d2 ######## iBeacon proximity uuid -// 13 b0 ######## -// 14 60 ####### -// 15 d0 ###### -// 16 f5 ##### -// 17 a7 #### -// 18 10 ### -// 19 96 ## -// 20 e0 # +// 0 4C # Byte 1 (LSB) of Company identifier code +// 1 00 # Byte 0 (MSB) of Company identifier code (0x004C == Apple) +// 2 02 # Byte 0 of iBeacon advertisement indicator +// 3 15 # Byte 1 of iBeacon advertisement indicator +// 4 e2 |\ +// 5 c5 |\\ +// 6 6d |#\\ +// 7 b5 |##\\ +// 8 df |###\\ +// 9 fb |####\\ +// 10 48 |#####\\ +// 11 d2 |#####|| iBeacon proximity UUID +// 12 b0 |#####|| +// 13 60 |#####// +// 14 d0 |####// +// 15 f5 |###// +// 16 a7 |##// +// 17 10 |#// +// 18 96 |// +// 19 e0 |/ +// 20 00 # major // 21 00 -// 22 00 # major +// 22 00# minor // 23 00 -// 24 00 # minor -// 25 c5 # The 2's complement of the calibrated Tx Power +// 24 c5 # The 2's complement of the calibrated Tx Power private final byte[] mData; - private final int mTxPower; + private final int mCalibratedTxPower; + private final int mCompanyIdentidier; + private final int mIBeaconAdvertisment; + private final int mMajor; + private final int mMinor; + private final long mUUID; + + public ManufacturerDataIBeacon(BluetoothLeDevice device){ + this(device.getAdRecordStore().getRecord(AdRecord.TYPE_MANUFACTURER_SPECIFIC_DATA).getData()); + } public ManufacturerDataIBeacon(byte[] data){ mData = data; - mTxPower = 0; + mCompanyIdentidier = convert2ByteArrayToInt(Arrays.copyOfRange(mData, 0, 2)); + mIBeaconAdvertisment = convert2ByteArrayToInt(Arrays.copyOfRange(mData, 2, 2)); + mUUID = ByteUtils.getLongFromByteArray(Arrays.copyOfRange(mData, 4, 16)); + mMajor = convert2ByteArrayToInt(Arrays.copyOfRange(mData, 20, 2)); + mMinor = convert2ByteArrayToInt(Arrays.copyOfRange(mData, 22, 2)); + mCalibratedTxPower = ByteUtils.getIntFromByte(data[24]); } + private static int convert2ByteArrayToInt(byte[] array){ + final byte[] result = new byte[4]; + result[2] = array[0]; + result[3] = array[1]; + return ByteUtils.getIntFromByteArray(result); + } + + public int getCalibratedTxPower(){ + return mCalibratedTxPower; + } + + public int getCompanyIdentifier(){ + return mCompanyIdentidier; + } + + public int getIBeaconAdvertisement(){ + return mIBeaconAdvertisment; + } + + public int getMajor(){ + return mMajor; + } + + public int getMinor(){ + return mMinor; + } + + public long getUUID(){ + return mUUID; + } // Code taken from: http://stackoverflow.com/questions/20416218/understanding-ibeacon-distancing protected static double calculateAccuracy(int txPower, double rssi) { @@ -48,7 +95,7 @@ public final class ManufacturerDataIBeacon { return Math.pow(ratio,10); } else { - double accuracy = (0.89976)*Math.pow(ratio,7.7095) + 0.111; + final double accuracy = (0.89976)*Math.pow(ratio,7.7095) + 0.111; return accuracy; } } diff --git a/src/uk/co/alt236/btlescan/util/AdRecordUtils.java b/src/uk/co/alt236/btlescan/util/AdRecordUtils.java index b3e085d..95c219c 100644 --- a/src/uk/co/alt236/btlescan/util/AdRecordUtils.java +++ b/src/uk/co/alt236/btlescan/util/AdRecordUtils.java @@ -51,7 +51,7 @@ public class AdRecordUtils { //Done once we run out of records if (length == 0) break; - final int type = ByteUtils.convertByteToInt(scanRecord[index]); + final int type = ByteUtils.getIntFromByte(scanRecord[index]); //Done if our record isn't a valid type if (type == 0) break; @@ -77,7 +77,7 @@ public class AdRecordUtils { //Done once we run out of records if (length == 0) break; - final int type = ByteUtils.convertByteToInt(scanRecord[index]); + final int type = ByteUtils.getIntFromByte(scanRecord[index]); //Done if our record isn't a valid type if (type == 0) break; @@ -102,7 +102,7 @@ public class AdRecordUtils { //Done once we run out of records if (length == 0) break; - final int type = ByteUtils.convertByteToInt(scanRecord[index]); + final int type = ByteUtils.getIntFromByte(scanRecord[index]); //Done if our record isn't a valid type if (type == 0) break; diff --git a/src/uk/co/alt236/btlescan/util/ByteUtils.java b/src/uk/co/alt236/btlescan/util/ByteUtils.java index a2ff05a..1982572 100644 --- a/src/uk/co/alt236/btlescan/util/ByteUtils.java +++ b/src/uk/co/alt236/btlescan/util/ByteUtils.java @@ -1,8 +1,18 @@ package uk.co.alt236.btlescan.util; -public class ByteUtils { - static final String HEXES = "0123456789ABCDEF"; +import java.nio.ByteBuffer; +public class ByteUtils { + private static final String HEXES = "0123456789ABCDEF"; + + /** + * Gets a pretty representation of a Byte Array as a HEX String. + * + * Sample output: [01, 30, FF, AA] + * + * @param array the array + * @return the string + */ public static String byteArrayToHexString(final byte[] array){ final StringBuffer sb = new StringBuffer(); boolean firstEntry = true; @@ -21,8 +31,35 @@ public class ByteUtils { return sb.toString(); } - - public static int convertByteToInt(byte bite){ + /** + * Converts a byte to an int, preserving the sign. + * + * For example, FF will be converted to 255 and not -1. + * + * @param bite the bite + * @return the int from byte + */ + public static int getIntFromByte(final byte bite){ return Integer.valueOf(bite & 0xFF); } + + /** + * Converts a byte array to an integer; + * + * @param bytes the bytes + * @return the int from byte array + */ + public static int getIntFromByteArray(final byte[] bytes) { + return ByteBuffer.wrap(bytes).getInt(); + } + + /** + * Converts a byte array to a long; + * + * @param bytes the bytes + * @return the long from byte array + */ + public static long getLongFromByteArray(final byte[] bytes) { + return ByteBuffer.wrap(bytes).getLong(); + } }