This is mostly working now...

This commit is contained in:
Alexandros Schillings
2014-03-10 00:21:29 +00:00
parent ea0d9a6d72
commit 916dab0e7a
10 changed files with 379 additions and 64 deletions
@@ -0,0 +1,11 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="56dp"
android:layout_height="wrap_content"
android:minWidth="56dp" >
<ProgressBar
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center" />
</FrameLayout>
+68
View File
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/device_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="24sp" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="MAC:"
android:textSize="12sp" />
<TextView
android:id="@+id/device_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:typeface="monospace" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" - "
android:textSize="12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="RSSI:"
android:textSize="12sp" />
<TextView
android:id="@+id/device_rssi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
+30 -4
View File
@@ -1,9 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/action_settings"
android:id="@+id/menu_refresh"
android:checkable="false"
android:orderInCategory="1"
android:showAsAction="ifRoom"/>
<item
android:id="@+id/menu_scan"
android:orderInCategory="100"
android:showAsAction="never"
android:title="@string/action_settings"/>
android:showAsAction="ifRoom|withText"
android:title="@string/menu_scan"/>
<item
android:id="@+id/menu_stop"
android:orderInCategory="101"
android:showAsAction="ifRoom|withText"
android:title="@string/menu_stop"/>
</menu>
</menu>
+7
View File
@@ -9,5 +9,12 @@
<string name="not_supported">Not supported</string>
<string name="supported">Supported</string>
<string name="no_data">No data</string>
<string name="unknown_device">Unknown Device</string>
<!-- Menu items -->
<string name="menu_connect">Connect</string>
<string name="menu_disconnect">Disconnect</string>
<string name="menu_scan">Scan</string>
<string name="menu_stop">Stop</string>
</resources>
+72 -40
View File
@@ -2,6 +2,7 @@ package uk.co.alt236.btlescan;
import java.util.Collection;
import uk.co.alt236.btlescan.adapters.LeDeviceListAdapter;
import uk.co.alt236.btlescan.containers.AdRecord;
import uk.co.alt236.btlescan.containers.AdRecordUtils;
import uk.co.alt236.btlescan.containers.BluetoothLeDevice;
@@ -13,6 +14,7 @@ import android.bluetooth.BluetoothDevice;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import butterknife.ButterKnife;
import butterknife.InjectView;
@@ -20,12 +22,11 @@ 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;
private LeDeviceListAdapter mLeDeviceListAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -35,62 +36,93 @@ public class MainActivity extends ListActivity {
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;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
if (!mScanner.isScanning()) {
menu.findItem(R.id.menu_stop).setVisible(false);
menu.findItem(R.id.menu_scan).setVisible(true);
menu.findItem(R.id.menu_refresh).setActionView(null);
} else {
menu.findItem(R.id.menu_stop).setVisible(true);
menu.findItem(R.id.menu_scan).setVisible(false);
menu.findItem(R.id.menu_refresh).setActionView(R.layout.actionbar_progress_indeterminate);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_scan:
mLeDeviceListAdapter.clear();
mScanner.scanLeDevice(-1, true);
invalidateOptionsMenu();
break;
case R.id.menu_stop:
mScanner.scanLeDevice(-1, false);
invalidateOptionsMenu();
break;
}
return true;
}
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
final BluetoothLeDevice deviceLe = new BluetoothLeDevice(device, rssi, scanRecord);
Log.d("TAG", "~ New BT Device: " + deviceLe);
final Collection<AdRecord> adRecords = deviceLe.getAdRecordStore().getRecordsAsCollection();
for(final AdRecord record : adRecords){
Log.d("TAG", "~ Has Record: " + record.getType() + ": '" + record.getHumanReadableType() +"', data: '"+ AdRecordUtils.getRecordDataAsString(record) + "'");
}
// runOnUiThread(new Runnable() {
// @Override
// public void run() {
// mLeDeviceListAdapter.addDevice(device);
// mLeDeviceListAdapter.notifyDataSetChanged();
// }
// });
}
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
final BluetoothLeDevice deviceLe = new BluetoothLeDevice(device, rssi, scanRecord);
Log.d("TAG", "~ New BT Device: " + deviceLe);
final Collection<AdRecord> adRecords = deviceLe.getAdRecordStore().getRecordsAsCollection();
for(final AdRecord record : adRecords){
Log.d("TAG", "~ Has Record: " + record.getType() + ": '" + record.getHumanReadableType() +"', data: '"+ AdRecordUtils.getRecordDataAsString(record) + "'");
}
runOnUiThread(new Runnable() {
@Override
public void run() {
mLeDeviceListAdapter.addDevice(deviceLe);
mLeDeviceListAdapter.notifyDataSetChanged();
}
});
}
};
@Override
protected void onPause() {
super.onPause();
mScanner.scanLeDevice(-1, false);
mLeDeviceListAdapter.clear();
}
@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);
}
mLeDeviceListAdapter = new LeDeviceListAdapter(this);
setListAdapter(mLeDeviceListAdapter);
mBluetoothUtils.askUserToEnableBluetoothIfNeeded();
if(mIsBluetoothOn && mIsBluetoothLePresent){
mScanner.scanLeDevice(true);
mScanner.scanLeDevice(-1, true);
invalidateOptionsMenu();
}
}
@@ -0,0 +1,92 @@
package uk.co.alt236.btlescan.adapters;
import java.util.ArrayList;
import java.util.List;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.containers.BluetoothLeDevice;
import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
// Adapter for holding devices found through scanning.
public class LeDeviceListAdapter extends BaseAdapter {
private final List<BluetoothLeDevice> mLeDevices;
private final LayoutInflater mInflator;
public LeDeviceListAdapter(Activity activity) {
super();
mLeDevices = new ArrayList<BluetoothLeDevice>();
mInflator = activity.getLayoutInflater();
}
public void addDevice(BluetoothLeDevice device) {
final int position = mLeDevices.indexOf(device);
if(position == -1){
mLeDevices.add(device);
} else {
mLeDevices.set(position, device);
}
}
public BluetoothLeDevice getDevice(int position) {
return mLeDevices.get(position);
}
public void clear() {
mLeDevices.clear();
}
@Override
public int getCount() {
return mLeDevices.size();
}
@Override
public Object getItem(int i) {
return mLeDevices.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder;
// General ListView optimization code.
if (view == null) {
view = mInflator.inflate(R.layout.list_item_device, null);
viewHolder = new ViewHolder();
viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address);
viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name);
viewHolder.deviceRssi = (TextView) view.findViewById(R.id.device_rssi);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
final BluetoothLeDevice device = mLeDevices.get(i);
final String deviceName = device.getName();
if (deviceName != null && deviceName.length() > 0){
viewHolder.deviceName.setText(deviceName);
} else{
viewHolder.deviceName.setText(R.string.unknown_device);
}
viewHolder.deviceAddress.setText(device.getAddress());
viewHolder.deviceRssi.setText(String.valueOf(device.getRssi()) + "db");
return view;
}
static class ViewHolder {
TextView deviceName;
TextView deviceAddress;
TextView deviceRssi;
}
}
@@ -7,6 +7,26 @@ import java.util.Arrays;
* AdRecord
*/
public final class AdRecord {
// 02 # Number of bytes that follow in first AD structure
// 01 # Flags AD type
// 1A # Flags value 0x1A = 000011010
// bit 0 (OFF) LE Limited Discoverable Mode
// bit 1 (ON) LE General Discoverable Mode
// bit 2 (OFF) BR/EDR Not Supported
// bit 3 (ON) Simultaneous LE and BR/EDR to Same Device Capable (controller)
// bit 4 (ON) Simultaneous LE and BR/EDR to Same Device Capable (Host)
// 1A # Number of bytes that follow in second (and last) AD structure
// FF # Manufacturer specific data AD type
// 4C 00 # Company identifier code (0x004C == Apple)
// 02 # Byte 0 of iBeacon advertisement indicator
// 15 # Byte 1 of iBeacon advertisement indicator
// e2 c5 6d b5 df fb 48 d2 b0 60 d0 f5 a7 10 96 e0 # iBeacon proximity uuid
// 00 00 # major
// 00 00 # minor
// c5 # The 2's complement of the calibrated Tx Power
/**
* General FLAGS
*
@@ -143,7 +163,7 @@ public final class AdRecord {
case TYPE_MANUFACTURER_SPECIFIC_DATA:
return "Manufacturer Specific Data";
case TYPE_LOCAL_NAME_COMPLETE:
return "Name";
return "Name (Complete)";
case TYPE_LOCAL_NAME_SHORT:
return "Name (Short)";
case TYPE_SECURITY_MANAGER_OOB_FLAGS:
@@ -11,6 +11,25 @@ import android.annotation.SuppressLint;
public class AdRecordUtils {
/* Helper functions to parse out common data payloads from an AD structure */
static final String HEXES = "0123456789ABCDEF";
public static String byteArrayToHexString(final byte[] array){
final StringBuffer sb = new StringBuffer();
boolean firstEntry = true;
sb.append('[');
for ( final byte b : array ) {
if(!firstEntry){
sb.append(", ");
}
sb.append(HEXES.charAt((b & 0xF0) >> 4));
sb.append(HEXES.charAt((b & 0x0F)));
firstEntry = false;
}
sb.append(']');
return sb.toString();
}
public static String getRecordDataAsString(AdRecord nameRecord) {
if(nameRecord == null){return new String();}
@@ -64,7 +83,7 @@ public class AdRecordUtils {
return Collections.unmodifiableList(records);
}
@SuppressLint("UseSparseArrays")
public static Map<Integer, AdRecord> parseScanRecordAsMap(byte[] scanRecord) {
final Map<Integer, AdRecord> records = new HashMap<Integer, AdRecord>();
@@ -7,7 +7,7 @@ import android.bluetooth.BluetoothDevice;
public class BluetoothLeDevice {
private final BluetoothDevice mDevice;
private final int mRssi;
private transient final int mRssi;
private final byte[] mScanRecord;
private final AdRecordStore mRecordStore;
@@ -18,14 +18,37 @@ public class BluetoothLeDevice {
mRecordStore = new AdRecordStore(AdRecordUtils.parseScanRecordAsMap(scanRecord));
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
BluetoothLeDevice other = (BluetoothLeDevice) obj;
if (mDevice == null) {
if (other.mDevice != null)
return false;
} else if (!mDevice.equals(other.mDevice))
return false;
if (!Arrays.equals(mScanRecord, other.mScanRecord))
return false;
return true;
}
public String getAddress(){
return mDevice.getAddress();
}
public AdRecordStore getAdRecordStore(){
return mRecordStore;
}
public String getBluetoothDeviceBondState(){
return resolveBondingState(mDevice.getBondState());
}
public String getBluetoothDeviceClassName(){
return resolveBluetoothClass(mDevice.getBluetoothClass().getDeviceClass());
}
@@ -33,7 +56,11 @@ public class BluetoothLeDevice {
public BluetoothDevice getDevice() {
return mDevice;
}
public String getName(){
return mDevice.getName();
}
public int getRssi() {
return mRssi;
}
@@ -41,11 +68,22 @@ public class BluetoothLeDevice {
public byte[] getScanRecord() {
return mScanRecord;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((mDevice == null) ? 0 : mDevice.hashCode());
result = prime * result + Arrays.hashCode(mScanRecord);
return result;
}
@Override
public String toString() {
return "BluetoothLeDevice [mDevice=" + mDevice + ", mRssi=" + mRssi + ", mScanRecord=" + Arrays.toString(mScanRecord) + ", mRecordStore=" + mRecordStore + ", getBluetoothDeviceBondState()=" + getBluetoothDeviceBondState() + ", getBluetoothDeviceClassName()=" + getBluetoothDeviceClassName() + "]";
return "BluetoothLeDevice [mDevice=" + mDevice + ", mRssi=" + mRssi + ", mScanRecord=" + AdRecordUtils.byteArrayToHexString(mScanRecord) + ", mRecordStore=" + mRecordStore + ", getBluetoothDeviceBondState()=" + getBluetoothDeviceBondState() + ", getBluetoothDeviceClassName()=" + getBluetoothDeviceClassName() + "]";
}
private static String resolveBluetoothClass(int btClass){
switch (btClass){
@@ -5,8 +5,6 @@ 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;
@@ -18,21 +16,25 @@ public class BluetoothLeScanner {
mBluetoothUtils = bluetoothUtils;
}
public void scanLeDevice(final boolean enable) {
public boolean isScanning() {
return mScanning;
}
public void scanLeDevice(final int duration, 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);
if(duration > 0){
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
Log.d("TAG", "~ Stopping Scan (timeout)");
mScanning = false;
mBluetoothUtils.getBluetoothAdapter().stopLeScan(mLeScanCallback);
}
}, duration);
}
mScanning = true;
mBluetoothUtils.getBluetoothAdapter().startLeScan(mLeScanCallback);
} else {