commit a738bcc38e6174cbed245a8471d77887f55fa7c0 Author: Alexandros Schillings Date: Sun Mar 9 23:00:00 2014 +0000 First commit diff --git a/.apt_generated/uk/co/alt236/btlescan/MainActivity$$ViewInjector.java b/.apt_generated/uk/co/alt236/btlescan/MainActivity$$ViewInjector.java new file mode 100644 index 0000000..5cd06b3 --- /dev/null +++ b/.apt_generated/uk/co/alt236/btlescan/MainActivity$$ViewInjector.java @@ -0,0 +1,26 @@ +// Generated code from Butter Knife. Do not modify! +package uk.co.alt236.btlescan; + +import android.view.View; +import butterknife.ButterKnife.Finder; + +public class MainActivity$$ViewInjector { + public static void inject(Finder finder, final uk.co.alt236.btlescan.MainActivity target, Object source) { + View view; + view = finder.findById(source, 2131230722); + if (view == null) { + throw new IllegalStateException("Required view with id '2131230722' for field 'mTvBluetoothStatus' was not found. If this view is optional add '@Optional' annotation."); + } + target.mTvBluetoothStatus = (android.widget.TextView) view; + view = finder.findById(source, 2131230721); + if (view == null) { + throw new IllegalStateException("Required view with id '2131230721' for field 'mTvBluetoothLeStatus' was not found. If this view is optional add '@Optional' annotation."); + } + target.mTvBluetoothLeStatus = (android.widget.TextView) view; + } + + public static void reset(uk.co.alt236.btlescan.MainActivity target) { + target.mTvBluetoothStatus = null; + target.mTvBluetoothLeStatus = null; + } +} diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..c54e8eb --- /dev/null +++ b/.classpath @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/.factorypath b/.factorypath new file mode 100644 index 0000000..946a26f --- /dev/null +++ b/.factorypath @@ -0,0 +1,3 @@ + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e38c1e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/bin +/gen +local.properties +.idea/ +lint.xml diff --git a/.project b/.project new file mode 100644 index 0000000..f0df143 --- /dev/null +++ b/.project @@ -0,0 +1,33 @@ + + + Bluetooth LE Scanner + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.jdt.apt.core.prefs b/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 0000000..7d52ece --- /dev/null +++ b/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=true +org.eclipse.jdt.apt.genSrcDir=.apt_generated +org.eclipse.jdt.apt.reconcileEnabled=true diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..f2b863f --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.processAnnotations=enabled +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..1a2e314 --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ic_launcher-web.png b/ic_launcher-web.png new file mode 100644 index 0000000..73c663b Binary files /dev/null and b/ic_launcher-web.png differ diff --git a/libs/android-support-v4.jar b/libs/android-support-v4.jar new file mode 100644 index 0000000..96644ed Binary files /dev/null and b/libs/android-support-v4.jar differ diff --git a/libs/butterknife-4.0.1.jar b/libs/butterknife-4.0.1.jar new file mode 100644 index 0000000..e048516 Binary files /dev/null and b/libs/butterknife-4.0.1.jar differ diff --git a/proguard-project.txt b/proguard-project.txt new file mode 100644 index 0000000..f2fe155 --- /dev/null +++ b/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/project.properties b/project.properties new file mode 100644 index 0000000..d5ad9f8 --- /dev/null +++ b/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=Google Inc.:Google APIs:19 diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..1563c4d Binary files /dev/null and b/res/drawable-hdpi/ic_launcher.png differ diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..de3905b Binary files /dev/null and b/res/drawable-mdpi/ic_launcher.png differ diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000..d737af2 Binary files /dev/null and b/res/drawable-xhdpi/ic_launcher.png differ diff --git a/res/drawable-xxhdpi/ic_launcher.png b/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..48cc2da Binary files /dev/null and b/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/res/layout/activity_main.xml b/res/layout/activity_main.xml new file mode 100644 index 0000000..898f037 --- /dev/null +++ b/res/layout/activity_main.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/menu/main.xml b/res/menu/main.xml new file mode 100644 index 0000000..c002028 --- /dev/null +++ b/res/menu/main.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml new file mode 100644 index 0000000..44f01db --- /dev/null +++ b/res/values-sw600dp/dimens.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/res/values-sw720dp-land/dimens.xml b/res/values-sw720dp-land/dimens.xml new file mode 100644 index 0000000..61e3fa8 --- /dev/null +++ b/res/values-sw720dp-land/dimens.xml @@ -0,0 +1,9 @@ + + + + 128dp + + diff --git a/res/values-v11/styles.xml b/res/values-v11/styles.xml new file mode 100644 index 0000000..3c02242 --- /dev/null +++ b/res/values-v11/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/res/values-v14/styles.xml b/res/values-v14/styles.xml new file mode 100644 index 0000000..a91fd03 --- /dev/null +++ b/res/values-v14/styles.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml new file mode 100644 index 0000000..55c1e59 --- /dev/null +++ b/res/values/dimens.xml @@ -0,0 +1,7 @@ + + + + 16dp + 16dp + + diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 0000000..e96ba21 --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,13 @@ + + + + Bluetooth LE Scanner + Settings + Hello world! + Off + On + Not supported + Supported + No data + + \ No newline at end of file diff --git a/res/values/styles.xml b/res/values/styles.xml new file mode 100644 index 0000000..6ce89c7 --- /dev/null +++ b/res/values/styles.xml @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/src/uk/co/alt236/btlescan/MainActivity.java b/src/uk/co/alt236/btlescan/MainActivity.java new file mode 100644 index 0000000..7aea23c --- /dev/null +++ b/src/uk/co/alt236/btlescan/MainActivity.java @@ -0,0 +1,85 @@ +package uk.co.alt236.btlescan; + +import uk.co.alt236.btlescan.containers.BluetoothLeDevice; +import uk.co.alt236.btlescan.util.BluetoothLeScanner; +import uk.co.alt236.btlescan.util.BluetoothUtils; +import android.app.ListActivity; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.widget.TextView; +import butterknife.ButterKnife; +import butterknife.InjectView; + +public class MainActivity extends ListActivity { + @InjectView(R.id.tvBluetoothLe) TextView mTvBluetoothLeStatus; + @InjectView(R.id.tvBluetoothStatus) TextView mTvBluetoothStatus; + + private static final long SCAN_PERIOD = 10000; + private BluetoothUtils mBluetoothUtils; + private BluetoothLeScanner mScanner; + //private LeDeviceListAdapter mLeDeviceListAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + ButterKnife.inject(this); + mBluetoothUtils = new BluetoothUtils(this); + mScanner = new BluetoothLeScanner(mLeScanCallback, mBluetoothUtils); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + + + + + private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { + @Override + public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { + Log.d("TAG", "~ New BT Device: " + new BluetoothLeDevice(device, rssi, scanRecord)); + +// runOnUiThread(new Runnable() { +// @Override +// public void run() { +// mLeDeviceListAdapter.addDevice(device); +// mLeDeviceListAdapter.notifyDataSetChanged(); +// } +// }); + + } + }; + + @Override + public void onResume(){ + super.onResume(); + final boolean mIsBluetoothOn = mBluetoothUtils.isBluetoothOn(); + final boolean mIsBluetoothLePresent = mBluetoothUtils.isBluetoothLeSupported(); + + if(mIsBluetoothOn){ + mTvBluetoothStatus.setText(R.string.on); + } else { + mTvBluetoothStatus.setText(R.string.off); + } + + if(mIsBluetoothLePresent){ + mTvBluetoothLeStatus.setText(R.string.supported); + } else { + mTvBluetoothLeStatus.setText(R.string.not_supported); + } + + mBluetoothUtils.askUserToEnableBluetoothIfNeeded(); + if(mIsBluetoothOn && mIsBluetoothLePresent){ + mScanner.scanLeDevice(true); + } + } + +} diff --git a/src/uk/co/alt236/btlescan/containers/AdRecord.java b/src/uk/co/alt236/btlescan/containers/AdRecord.java new file mode 100644 index 0000000..eaee886 --- /dev/null +++ b/src/uk/co/alt236/btlescan/containers/AdRecord.java @@ -0,0 +1,181 @@ +package uk.co.alt236.btlescan.containers; +import java.util.Arrays; + +/** + * Created by Dave Smith + * Double Encore, Inc. + * AdRecord + */ +public class AdRecord { + /** + * General FLAGS + * + * Description: Flags + * + * Information: + * Bit 0: LE Limited Discoverable Mode + * Bit 1: LE General Discoverable Mode + * Bit 2: BR/EDR Not Supported (i.e. bit 37 of LMP Extended Feature bits Page 0) + * Bit 3: Simultaneous LE and BR/EDR to Same Device Capable (Controller) (i.e. bit 49 of LMP Extended Feature bits Page 0) + * Bit 4: Simultaneous LE and BR/EDR to Same Device Capable (Host) (i.e. bit 66 of LMP Extended Feature bits Page 1) + * Bits 5-7 Reserved + */ + public static final int TYPE_FLAGS = 0x01; + + // SERVICE + public static final int TYPE_UUID16_INC = 0x02; + public static final int TYPE_UUID16 = 0x03; + public static final int TYPE_UUID32_INC = 0x04; + public static final int TYPE_UUID32 = 0x05; + public static final int TYPE_UUID128_INC = 0x06; + public static final int TYPE_UUID128 = 0x07; + + // Local name + public static final int TYPE_LOCAL_NAME_SHORT = 0x08; + public static final int TYPE_LOCAL_NAME_COMPLETE = 0x09; + + // TX Power Level + public static final int TYPE_TX_POWER_LEVEL = 0x0A; + + // SIMPLE PAIRING OPTIONAL OOB TAGS + public static final int TYPE_DEVICE_CLASS = 0x0D; + public static final int TYPE_SIMPLE_PAIRING_HASH_C = 0x0E; + public static final int TYPE_SIMPLE_PAIRING_RANDOMIZER_R = 0x0F; + + // SECURITY MANAGER TK VALUE + public static final int TYPE_TK_VALUE = 0x10; + + + /* 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 + * Bit 2: Simultaneous LE and BR/EDR to Same Device Capable (Host) (i.e. bit 66 of LMP Extended Feature bits Page 1) + * Bit 3: Address type (0 = Public Address, 1 = Random Address) + * Bits 4-7 Reserved + */ + public static final int TYPE_SECURITY_MANAGER_OOB_FLAGS = 0x11; + + + /* SLAVE CONNECTION INTERVAL RANGE + * + * Description: Slave Connection Interval Range + * + * 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 + * Value of 0xFFFF indicates no specific minimum. + * Values outside the range are reserved. (excluding 0xFFFF) + * + * The second 2 octets defines the maximum value for the connection interval in the following manner: + * connInterval max = Conn_Interval_Max * 1.25 ms + * Conn_Interval_Max range: 0x0006 to 0x0C80 + * Conn_Interval_Max shall be equal to or greater + * than the Conn_Interval_Min. + * Value of 0xFFFF indicates no specific maximum. + * Values outside the range are reserved (excluding 0xFFFF) + */ + public static final int TYPE_CONNECTION_INTERVAL_RANGE = 0x12; + + // SERVICE SOLICITATION + public static final int TYPE_SERVICE_UUIDS_LIST_16BIT = 0x14; + 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 + */ + public static final int TYPE_SERVICE_DATA = 0x16; + + + /* MANUFACTURER SPECIFIC DATA + * + * Description: Manufacturer Specific Data (2 or more octets) + * Information: The first 2 octets contain the Company Identifier Code followed by additional manufacturer specific data + */ + public static final int TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF; + + /* Model Object Definition */ + private final int mLength; + private final int mType; + private final byte[] mData; + + public AdRecord(int length, int type, byte[] data) { + mLength = length; + mType = type; + mData = data; + } + + public byte[] getData(){ + return mData; + } + + public String getHumanReadableType(){ + return getHumanReadableAdType(mType); + } + + 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() + "]"; + } + + private static String getHumanReadableAdType(int type){ + switch(type){ + case TYPE_CONNECTION_INTERVAL_RANGE: + return "Slave Connection Interval Range"; + case TYPE_DEVICE_CLASS: + return "Class of device"; + case TYPE_FLAGS: + return "Flags"; + case TYPE_MANUFACTURER_SPECIFIC_DATA: + return "Manufacturer Specific Data"; + case TYPE_LOCAL_NAME_COMPLETE: + return "Name"; + case TYPE_LOCAL_NAME_SHORT: + return "Name (Short)"; + case TYPE_SECURITY_MANAGER_OOB_FLAGS: + return "Security Manager OOB Flags"; + case TYPE_SERVICE_UUIDS_LIST_128BIT: + return "Service UUIDs (128bit)"; + case TYPE_SERVICE_UUIDS_LIST_16BIT: + return "Service UUIDs (16bit)"; + case TYPE_SERVICE_DATA: + return "Service Data"; + case TYPE_SIMPLE_PAIRING_HASH_C: + return "Simple Pairing Hash C"; + case TYPE_SIMPLE_PAIRING_RANDOMIZER_R: + return "Simple Pairing Randomizer R"; + case TYPE_TK_VALUE: + return "TK Value"; + case TYPE_TX_POWER_LEVEL: + return "Transmission Power Level"; + case TYPE_UUID128: + return "Complete list of 128-bit UUIDs available"; + case TYPE_UUID128_INC: + return "More 128-bit UUIDs available"; + case TYPE_UUID16: + return "Complete list of 16-bit UUIDs available"; + case TYPE_UUID16_INC: + return "More 16-bit UUIDs available"; + case TYPE_UUID32: + return "Complete list of 32-bit UUIDs available"; + case TYPE_UUID32_INC: + return "More 32-bit UUIDs available"; + default: + return "Unknown AdRecord Structure: " + type; + } + } +} \ No newline at end of file diff --git a/src/uk/co/alt236/btlescan/containers/AdRecordStore.java b/src/uk/co/alt236/btlescan/containers/AdRecordStore.java new file mode 100644 index 0000000..a1c6e89 --- /dev/null +++ b/src/uk/co/alt236/btlescan/containers/AdRecordStore.java @@ -0,0 +1,53 @@ +package uk.co.alt236.btlescan.containers; + +import java.util.Map; + +public class AdRecordStore { + private final Map mAdRecords; + private final int mServiceDataUUId; + private final String mLocalNameComplete; + private final String mLocalNameShort; + + public AdRecordStore(Map 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)); + + } + + 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 boolean isRecordPresent(int record){ + return mAdRecords.containsKey(record); + } + + @Override + public String toString() { + return "AdRecordStore [mServiceDataUUId=" + mServiceDataUUId + ", mLocalNameComplete=" + mLocalNameComplete + ", mLocalNameShort=" + mLocalNameShort + "]"; + } +} diff --git a/src/uk/co/alt236/btlescan/containers/AdRecordUtils.java b/src/uk/co/alt236/btlescan/containers/AdRecordUtils.java new file mode 100644 index 0000000..432bfd1 --- /dev/null +++ b/src/uk/co/alt236/btlescan/containers/AdRecordUtils.java @@ -0,0 +1,91 @@ +package uk.co.alt236.btlescan.containers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import android.annotation.SuppressLint; + +public class AdRecordUtils { + /* Helper functions to parse out common data payloads from an AD structure */ + + public static String getRecordDataAsString(AdRecord nameRecord) { + if(nameRecord == null){return new String();} + return new String(nameRecord.getData()); + } + + public static byte[] getServiceData(AdRecord serviceData) { + if (serviceData == null) {return null;} + if (serviceData.getType() != AdRecord.TYPE_SERVICE_DATA) return null; + + final byte[] raw = serviceData.getData(); + //Chop out the uuid + return Arrays.copyOfRange(raw, 2, raw.length); + } + + public static int getServiceDataUuid(AdRecord serviceData) { + if (serviceData == null) {return -1;} + if (serviceData.getType() != AdRecord.TYPE_SERVICE_DATA) return -1; + + final byte[] raw = serviceData.getData(); + //Find UUID data in byte array + int uuid = (raw[1] & 0xFF) << 8; + uuid += (raw[0] & 0xFF); + + return uuid; + } + + /* + * Read out all the AD structures from the raw scan record + */ + public static List parseScanRecordAsList(byte[] scanRecord) { + final List records = new ArrayList(); + + 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.add(new AdRecord(length, type, data)); + + //Advance + index += length; + } + + return records; + } + + @SuppressLint("UseSparseArrays") + public static Map parseScanRecordAsMap(byte[] scanRecord) { + final Map records = new HashMap(); + + 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 new file mode 100644 index 0000000..bf82a57 --- /dev/null +++ b/src/uk/co/alt236/btlescan/containers/BluetoothLeDevice.java @@ -0,0 +1,169 @@ +package uk.co.alt236.btlescan.containers; + +import java.util.Arrays; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; + +public class BluetoothLeDevice { + private final BluetoothDevice mDevice; + private final int mRssi; + private final byte[] mScanRecord; + private final AdRecordStore mRecordStore; + + public BluetoothLeDevice(BluetoothDevice device, int rssi, byte[] scanRecord){ + mDevice = device; + mRssi = rssi; + mScanRecord = scanRecord; + mRecordStore = new AdRecordStore(AdRecordUtils.parseScanRecordAsMap(scanRecord)); + } + + public AdRecordStore getAdRecordStore(){ + return mRecordStore; + } + + public String getBluetoothDeviceBondState(){ + return resolveBondingState(mDevice.getBondState()); + } + + public String getBluetoothDeviceClassName(){ + return resolveBluetoothClass(mDevice.getBluetoothClass().getDeviceClass()); + } + + public BluetoothDevice getDevice() { + return mDevice; + } + + public int getRssi() { + return mRssi; + } + + public byte[] getScanRecord() { + return mScanRecord; + } + + @Override + public String toString() { + return "BluetoothLeDevice [mDevice=" + mDevice + ", mRssi=" + mRssi + ", mScanRecord=" + Arrays.toString(mScanRecord) + ", mRecordStore=" + mRecordStore + ", getBluetoothDeviceBondState()=" + getBluetoothDeviceBondState() + ", getBluetoothDeviceClassName()=" + getBluetoothDeviceClassName() + "]"; + } + + 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: + return "A/V, Car Audio"; + case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: + return "A/V, Handsfree"; + case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES: + return "A/V, Headphones"; + case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO: + return "A/V, HiFi Audio"; + case BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER: + return "A/V, Loudspeaker"; + case BluetoothClass.Device.AUDIO_VIDEO_MICROPHONE: + return "A/V, Microphone"; + case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO: + return "A/V, Portable Audio"; + case BluetoothClass.Device.AUDIO_VIDEO_SET_TOP_BOX: + return "A/V, Set Top Box"; + case BluetoothClass.Device.AUDIO_VIDEO_UNCATEGORIZED: + return "A/V, Uncategorized"; + case BluetoothClass.Device.AUDIO_VIDEO_VCR: + return "A/V, VCR"; + case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_CAMERA: + return "A/V, Video Camera"; + 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"; + case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_GAMING_TOY: + return "A/V, Video Gaming Toy"; + case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_MONITOR: + return "A/V, Video Monitor"; + case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: + return "A/V, Video Wearable Headset"; + case BluetoothClass.Device.COMPUTER_DESKTOP: + return "Computer, Desktop"; + case BluetoothClass.Device.COMPUTER_HANDHELD_PC_PDA: + return "Computer, Handheld PC/PDA"; + case BluetoothClass.Device.COMPUTER_LAPTOP: + return "Computer, Laptop"; + case BluetoothClass.Device.COMPUTER_PALM_SIZE_PC_PDA: + return "Computer, Palm Size PC/PDA"; + case BluetoothClass.Device.COMPUTER_SERVER: + return "Computer, Server"; + case BluetoothClass.Device.COMPUTER_UNCATEGORIZED: + return "Computer, Uncategorized"; + case BluetoothClass.Device.COMPUTER_WEARABLE: + return "Computer, Wearable"; + 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 : + return "Health, Pulse Oximeter"; + case BluetoothClass.Device.HEALTH_PULSE_RATE : + return "Health, Pulse Rate"; + case BluetoothClass.Device.HEALTH_THERMOMETER : + return "Health, Thermometer"; + case BluetoothClass.Device.HEALTH_UNCATEGORIZED : + return "Health, Uncategorized"; + case BluetoothClass.Device.HEALTH_WEIGHING: + return "Health, Weighting"; + case BluetoothClass.Device.PHONE_CELLULAR: + return "Phone, Cellular"; + case BluetoothClass.Device.PHONE_CORDLESS: + return "Phone, Cordless"; + case BluetoothClass.Device.PHONE_ISDN: + return "Phone, ISDN"; + case BluetoothClass.Device.PHONE_MODEM_OR_GATEWAY: + return "Phone, Modem or Gateway"; + case BluetoothClass.Device.PHONE_SMART: + return "Phone, Smart"; + case BluetoothClass.Device.PHONE_UNCATEGORIZED: + return "Phone, Uncategorized"; + case BluetoothClass.Device.TOY_CONTROLLER: + return "Toy, Controller"; + case BluetoothClass.Device.TOY_DOLL_ACTION_FIGURE: + return "Toy, Doll/Action Figure"; + case BluetoothClass.Device.TOY_GAME: + return "Toy, Game"; + case BluetoothClass.Device.TOY_ROBOT: + return "Toy, Robot"; + case BluetoothClass.Device.TOY_UNCATEGORIZED: + return "Toy, Uncategorized"; + case BluetoothClass.Device.TOY_VEHICLE: + return "Toy, Vehicle"; + case BluetoothClass.Device.WEARABLE_GLASSES: + return "Wearable, Glasses"; + case BluetoothClass.Device.WEARABLE_HELMET: + return "Wearable, Helmet"; + case BluetoothClass.Device.WEARABLE_JACKET: + return "Wearable, Jacket"; + case BluetoothClass.Device.WEARABLE_PAGER: + return "Wearable, Pager"; + case BluetoothClass.Device.WEARABLE_UNCATEGORIZED: + return "Wearable, Uncategorized"; + case BluetoothClass.Device.WEARABLE_WRIST_WATCH: + return "Wearable, Wrist Watch"; + default: + return "Unknown, Unknown (class=" + btClass +")"; + } + } + + private static String resolveBondingState(int bondState){ + switch (bondState){ + case BluetoothDevice.BOND_BONDED: + return "Paired"; + case BluetoothDevice.BOND_BONDING: + return "Pairing"; + case BluetoothDevice.BOND_NONE: + return "Unbonded"; + default: + return "Unknown"; + } + } +} diff --git a/src/uk/co/alt236/btlescan/util/BluetoothLeScanner.java b/src/uk/co/alt236/btlescan/util/BluetoothLeScanner.java new file mode 100644 index 0000000..c52156c --- /dev/null +++ b/src/uk/co/alt236/btlescan/util/BluetoothLeScanner.java @@ -0,0 +1,44 @@ +package uk.co.alt236.btlescan.util; + +import android.bluetooth.BluetoothAdapter; +import android.os.Handler; +import android.util.Log; + +public class BluetoothLeScanner { + private static final long SCAN_PERIOD = 10000; + + private final Handler mHandler; + private final BluetoothAdapter.LeScanCallback mLeScanCallback; + private final BluetoothUtils mBluetoothUtils; + private boolean mScanning; + + public BluetoothLeScanner(BluetoothAdapter.LeScanCallback leScanCallback, BluetoothUtils bluetoothUtils){ + mHandler = new Handler(); + mLeScanCallback = leScanCallback; + mBluetoothUtils = bluetoothUtils; + } + + + public void scanLeDevice(final boolean enable) { + if (enable) { + if(mScanning){return;} + Log.d("TAG", "~ Starting Scan"); + // Stops scanning after a pre-defined scan period. + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + Log.d("TAG", "~ Stopping Scan (timeout)"); + mScanning = false; + mBluetoothUtils.getBluetoothAdapter().stopLeScan(mLeScanCallback); + } + }, SCAN_PERIOD); + + mScanning = true; + mBluetoothUtils.getBluetoothAdapter().startLeScan(mLeScanCallback); + } else { + Log.d("TAG", "~ Stopping Scan"); + mScanning = false; + mBluetoothUtils.getBluetoothAdapter().stopLeScan(mLeScanCallback); + } + } +} diff --git a/src/uk/co/alt236/btlescan/util/BluetoothUtils.java b/src/uk/co/alt236/btlescan/util/BluetoothUtils.java new file mode 100644 index 0000000..3bf7dd6 --- /dev/null +++ b/src/uk/co/alt236/btlescan/util/BluetoothUtils.java @@ -0,0 +1,45 @@ +package uk.co.alt236.btlescan.util; + +import android.app.Activity; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; + +public final class BluetoothUtils { + private final Activity mActivity; + private final BluetoothAdapter mBluetoothAdapter; + private final BluetoothManager mBluetoothManager; + + public final static int REQUEST_ENABLE_BT = 2001; + + public BluetoothUtils(Activity activity){ + mActivity = activity; + mBluetoothManager = (BluetoothManager) mActivity.getSystemService(Context.BLUETOOTH_SERVICE); + mBluetoothAdapter = mBluetoothManager.getAdapter(); + } + + public void askUserToEnableBluetoothIfNeeded(){ + if (isBluetoothLeSupported() && (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled())) { + final Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); + mActivity.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); + } + } + + public BluetoothAdapter getBluetoothAdapter(){ + return mBluetoothAdapter; + } + + public boolean isBluetoothLeSupported(){ + return mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE); + } + + public boolean isBluetoothOn(){ + if (mBluetoothAdapter == null) { + return false; + } else { + return mBluetoothAdapter.isEnabled(); + } + } +}