From 827e3ba19c0faecfe47f141283a75999d60e275d Mon Sep 17 00:00:00 2001 From: Alexandros Schillings Date: Mon, 10 Mar 2014 13:07:56 +0000 Subject: [PATCH] Made the containers parcelable --- .../alt236/btlescan/containers/AdRecord.java | 79 ++++++++++++----- .../btlescan/containers/AdRecordStore.java | 84 ++++++++++++++----- .../btlescan/containers/AdRecordUtils.java | 26 ++++++ .../containers/BluetoothLeDevice.java | 81 +++++++++++++----- 4 files changed, 206 insertions(+), 64 deletions(-) diff --git a/src/uk/co/alt236/btlescan/containers/AdRecord.java b/src/uk/co/alt236/btlescan/containers/AdRecord.java index 443052f..74089d8 100644 --- a/src/uk/co/alt236/btlescan/containers/AdRecord.java +++ b/src/uk/co/alt236/btlescan/containers/AdRecord.java @@ -1,16 +1,21 @@ package uk.co.alt236.btlescan.containers; import java.util.Arrays; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + /** * Created by Dave Smith * Double Encore, Inc. - * AdRecord + * + * Expanded by Alexandros Schillings */ -public final class AdRecord { - +public final class AdRecord implements Parcelable{ + // 02 # Number of bytes that follow in first AD structure // 01 # Flags AD type - // 1A # Flags value 0x1A = 000011010 + // 1A # Flags value 0x1A = 000011010 // bit 0 (OFF) LE Limited Discoverable Mode // bit 1 (ON) LE General Discoverable Mode // bit 2 (OFF) BR/EDR Not Supported @@ -22,16 +27,16 @@ public final class AdRecord { // 02 # Byte 0 of iBeacon advertisement indicator // 15 # Byte 1 of iBeacon advertisement indicator // e2 c5 6d b5 df fb 48 d2 b0 60 d0 f5 a7 10 96 e0 # iBeacon proximity uuid - // 00 00 # major - // 00 00 # minor + // 00 00 # major + // 00 00 # minor // c5 # The 2's complement of the calibrated Tx Power - - + + /** * General FLAGS - * + * * Description: Flags - * + * * Information: * Bit 0: LE Limited Discoverable Mode * Bit 1: LE General Discoverable Mode @@ -67,9 +72,9 @@ public final class AdRecord { /* SECURITY MANAGER OOB FLAGS - * + * * Description: Flag (1 octet) - * + * * Information: * Bit 0: OOB Flags Field: (0 = OOB data not present, 1 = OOB data present) * Bit 1: LE supported (Host) (i.e. bit 65 of LMP Extended Feature bits Page 1 @@ -81,10 +86,10 @@ public final class AdRecord { /* SLAVE CONNECTION INTERVAL RANGE - * + * * Description: Slave Connection Interval Range - * - * Information: + * + * Information: * The first 2 octets defines the minimum value for the connection interval in the following manner: * connInterval min = Conn_Interval_Min * 1.25 ms * Conn_Interval_Min range: 0x0006 to 0x0C80 @@ -106,7 +111,7 @@ public final class AdRecord { public static final int TYPE_SERVICE_UUIDS_LIST_128BIT = 0x15; /* SERVICE DATA - * + * * Description: Service Data (2 or more octets) * Information: The first 2 octets contain the 16 bit Service UUID followed by additional service data */ @@ -121,16 +126,39 @@ public final class AdRecord { public static final int TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF; /* Model Object Definition */ - private final int mLength; + private final int mLength; private final int mType; private final byte[] mData; - + + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public AdRecord createFromParcel(Parcel in) { + return new AdRecord(in); + } + + public AdRecord[] newArray(int size) { + return new AdRecord[size]; + } + }; + public AdRecord(int length, int type, byte[] data) { mLength = length; mType = type; mData = data; } + public AdRecord(Parcel in) { + final Bundle b = in.readBundle(); + mLength = b.getInt("record_length"); + mType = b.getInt("record_type"); + mData = b.getByteArray("record_data"); + } + + @Override + public int describeContents() { + return 0; + } + public byte[] getData(){ return mData; } @@ -142,16 +170,27 @@ public final class AdRecord { public int getLength() { return mLength; } - + public int getType() { return mType; } - + @Override public String toString() { return "AdRecord [mLength=" + mLength + ", mType=" + mType + ", mData=" + Arrays.toString(mData) + ", getHumanReadableType()=" + getHumanReadableType() + "]"; } + @Override + public void writeToParcel(Parcel parcel, int arg1) { + final Bundle b = new Bundle(); + + b.putInt("record_length", mLength); + b.putInt("record_type", mType); + b.putByteArray("record_data", mData); + + parcel.writeBundle(b); + } + private static String getHumanReadableAdType(int type){ switch(type){ case TYPE_CONNECTION_INTERVAL_RANGE: diff --git a/src/uk/co/alt236/btlescan/containers/AdRecordStore.java b/src/uk/co/alt236/btlescan/containers/AdRecordStore.java index 9102361..9143e36 100644 --- a/src/uk/co/alt236/btlescan/containers/AdRecordStore.java +++ b/src/uk/co/alt236/btlescan/containers/AdRecordStore.java @@ -1,59 +1,101 @@ package uk.co.alt236.btlescan.containers; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Map; -public class AdRecordStore { - private final Map mAdRecords; - private final int mServiceDataUUId; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.SparseArray; + +public class AdRecordStore implements Parcelable{ + + private final SparseArray mAdRecords; private final String mLocalNameComplete; private final String mLocalNameShort; - - public AdRecordStore(Map adRecords){ + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public AdRecordStore createFromParcel(Parcel in) { + return new AdRecordStore(in); + } + + public AdRecordStore[] newArray(int size) { + return new AdRecordStore[size]; + } + }; + + public AdRecordStore(Parcel in) { + final Bundle b = in.readBundle(); + mAdRecords = b.getSparseParcelableArray("records_array"); + mLocalNameComplete = b.getString("local_name_complete"); + mLocalNameShort = b.getString("local_name_short"); + } + + public AdRecordStore(final SparseArray adRecords){ mAdRecords = adRecords; - mServiceDataUUId = AdRecordUtils.getServiceDataUuid( - mAdRecords.get(AdRecord.TYPE_SERVICE_DATA)); - + mLocalNameComplete = AdRecordUtils.getRecordDataAsString( mAdRecords.get(AdRecord.TYPE_LOCAL_NAME_COMPLETE)); - + mLocalNameShort = AdRecordUtils.getRecordDataAsString( mAdRecords.get(AdRecord.TYPE_LOCAL_NAME_SHORT)); - + } - + + @Override + public int describeContents() { + return 0; + } + public String getLocalNameComplete() { return mLocalNameComplete; } - + public String getLocalNameShort() { return mLocalNameShort; } - + public AdRecord getRecord(int record){ return mAdRecords.get(record); } - + public String getRecordDataAsString(int record){ return AdRecordUtils.getRecordDataAsString( mAdRecords.get(record)); } - public int getServiceDataUUID(){ - return mServiceDataUUId; + public Collection getRecordsAsCollection() { + return Collections.unmodifiableCollection(asList(mAdRecords)); } public boolean isRecordPresent(int record){ - return mAdRecords.containsKey(record); + return mAdRecords.indexOfKey(record) >= 0; } @Override public String toString() { - return "AdRecordStore [mServiceDataUUId=" + mServiceDataUUId + ", mLocalNameComplete=" + mLocalNameComplete + ", mLocalNameShort=" + mLocalNameShort + "]"; + return "AdRecordStore [mLocalNameComplete=" + mLocalNameComplete + ", mLocalNameShort=" + mLocalNameShort + "]"; } - public Collection getRecordsAsCollection() { - return Collections.unmodifiableCollection(mAdRecords.values()); + @Override + public void writeToParcel(Parcel parcel, int arg1) { + final Bundle b = new Bundle(); + b.putString("local_name_complete", mLocalNameComplete); + b.putString("local_name_short", mLocalNameShort); + b.putSparseParcelableArray("records_array", mAdRecords); + + parcel.writeBundle(b); + } + + public static Collection asList(SparseArray sparseArray) { + if (sparseArray == null) return null; + + final Collection arrayList = new ArrayList(sparseArray.size()); + for (int i = 0; i < sparseArray.size(); i++){ + arrayList.add(sparseArray.valueAt(i)); + } + + return arrayList; } } diff --git a/src/uk/co/alt236/btlescan/containers/AdRecordUtils.java b/src/uk/co/alt236/btlescan/containers/AdRecordUtils.java index 8b57309..a6738c0 100644 --- a/src/uk/co/alt236/btlescan/containers/AdRecordUtils.java +++ b/src/uk/co/alt236/btlescan/containers/AdRecordUtils.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Map; import android.annotation.SuppressLint; +import android.util.SparseArray; public class AdRecordUtils { /* Helper functions to parse out common data payloads from an AD structure */ @@ -108,4 +109,29 @@ public class AdRecordUtils { return Collections.unmodifiableMap(records); } + + + public static SparseArray parseScanRecordAsSparseArray(byte[] scanRecord) { + final SparseArray records = new SparseArray(); + + int index = 0; + while (index < scanRecord.length) { + final int length = scanRecord[index++]; + //Done once we run out of records + if (length == 0) break; + + int type = scanRecord[index]; + //Done if our record isn't a valid type + if (type == 0) break; + + final byte[] data = Arrays.copyOfRange(scanRecord, index+1, index+length); + + records.put(type, new AdRecord(length, type, data)); + + //Advance + index += length; + } + + return records; + } } diff --git a/src/uk/co/alt236/btlescan/containers/BluetoothLeDevice.java b/src/uk/co/alt236/btlescan/containers/BluetoothLeDevice.java index 30803c7..fa30452 100644 --- a/src/uk/co/alt236/btlescan/containers/BluetoothLeDevice.java +++ b/src/uk/co/alt236/btlescan/containers/BluetoothLeDevice.java @@ -4,20 +4,46 @@ import java.util.Arrays; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; -public class BluetoothLeDevice { - private final BluetoothDevice mDevice; - private transient final int mRssi; - private final byte[] mScanRecord; +public class BluetoothLeDevice implements Parcelable{ private final AdRecordStore mRecordStore; - + private final BluetoothDevice mDevice; + private final byte[] mScanRecord; + private transient final int mRssi; + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public BluetoothLeDevice createFromParcel(Parcel in) { + return new BluetoothLeDevice(in); + } + + public BluetoothLeDevice[] newArray(int size) { + return new BluetoothLeDevice[size]; + } + }; + public BluetoothLeDevice(BluetoothDevice device, int rssi, byte[] scanRecord){ mDevice = device; mRssi = rssi; mScanRecord = scanRecord; - mRecordStore = new AdRecordStore(AdRecordUtils.parseScanRecordAsMap(scanRecord)); + mRecordStore = new AdRecordStore(AdRecordUtils.parseScanRecordAsSparseArray(scanRecord)); } - + + private BluetoothLeDevice(Parcel in) { + final Bundle b = in.readBundle(); + mDevice = b.getParcelable("bluetooth_device"); + mRecordStore = b.getParcelable("device_scanrecord_store"); + mRssi = b.getInt("device_rssi"); + mScanRecord = b.getByteArray("device_scanrecord"); + } + + @Override + public int describeContents() { + return 0; + } + @Override public boolean equals(Object obj) { if (this == obj) @@ -36,11 +62,11 @@ public class BluetoothLeDevice { return false; return true; } - + public String getAddress(){ return mDevice.getAddress(); } - + public AdRecordStore getAdRecordStore(){ return mRecordStore; } @@ -52,15 +78,15 @@ public class BluetoothLeDevice { public String getBluetoothDeviceClassName(){ return resolveBluetoothClass(mDevice.getBluetoothClass().getDeviceClass()); } - + public BluetoothDevice getDevice() { return mDevice; } - + public String getName(){ return mDevice.getName(); } - + public int getRssi() { return mRssi; } @@ -77,19 +103,28 @@ public class BluetoothLeDevice { result = prime * result + Arrays.hashCode(mScanRecord); return result; } - + @Override public String toString() { return "BluetoothLeDevice [mDevice=" + mDevice + ", mRssi=" + mRssi + ", mScanRecord=" + AdRecordUtils.byteArrayToHexString(mScanRecord) + ", mRecordStore=" + mRecordStore + ", getBluetoothDeviceBondState()=" + getBluetoothDeviceBondState() + ", getBluetoothDeviceClassName()=" + getBluetoothDeviceClassName() + "]"; } - - + + @Override + public void writeToParcel(Parcel parcel, int arg1) { + final Bundle b = new Bundle(); + b.putByteArray("device_scanrecord", mScanRecord); + b.putInt("device_rssi", mRssi); + b.putParcelable("bluetooth_device", mDevice); + b.putParcelable("device_scanrecord_store", mRecordStore); + + parcel.writeBundle(b); + } private static String resolveBluetoothClass(int btClass){ switch (btClass){ case BluetoothClass.Device.AUDIO_VIDEO_CAMCORDER: return "A/V, Camcorder"; - case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: + case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: return "A/V, Car Audio"; case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: return "A/V, Handsfree"; @@ -111,7 +146,7 @@ public class BluetoothLeDevice { return "A/V, VCR"; case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_CAMERA: return "A/V, Video Camera"; - case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_CONFERENCING: + case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_CONFERENCING: return "A/V, Video Conferencing"; case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER: return "A/V, Video Display and Loudspeaker"; @@ -135,17 +170,17 @@ public class BluetoothLeDevice { return "Computer, Uncategorized"; case BluetoothClass.Device.COMPUTER_WEARABLE: return "Computer, Wearable"; - case BluetoothClass.Device.HEALTH_BLOOD_PRESSURE: + case BluetoothClass.Device.HEALTH_BLOOD_PRESSURE: return "Health, Blood Pressure"; case BluetoothClass.Device.HEALTH_DATA_DISPLAY: return "Health, Data Display"; case BluetoothClass.Device.HEALTH_GLUCOSE: return "Health, Glucose"; - case BluetoothClass.Device.HEALTH_PULSE_OXIMETER : + case BluetoothClass.Device.HEALTH_PULSE_OXIMETER : return "Health, Pulse Oximeter"; case BluetoothClass.Device.HEALTH_PULSE_RATE : return "Health, Pulse Rate"; - case BluetoothClass.Device.HEALTH_THERMOMETER : + case BluetoothClass.Device.HEALTH_THERMOMETER : return "Health, Thermometer"; case BluetoothClass.Device.HEALTH_UNCATEGORIZED : return "Health, Uncategorized"; @@ -177,9 +212,9 @@ public class BluetoothLeDevice { return "Toy, Vehicle"; case BluetoothClass.Device.WEARABLE_GLASSES: return "Wearable, Glasses"; - case BluetoothClass.Device.WEARABLE_HELMET: + case BluetoothClass.Device.WEARABLE_HELMET: return "Wearable, Helmet"; - case BluetoothClass.Device.WEARABLE_JACKET: + case BluetoothClass.Device.WEARABLE_JACKET: return "Wearable, Jacket"; case BluetoothClass.Device.WEARABLE_PAGER: return "Wearable, Pager"; @@ -191,7 +226,7 @@ public class BluetoothLeDevice { return "Unknown, Unknown (class=" + btClass +")"; } } - + private static String resolveBondingState(int bondState){ switch (bondState){ case BluetoothDevice.BOND_BONDED: