First commit

This commit is contained in:
Alexandros Schillings
2014-03-09 23:00:00 +00:00
commit a738bcc38e
33 changed files with 992 additions and 0 deletions
@@ -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;
}
}
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry kind="src" path=".apt_generated">
<attributes>
<attribute name="optional" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
+3
View File
@@ -0,0 +1,3 @@
<factorypath>
<factorypathentry kind="WKSPJAR" id="/Bluetooth LE Scanner/libs/butterknife-4.0.1.jar" enabled="true" runInBatchMode="false"/>
</factorypath>
+5
View File
@@ -0,0 +1,5 @@
/bin
/gen
local.properties
.idea/
lint.xml
+33
View File
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>Bluetooth LE Scanner</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
+4
View File
@@ -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
+5
View File
@@ -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
+34
View File
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="uk.co.alt236.btlescan"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="18"
android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="false" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="uk.co.alt236.btlescan.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.
Binary file not shown.
+20
View File
@@ -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 *;
#}
+14
View File
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

+77
View File
@@ -0,0 +1,77 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<GridLayout
android:id="@+id/gridLayout1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:columnCount="2" >
<TextView
android:layout_column="0"
android:layout_gravity="center_horizontal"
android:layout_row="0"
android:text="Bluetooth LE:"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/tvBluetoothLe"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="1"
android:layout_gravity="left"
android:layout_marginLeft="28dp"
android:layout_row="0"
android:text="@string/not_supported"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="0"
android:layout_gravity="left"
android:layout_row="1"
android:text="Bluetooth:"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/tvBluetoothStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="1"
android:layout_gravity="right|bottom"
android:layout_row="1"
android:text="@string/off"
android:textAppearance="?android:attr/textAppearanceMedium" />
</GridLayout>
<View
android:id="@+id/upperSepparator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@id/gridLayout1"
android:background="@android:color/holo_blue_dark" />
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/upperSepparator" />
<TextView
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/upperSepparator"
android:text="@string/no_data" />
</RelativeLayout>
+9
View File
@@ -0,0 +1,9 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:showAsAction="never"
android:title="@string/action_settings"/>
</menu>
+8
View File
@@ -0,0 +1,8 @@
<resources>
<!--
Customize dimensions originally defined in res/values/dimens.xml (such as
screen margins) for sw600dp devices (e.g. 7" tablets) here.
-->
</resources>
+9
View File
@@ -0,0 +1,9 @@
<resources>
<!--
Customize dimensions originally defined in res/values/dimens.xml (such as
screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here.
-->
<dimen name="activity_horizontal_margin">128dp</dimen>
</resources>
+11
View File
@@ -0,0 +1,11 @@
<resources>
<!--
Base application theme for API 11+. This theme completely replaces
AppBaseTheme from res/values/styles.xml on API 11+ devices.
-->
<style name="AppBaseTheme" parent="android:Theme.Holo.Light">
<!-- API 11 theme customizations can go here. -->
</style>
</resources>
+12
View File
@@ -0,0 +1,12 @@
<resources>
<!--
Base application theme for API 14+. This theme completely replaces
AppBaseTheme from BOTH res/values/styles.xml and
res/values-v11/styles.xml on API 14+ devices.
-->
<style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
<!-- API 14 theme customizations can go here. -->
</style>
</resources>
+7
View File
@@ -0,0 +1,7 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Bluetooth LE Scanner</string>
<string name="action_settings">Settings</string>
<string name="hello_world">Hello world!</string>
<string name="off">Off</string>
<string name="on">On</string>
<string name="not_supported">Not supported</string>
<string name="supported">Supported</string>
<string name="no_data">No data</string>
</resources>
+20
View File
@@ -0,0 +1,20 @@
<resources>
<!--
Base application theme, dependent on API level. This theme is replaced
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
-->
<style name="AppBaseTheme" parent="android:Theme.Light">
<!--
Theme customizations available in newer API levels can go in
res/values-vXX/styles.xml, while customizations related to
backward-compatibility can go here.
-->
</style>
<!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme">
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
</style>
</resources>
@@ -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);
}
}
}
@@ -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;
}
}
}
@@ -0,0 +1,53 @@
package uk.co.alt236.btlescan.containers;
import java.util.Map;
public class AdRecordStore {
private final Map<Integer, AdRecord> mAdRecords;
private final int mServiceDataUUId;
private final String mLocalNameComplete;
private final String mLocalNameShort;
public AdRecordStore(Map<Integer, AdRecord> 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 + "]";
}
}
@@ -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<AdRecord> parseScanRecordAsList(byte[] scanRecord) {
final List<AdRecord> records = new ArrayList<AdRecord>();
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<Integer, AdRecord> parseScanRecordAsMap(byte[] scanRecord) {
final Map<Integer, AdRecord> records = new HashMap<Integer, AdRecord>();
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;
}
}
@@ -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";
}
}
}
@@ -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);
}
}
}
@@ -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();
}
}
}