First commit
This commit is contained in:
@@ -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
@@ -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>
|
||||
@@ -0,0 +1,3 @@
|
||||
<factorypath>
|
||||
<factorypathentry kind="WKSPJAR" id="/Bluetooth LE Scanner/libs/butterknife-4.0.1.jar" enabled="true" runInBatchMode="false"/>
|
||||
</factorypath>
|
||||
@@ -0,0 +1,5 @@
|
||||
/bin
|
||||
/gen
|
||||
local.properties
|
||||
.idea/
|
||||
lint.xml
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
@@ -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 *;
|
||||
#}
|
||||
@@ -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 |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user