From 0400f11f594e733bd78b2730c494fd9599d4d31b Mon Sep 17 00:00:00 2001 From: Alexandros Schillings Date: Mon, 10 Mar 2014 19:34:53 +0000 Subject: [PATCH] We can now recognise iBeacons (need to check how correct this is) --- res/drawable-xhdpi/ic_bluetooth.png | Bin 0 -> 824 bytes res/drawable-xhdpi/ic_bluetooth_ibeacon.png | Bin 0 -> 3164 bytes res/layout/list_item_device.xml | 80 ++++++++++-------- .../btlescan/activities/DetailsActivity.java | 2 +- .../btlescan/activities/MainActivity.java | 5 -- .../adapters/LeDeviceListAdapter.java | 22 +++-- .../alt236/btlescan/containers/AdRecord.java | 16 ++-- .../btlescan/containers/AdRecordStore.java | 1 + .../containers/BluetoothLeDevice.java | 1 + .../containers/ManufacturerDataIBeacon.java | 28 ++++++ .../{containers => util}/AdRecordUtils.java | 3 +- .../btlescan/util/ManufacturerDataParser.java | 36 ++++++++ 12 files changed, 143 insertions(+), 51 deletions(-) create mode 100644 res/drawable-xhdpi/ic_bluetooth.png create mode 100644 res/drawable-xhdpi/ic_bluetooth_ibeacon.png create mode 100644 src/uk/co/alt236/btlescan/containers/ManufacturerDataIBeacon.java rename src/uk/co/alt236/btlescan/{containers => util}/AdRecordUtils.java (97%) create mode 100644 src/uk/co/alt236/btlescan/util/ManufacturerDataParser.java diff --git a/res/drawable-xhdpi/ic_bluetooth.png b/res/drawable-xhdpi/ic_bluetooth.png new file mode 100644 index 0000000000000000000000000000000000000000..2434af2c9904e3f74db994d415638d7bfe8c4807 GIT binary patch literal 824 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 + android:gravity="center_vertical" + android:orientation="horizontal" > - + android:paddingRight="5dp" + android:src="@drawable/ic_bluetooth" /> + android:orientation="vertical" > + android:textSize="24sp" /> - + android:orientation="horizontal" > - + - + - + + + + + + \ No newline at end of file diff --git a/src/uk/co/alt236/btlescan/activities/DetailsActivity.java b/src/uk/co/alt236/btlescan/activities/DetailsActivity.java index 8755539..3e5914a 100644 --- a/src/uk/co/alt236/btlescan/activities/DetailsActivity.java +++ b/src/uk/co/alt236/btlescan/activities/DetailsActivity.java @@ -4,8 +4,8 @@ import java.util.Collection; import uk.co.alt236.btlescan.R; import uk.co.alt236.btlescan.containers.AdRecord; -import uk.co.alt236.btlescan.containers.AdRecordUtils; import uk.co.alt236.btlescan.containers.BluetoothLeDevice; +import uk.co.alt236.btlescan.util.AdRecordUtils; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; diff --git a/src/uk/co/alt236/btlescan/activities/MainActivity.java b/src/uk/co/alt236/btlescan/activities/MainActivity.java index f59912e..406404d 100644 --- a/src/uk/co/alt236/btlescan/activities/MainActivity.java +++ b/src/uk/co/alt236/btlescan/activities/MainActivity.java @@ -31,11 +31,6 @@ public class MainActivity extends ListActivity { public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { final BluetoothLeDevice deviceLe = new BluetoothLeDevice(device, rssi, scanRecord); - // Log.d("TAG", "~ New BT Device: " + deviceLe); - // final Collection adRecords = deviceLe.getAdRecordStore().getRecordsAsCollection(); - // for(final AdRecord record : adRecords){ - // Log.d("TAG", "~ Has Record: " + record.getType() + ": '" + record.getHumanReadableType() +"', data: '"+ AdRecordUtils.getRecordDataAsString(record) + "'"); - // } runOnUiThread(new Runnable() { @Override diff --git a/src/uk/co/alt236/btlescan/adapters/LeDeviceListAdapter.java b/src/uk/co/alt236/btlescan/adapters/LeDeviceListAdapter.java index e05fe59..325195c 100644 --- a/src/uk/co/alt236/btlescan/adapters/LeDeviceListAdapter.java +++ b/src/uk/co/alt236/btlescan/adapters/LeDeviceListAdapter.java @@ -5,17 +5,19 @@ import java.util.List; import uk.co.alt236.btlescan.R; import uk.co.alt236.btlescan.containers.BluetoothLeDevice; +import uk.co.alt236.btlescan.util.ManufacturerDataParser; import android.app.Activity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; +import android.widget.ImageView; import android.widget.TextView; // Adapter for holding devices found through scanning. public class LeDeviceListAdapter extends BaseAdapter { private final List mLeDevices; private final LayoutInflater mInflator; - + public LeDeviceListAdapter(Activity activity) { super(); mLeDevices = new ArrayList(); @@ -64,6 +66,7 @@ import android.widget.TextView; viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address); viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name); viewHolder.deviceRssi = (TextView) view.findViewById(R.id.device_rssi); + viewHolder.deviceIcon = (ImageView) view.findViewById(R.id.device_icon); view.setTag(viewHolder); } else { viewHolder = (ViewHolder) view.getTag(); @@ -71,22 +74,31 @@ import android.widget.TextView; final BluetoothLeDevice device = mLeDevices.get(i); final String deviceName = device.getName(); - + if (deviceName != null && deviceName.length() > 0){ viewHolder.deviceName.setText(deviceName); } else{ viewHolder.deviceName.setText(R.string.unknown_device); } - + + final boolean isIBeacon = ManufacturerDataParser.isThisAnIBeacon(device); + + if (isIBeacon){ + viewHolder.deviceIcon.setImageResource(R.drawable.ic_bluetooth_ibeacon); + } else { + viewHolder.deviceIcon.setImageResource(R.drawable.ic_bluetooth); + } + viewHolder.deviceAddress.setText(device.getAddress()); viewHolder.deviceRssi.setText(String.valueOf(device.getRssi()) + "db"); return view; } - + static class ViewHolder { TextView deviceName; TextView deviceAddress; TextView deviceRssi; + ImageView deviceIcon; } - + } \ No newline at end of file diff --git a/src/uk/co/alt236/btlescan/containers/AdRecord.java b/src/uk/co/alt236/btlescan/containers/AdRecord.java index ca72dae..d81cfc1 100644 --- a/src/uk/co/alt236/btlescan/containers/AdRecord.java +++ b/src/uk/co/alt236/btlescan/containers/AdRecord.java @@ -32,6 +32,10 @@ public final class AdRecord implements Parcelable{ // c5 # The 2's complement of the calibrated Tx Power + private static final String PARCEL_RECORD_DATA = "record_data"; + private static final String PARCEL_RECORD_TYPE = "record_type"; + private static final String PARCEL_RECORD_LENGTH = "record_length"; + /** * General FLAGS * @@ -149,9 +153,9 @@ public final class AdRecord implements Parcelable{ public AdRecord(Parcel in) { final Bundle b = in.readBundle(getClass().getClassLoader()); - mLength = b.getInt("record_length"); - mType = b.getInt("record_type"); - mData = b.getByteArray("record_data"); + mLength = b.getInt(PARCEL_RECORD_LENGTH); + mType = b.getInt(PARCEL_RECORD_TYPE); + mData = b.getByteArray(PARCEL_RECORD_DATA); } @Override @@ -184,9 +188,9 @@ public final class AdRecord implements Parcelable{ public void writeToParcel(Parcel parcel, int arg1) { final Bundle b = new Bundle(getClass().getClassLoader()); - b.putInt("record_length", mLength); - b.putInt("record_type", mType); - b.putByteArray("record_data", mData); + b.putInt(PARCEL_RECORD_LENGTH, mLength); + b.putInt(PARCEL_RECORD_TYPE, mType); + b.putByteArray(PARCEL_RECORD_DATA, mData); parcel.writeBundle(b); } diff --git a/src/uk/co/alt236/btlescan/containers/AdRecordStore.java b/src/uk/co/alt236/btlescan/containers/AdRecordStore.java index bbabcbb..ed11af1 100644 --- a/src/uk/co/alt236/btlescan/containers/AdRecordStore.java +++ b/src/uk/co/alt236/btlescan/containers/AdRecordStore.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import uk.co.alt236.btlescan.util.AdRecordUtils; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; diff --git a/src/uk/co/alt236/btlescan/containers/BluetoothLeDevice.java b/src/uk/co/alt236/btlescan/containers/BluetoothLeDevice.java index 9cde283..591d58f 100644 --- a/src/uk/co/alt236/btlescan/containers/BluetoothLeDevice.java +++ b/src/uk/co/alt236/btlescan/containers/BluetoothLeDevice.java @@ -2,6 +2,7 @@ package uk.co.alt236.btlescan.containers; import java.util.Arrays; +import uk.co.alt236.btlescan.util.AdRecordUtils; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.os.Bundle; diff --git a/src/uk/co/alt236/btlescan/containers/ManufacturerDataIBeacon.java b/src/uk/co/alt236/btlescan/containers/ManufacturerDataIBeacon.java new file mode 100644 index 0000000..6d09f3c --- /dev/null +++ b/src/uk/co/alt236/btlescan/containers/ManufacturerDataIBeacon.java @@ -0,0 +1,28 @@ +package uk.co.alt236.btlescan.containers; + +public final class ManufacturerDataIBeacon { + private final byte[] mData; + private final int mTxPower; + + public ManufacturerDataIBeacon(byte[] data){ + mData = data; + mTxPower = 0; + } + + + // Code taken from: http://stackoverflow.com/questions/20416218/understanding-ibeacon-distancing + protected static double calculateAccuracy(int txPower, double rssi) { + if (rssi == 0) { + return -1.0; // if we cannot determine accuracy, return -1. + } + + double ratio = rssi*1.0/txPower; + if (ratio < 1.0) { + return Math.pow(ratio,10); + } + else { + double accuracy = (0.89976)*Math.pow(ratio,7.7095) + 0.111; + return accuracy; + } + } +} diff --git a/src/uk/co/alt236/btlescan/containers/AdRecordUtils.java b/src/uk/co/alt236/btlescan/util/AdRecordUtils.java similarity index 97% rename from src/uk/co/alt236/btlescan/containers/AdRecordUtils.java rename to src/uk/co/alt236/btlescan/util/AdRecordUtils.java index 40e507c..f7d6dbc 100644 --- a/src/uk/co/alt236/btlescan/containers/AdRecordUtils.java +++ b/src/uk/co/alt236/btlescan/util/AdRecordUtils.java @@ -1,4 +1,4 @@ -package uk.co.alt236.btlescan.containers; +package uk.co.alt236.btlescan.util; import java.util.ArrayList; import java.util.Arrays; @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import uk.co.alt236.btlescan.containers.AdRecord; import android.annotation.SuppressLint; import android.util.SparseArray; diff --git a/src/uk/co/alt236/btlescan/util/ManufacturerDataParser.java b/src/uk/co/alt236/btlescan/util/ManufacturerDataParser.java new file mode 100644 index 0000000..5bb5494 --- /dev/null +++ b/src/uk/co/alt236/btlescan/util/ManufacturerDataParser.java @@ -0,0 +1,36 @@ +package uk.co.alt236.btlescan.util; + +import uk.co.alt236.btlescan.containers.BluetoothLeDevice; + +public class ManufacturerDataParser { + private static final byte[] SCAN_RECORD_PREFIX_IBEACON_1 = new byte[]{0x02, 0x01, 0x1A, 0x1A, (byte) 0xFF, 0x4C, 0x00, 0x02, 0x15}; + private static final byte[] SCAN_RECORD_PREFIX_IBEACON_2 = new byte[]{0x02, 0x01, 0x06, 0x1A, (byte) 0xFF, 0x4C, 0x00, 0x02, 0x15}; + + public static boolean isThisAnIBeacon(BluetoothLeDevice device){ + return isThisAnIBeacon(device.getScanRecord()); + } + + public static boolean isThisAnIBeacon(byte[] scanRecord){ + if(doesArrayBeginWith(scanRecord, SCAN_RECORD_PREFIX_IBEACON_1)){ + return true; + } + + if(doesArrayBeginWith(scanRecord, SCAN_RECORD_PREFIX_IBEACON_2)){ + return true; + } + + return false; + } + + private static boolean doesArrayBeginWith(byte[] array, byte[] prefix){ + if(array.length < prefix.length){return false;} + + for(int i = 0; i < prefix.length; i++){ + if(array[i] != prefix[i]){ + return false; + } + } + + return true; + } +}