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