diff --git a/res/drawable-xhdpi/ic_bluetooth.png b/res/drawable-xhdpi/ic_bluetooth.png new file mode 100644 index 0000000..2434af2 Binary files /dev/null and b/res/drawable-xhdpi/ic_bluetooth.png differ diff --git a/res/drawable-xhdpi/ic_bluetooth_ibeacon.png b/res/drawable-xhdpi/ic_bluetooth_ibeacon.png new file mode 100644 index 0000000..bfa0a71 Binary files /dev/null and b/res/drawable-xhdpi/ic_bluetooth_ibeacon.png differ diff --git a/res/layout/list_item_device.xml b/res/layout/list_item_device.xml index b7d0e2d..a2f4b70 100644 --- a/res/layout/list_item_device.xml +++ b/res/layout/list_item_device.xml @@ -17,52 +17,66 @@ + 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; + } +}