Migrated project from Eclipse to Android Studio

This commit is contained in:
Alexandros Schillings
2015-07-03 14:22:13 +01:00
parent cc58a76bc6
commit 67bc13a9c2
103 changed files with 914 additions and 184 deletions
+43
View File
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="uk.co.alt236.btlescan"
android:versionCode="3"
android:versionName="0.0.3" >
<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.activities.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>
<activity
android:name="uk.co.alt236.btlescan.activities.DeviceDetailsActivity"
android:label="@string/app_name" >
</activity>
<activity android:name="uk.co.alt236.btlescan.activities.DeviceControlActivity" />
<service
android:name="uk.co.alt236.btlescan.services.BluetoothLeService"
android:enabled="true" />
</application>
</manifest>
@@ -0,0 +1,412 @@
/*
* 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.
*/
package uk.co.alt236.btlescan.activities;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.bluetoothlelib.resolvers.GattAttributeResolver;
import uk.co.alt236.bluetoothlelib.util.ByteUtils;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.services.BluetoothLeService;
import android.app.Activity;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ExpandableListView;
import android.widget.SimpleExpandableListAdapter;
import android.widget.TextView;
import butterknife.ButterKnife;
import butterknife.InjectView;
/**
* For a given BLE device, this Activity provides the user interface to connect, display data,
* and display GATT services and characteristics supported by the device. The Activity
* communicates with {@code BluetoothLeService}, which in turn interacts with the
* Bluetooth LE API.
*/
public class DeviceControlActivity extends Activity {
private final static String TAG = DeviceControlActivity.class.getSimpleName();
public static final String EXTRA_DEVICE = "extra_device";
private static final String LIST_NAME = "NAME";
private static final String LIST_UUID = "UUID";
private BluetoothGattCharacteristic mNotifyCharacteristic;
private BluetoothLeService mBluetoothLeService;
private List<List<BluetoothGattCharacteristic>> mGattCharacteristics = new ArrayList<List<BluetoothGattCharacteristic>>();
private String mDeviceAddress;
private String mDeviceName;
@InjectView(R.id.gatt_services_list) ExpandableListView mGattServicesList;
@InjectView(R.id.connection_state) TextView mConnectionState;
@InjectView(R.id.uuid) TextView mGattUUID;
@InjectView(R.id.description) TextView mGattUUIDDesc;
@InjectView(R.id.data_as_string) TextView mDataAsString;
@InjectView(R.id.data_as_array) TextView mDataAsArray;
private boolean mConnected = false;
private String mExportString;
private BluetoothLeDevice mDevice;
// Code to manage Service lifecycle.
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService();
if (!mBluetoothLeService.initialize()) {
Log.e(TAG, "Unable to initialize Bluetooth");
finish();
}
// Automatically connects to the device upon successful start-up initialization.
mBluetoothLeService.connect(mDeviceAddress);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
mBluetoothLeService = null;
}
};
// Handles various events fired by the Service.
// ACTION_GATT_CONNECTED: connected to a GATT server.
// ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
// ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
// ACTION_DATA_AVAILABLE: received data from the device.
// this can be a result of read or notification operations.
private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
mConnected = true;
updateConnectionState(R.string.connected);
invalidateOptionsMenu();
} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
mConnected = false;
updateConnectionState(R.string.disconnected);
invalidateOptionsMenu();
clearUI();
} else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
// Show all the supported services and characteristics on the user interface.
displayGattServices(mBluetoothLeService.getSupportedGattServices());
} else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
final String noData = getString(R.string.no_data);
final String uuid = intent.getStringExtra(BluetoothLeService.EXTRA_UUID_CHAR);
final byte[] dataArr = intent.getByteArrayExtra(BluetoothLeService.EXTRA_DATA_RAW);
mGattUUID.setText(tryString(uuid, noData));
mGattUUIDDesc.setText(GattAttributeResolver.getAttributeName(uuid, getString(R.string.unknown)));
mDataAsArray.setText(ByteUtils.byteArrayToHexString(dataArr));
mDataAsString.setText(new String(dataArr));
}
}
};
// If a given GATT characteristic is selected, check for supported features. This sample
// demonstrates 'Read' and 'Notify' features. See
// http://d.android.com/reference/android/bluetooth/BluetoothGatt.html for the complete
// list of supported characteristic features.
private final ExpandableListView.OnChildClickListener servicesListClickListner = new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
if (mGattCharacteristics != null) {
final BluetoothGattCharacteristic characteristic = mGattCharacteristics.get(groupPosition).get(childPosition);
final int charaProp = characteristic.getProperties();
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
// If there is an active notification on a characteristic, clear
// it first so it doesn't update the data field on the user interface.
if (mNotifyCharacteristic != null) {
mBluetoothLeService.setCharacteristicNotification(mNotifyCharacteristic, false);
mNotifyCharacteristic = null;
}
mBluetoothLeService.readCharacteristic(characteristic);
}
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
mNotifyCharacteristic = characteristic;
mBluetoothLeService.setCharacteristicNotification(characteristic, true);
}
return true;
}
return false;
}
};
private void clearUI() {
mGattServicesList.setAdapter((SimpleExpandableListAdapter) null);
mGattUUID.setText(R.string.no_data);
mGattUUIDDesc.setText(R.string.no_data);
mDataAsArray.setText(R.string.no_data);
mDataAsString.setText(R.string.no_data);
}
private void generateExportString(List<BluetoothGattService> gattServices){
final String unknownServiceString = getResources().getString(R.string.unknown_service);
final String unknownCharaString = getResources().getString(R.string.unknown_characteristic);
final StringBuilder exportBuilder = new StringBuilder();
exportBuilder.append("Device Name: ");
exportBuilder.append(mDeviceName);
exportBuilder.append('\n');
exportBuilder.append("Device Address: ");
exportBuilder.append(mDeviceAddress);
exportBuilder.append('\n');
exportBuilder.append('\n');
exportBuilder.append("Services:");
exportBuilder.append("--------------------------");
exportBuilder.append('\n');
String uuid = null;
for (BluetoothGattService gattService : gattServices) {
uuid = gattService.getUuid().toString();
exportBuilder.append(GattAttributeResolver.getAttributeName(uuid, unknownServiceString));
exportBuilder.append(" (");
exportBuilder.append(uuid);
exportBuilder.append(')');
exportBuilder.append('\n');
final List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
for (final BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
uuid = gattCharacteristic.getUuid().toString();
exportBuilder.append('\t');
exportBuilder.append(GattAttributeResolver.getAttributeName(uuid, unknownCharaString));
exportBuilder.append(" (");
exportBuilder.append(uuid);
exportBuilder.append(')');
exportBuilder.append('\n');
}
exportBuilder.append('\n');
exportBuilder.append('\n');
}
exportBuilder.append("--------------------------");
exportBuilder.append('\n');
mExportString = exportBuilder.toString();
}
// Demonstrates how to iterate through the supported GATT Services/Characteristics.
// In this sample, we populate the data structure that is bound to the ExpandableListView
// on the UI.
private void displayGattServices(List<BluetoothGattService> gattServices) {
if (gattServices == null) return;
generateExportString(gattServices);
String uuid = null;
final String unknownServiceString = getResources().getString(R.string.unknown_service);
final String unknownCharaString = getResources().getString(R.string.unknown_characteristic);
final List<Map<String, String>> gattServiceData = new ArrayList<Map<String, String>>();
final List<List<Map<String, String>>> gattCharacteristicData = new ArrayList<List<Map<String, String>>>();
mGattCharacteristics = new ArrayList<List<BluetoothGattCharacteristic>>();
// Loops through available GATT Services.
for (BluetoothGattService gattService : gattServices) {
final Map<String, String> currentServiceData = new HashMap<String, String>();
uuid = gattService.getUuid().toString();
currentServiceData.put(LIST_NAME, GattAttributeResolver.getAttributeName(uuid, unknownServiceString));
currentServiceData.put(LIST_UUID, uuid);
gattServiceData.add(currentServiceData);
final List<Map<String, String>> gattCharacteristicGroupData = new ArrayList<Map<String, String>>();
final List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
final List<BluetoothGattCharacteristic> charas = new ArrayList<BluetoothGattCharacteristic>();
// Loops through available Characteristics.
for (final BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
charas.add(gattCharacteristic);
final Map<String, String> currentCharaData = new HashMap<String, String>();
uuid = gattCharacteristic.getUuid().toString();
currentCharaData.put(LIST_NAME, GattAttributeResolver.getAttributeName(uuid, unknownCharaString));
currentCharaData.put(LIST_UUID, uuid);
gattCharacteristicGroupData.add(currentCharaData);
}
mGattCharacteristics.add(charas);
gattCharacteristicData.add(gattCharacteristicGroupData);
}
final SimpleExpandableListAdapter gattServiceAdapter = new SimpleExpandableListAdapter(
this,
gattServiceData,
android.R.layout.simple_expandable_list_item_2,
new String[] {LIST_NAME, LIST_UUID},
new int[] { android.R.id.text1, android.R.id.text2 },
gattCharacteristicData,
android.R.layout.simple_expandable_list_item_2,
new String[] {LIST_NAME, LIST_UUID},
new int[] { android.R.id.text1, android.R.id.text2 }
);
mGattServicesList.setAdapter(gattServiceAdapter);
invalidateOptionsMenu();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gatt_services);
final Intent intent = getIntent();
mDevice = intent.getParcelableExtra(EXTRA_DEVICE);
mDeviceName = mDevice.getName();
mDeviceAddress = mDevice.getAddress();
ButterKnife.inject(this);
// Sets up UI references.
((TextView) findViewById(R.id.device_address)).setText(mDeviceAddress);
mGattServicesList.setOnChildClickListener(servicesListClickListner);
getActionBar().setTitle(mDeviceName);
getActionBar().setDisplayHomeAsUpEnabled(true);
final Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.gatt_services, menu);
if (mConnected) {
menu.findItem(R.id.menu_connect).setVisible(false);
menu.findItem(R.id.menu_disconnect).setVisible(true);
} else {
menu.findItem(R.id.menu_connect).setVisible(true);
menu.findItem(R.id.menu_disconnect).setVisible(false);
}
if(mExportString == null){
menu.findItem(R.id.menu_share).setVisible(false);
} else {
menu.findItem(R.id.menu_share).setVisible(true);
}
return true;
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mServiceConnection);
mBluetoothLeService = null;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case R.id.menu_connect:
mBluetoothLeService.connect(mDeviceAddress);
return true;
case R.id.menu_disconnect:
mBluetoothLeService.disconnect();
return true;
case android.R.id.home:
onBackPressed();
return true;
case R.id.menu_share:
final Intent intent = new Intent(android.content.Intent.ACTION_SEND);
final String subject = getString(R.string.exporter_email_device_services_subject, mDeviceName, mDeviceAddress);
intent.setType("text/plain");
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject);
intent.putExtra(android.content.Intent.EXTRA_TEXT, mExportString);
startActivity(Intent.createChooser(
intent,
getString(R.string.exporter_email_device_list_picker_text)));
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(mGattUpdateReceiver);
}
@Override
protected void onResume() {
super.onResume();
registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
if (mBluetoothLeService != null) {
final boolean result = mBluetoothLeService.connect(mDeviceAddress);
Log.d(TAG, "Connect request result=" + result);
}
}
private void updateConnectionState(final int resourceId) {
runOnUiThread(new Runnable() {
@Override
public void run() {
final int colourId;
switch(resourceId){
case R.string.connected:
colourId = android.R.color.holo_green_dark;
break;
case R.string.disconnected:
colourId = android.R.color.holo_red_dark;
break;
default:
colourId = android.R.color.black;
break;
}
mConnectionState.setText(resourceId);
mConnectionState.setTextColor(getResources().getColor(colourId));
}
});
}
private static IntentFilter makeGattUpdateIntentFilter() {
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);
intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);
intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE);
return intentFilter;
}
private static String tryString(String string, String fallback){
if(string == null){
return fallback;
} else{
return string;
}
}
}
@@ -0,0 +1,206 @@
package uk.co.alt236.btlescan.activities;
import java.util.Collection;
import java.util.Locale;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.bluetoothlelib.device.adrecord.AdRecord;
import uk.co.alt236.bluetoothlelib.device.mfdata.IBeaconManufacturerData;
import uk.co.alt236.bluetoothlelib.resolvers.CompanyIdentifierResolver;
import uk.co.alt236.bluetoothlelib.util.AdRecordUtils;
import uk.co.alt236.bluetoothlelib.util.ByteUtils;
import uk.co.alt236.bluetoothlelib.util.IBeaconUtils;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.util.TimeFormatter;
import android.app.ListActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.LinearLayout;
import android.widget.TextView;
import butterknife.ButterKnife;
import com.commonsware.cwac.merge.MergeAdapter;
public class DeviceDetailsActivity extends ListActivity{
public static final String EXTRA_DEVICE = "extra_device";
private BluetoothLeDevice mDevice;
private void appendAdRecordView(MergeAdapter adapter, String title, AdRecord record){
final LinearLayout lt = (LinearLayout) getLayoutInflater().inflate(R.layout.list_item_view_adrecord, null);
final TextView tvString = (TextView) lt.findViewById(R.id.data_as_string);
final TextView tvArray = (TextView) lt.findViewById(R.id.data_as_array);
final TextView tvTitle = (TextView) lt.findViewById(R.id.title);
tvTitle.setText(title );
tvString.setText("'" + AdRecordUtils.getRecordDataAsString(record) + "'");
tvArray.setText("'" + ByteUtils.byteArrayToHexString(record.getData()) + "'");
adapter.addView(lt);
}
private void appendDeviceInfo(MergeAdapter adapter, BluetoothLeDevice device){
final LinearLayout lt = (LinearLayout) getLayoutInflater().inflate(R.layout.list_item_view_device_info, null);
final TextView tvName = (TextView) lt.findViewById(R.id.deviceName);
final TextView tvAddress = (TextView) lt.findViewById(R.id.deviceAddress);
final TextView tvClass = (TextView) lt.findViewById(R.id.deviceClass);
final TextView tvBondingState = (TextView) lt.findViewById(R.id.deviceBondingState);
tvName.setText(device.getName());
tvAddress.setText(device.getAddress());
tvClass.setText(device.getBluetoothDeviceClassName());
tvBondingState.setText(device.getBluetoothDeviceBondState());
adapter.addView(lt);
}
private void appendHeader(MergeAdapter adapter, String title){
final LinearLayout lt = (LinearLayout) getLayoutInflater().inflate(R.layout.list_item_view_header, null);
final TextView tvTitle = (TextView) lt.findViewById(R.id.title);
tvTitle.setText(title);
adapter.addView(lt);
}
private void appendIBeaconInfo(MergeAdapter adapter, IBeaconManufacturerData iBeaconData){
final LinearLayout lt = (LinearLayout) getLayoutInflater().inflate(R.layout.list_item_view_ibeacon_details, null);
final TextView tvCompanyId = (TextView) lt.findViewById(R.id.companyId);
final TextView tvAdvert = (TextView) lt.findViewById(R.id.advertisement);
final TextView tvUUID = (TextView) lt.findViewById(R.id.uuid);
final TextView tvMajor = (TextView) lt.findViewById(R.id.major);
final TextView tvMinor = (TextView) lt.findViewById(R.id.minor);
final TextView tvTxPower = (TextView) lt.findViewById(R.id.txpower);
tvCompanyId.setText(
CompanyIdentifierResolver.getCompanyName(iBeaconData.getCompanyIdentifier(), getString(R.string.unknown))
+ " (" + hexEncode(iBeaconData.getCompanyIdentifier()) + ")");
tvAdvert.setText(iBeaconData.getIBeaconAdvertisement() + " (" + hexEncode( iBeaconData.getIBeaconAdvertisement() ) + ")");
tvUUID.setText(iBeaconData.getUUID().toString());
tvMajor.setText(iBeaconData.getMajor() + " (" + hexEncode( iBeaconData.getMajor() ) + ")");
tvMinor.setText(iBeaconData.getMinor() + " (" + hexEncode( iBeaconData.getMinor() ) + ")");
tvTxPower.setText(iBeaconData.getCalibratedTxPower() + " (" + hexEncode( iBeaconData.getCalibratedTxPower() ) + ")");
adapter.addView(lt);
}
private void appendRssiInfo(MergeAdapter adapter, BluetoothLeDevice device){
final LinearLayout lt = (LinearLayout) getLayoutInflater().inflate(R.layout.list_item_view_rssi_info, null);
final TextView tvFirstTimestamp = (TextView) lt.findViewById(R.id.firstTimestamp);
final TextView tvFirstRssi = (TextView) lt.findViewById(R.id.firstRssi);
final TextView tvLastTimestamp = (TextView) lt.findViewById(R.id.lastTimestamp);
final TextView tvLastRssi = (TextView) lt.findViewById(R.id.lastRssi);
final TextView tvRunningAverageRssi = (TextView) lt.findViewById(R.id.runningAverageRssi);
tvFirstTimestamp.setText(formatTime(device.getFirstTimestamp()));
tvFirstRssi.setText(formatRssi(device.getFirstRssi()));
tvLastTimestamp.setText(formatTime(device.getTimestamp()));
tvLastRssi.setText(formatRssi(device.getRssi()));
tvRunningAverageRssi.setText(formatRssi(device.getRunningAverageRssi()));
adapter.addView(lt);
}
private void appendSimpleText(MergeAdapter adapter, byte data[]){
appendSimpleText(adapter, ByteUtils.byteArrayToHexString(data));
}
private void appendSimpleText(MergeAdapter adapter, String data){
final LinearLayout lt = (LinearLayout) getLayoutInflater().inflate(R.layout.list_item_view_textview, null);
final TextView tvData = (TextView) lt.findViewById(R.id.data);
tvData.setText(data);
adapter.addView(lt);
}
private String formatRssi(double rssi){
return getString(R.string.formatter_db, String.valueOf(rssi));
}
private String formatRssi(int rssi){
return getString(R.string.formatter_db, String.valueOf(rssi));
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_details);
ButterKnife.inject(this);
mDevice = getIntent().getParcelableExtra(EXTRA_DEVICE);
pupulateDetails(mDevice);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.details, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_connect:
final Intent intent = new Intent(this, DeviceControlActivity.class);
intent.putExtra(DeviceControlActivity.EXTRA_DEVICE, mDevice);
startActivity(intent);
break;
}
return true;
}
private void pupulateDetails(BluetoothLeDevice device) {
final MergeAdapter adapter = new MergeAdapter();
if(device == null){
appendHeader(adapter, getString(R.string.header_device_info));
appendSimpleText(adapter, getString(R.string.invalid_device_data));
} else {
appendHeader(adapter, getString(R.string.header_device_info));
appendDeviceInfo(adapter, device);
appendHeader(adapter, getString(R.string.header_rssi_info));
appendRssiInfo(adapter, device);
appendHeader(adapter, getString(R.string.header_scan_record));
appendSimpleText(adapter, device.getScanRecord());
final Collection<AdRecord> adRecords = device.getAdRecordStore().getRecordsAsCollection();
if(adRecords.size() > 0){
appendHeader(adapter, getString(R.string.header_raw_ad_records));
for(final AdRecord record : adRecords){
appendAdRecordView(
adapter,
"#" + record.getType() + " " + record.getHumanReadableType(),
record);
}
}
final boolean isIBeacon = IBeaconUtils.isThisAnIBeacon(device);
if(isIBeacon){
final IBeaconManufacturerData iBeaconData = new IBeaconManufacturerData(device);
appendHeader(adapter, getString(R.string.header_ibeacon_data));
appendIBeaconInfo(adapter, iBeaconData);
}
}
getListView().setAdapter(adapter);
}
private static String formatTime(long time){
return TimeFormatter.getIsoDateTime(time);
}
private static String hexEncode(int integer){
return "0x" + Integer.toHexString(integer).toUpperCase(Locale.US);
}
}
@@ -0,0 +1,196 @@
package uk.co.alt236.btlescan.activities;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.adapters.LeDeviceListAdapter;
import uk.co.alt236.btlescan.containers.BluetoothLeDeviceStore;
import uk.co.alt236.btlescan.util.BluetoothLeScanner;
import uk.co.alt236.btlescan.util.BluetoothUtils;
import uk.co.alt236.easycursor.objectcursor.EasyObjectCursor;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.text.util.Linkify;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
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;
@InjectView(R.id.tvItemCount) TextView mTvItemCount;
private BluetoothUtils mBluetoothUtils;
private BluetoothLeScanner mScanner;
private LeDeviceListAdapter mLeDeviceListAdapter;
private BluetoothLeDeviceStore mDeviceStore;
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, System.currentTimeMillis());
mDeviceStore.addDevice(deviceLe);
final EasyObjectCursor<BluetoothLeDevice> c = mDeviceStore.getDeviceCursor();
runOnUiThread(new Runnable() {
@Override
public void run() {
mLeDeviceListAdapter.swapCursor(c);
updateItemCount(mLeDeviceListAdapter.getCount());
}
});
}
};
private void updateItemCount(int count){
mTvItemCount.setText(
getString(
R.string.formatter_item_count,
String.valueOf(count)));
}
private void displayAboutDialog(){
// REALLY REALLY LAZY LINKIFIED DIALOG
final int paddingSizeDp = 5;
final float scale = getResources().getDisplayMetrics().density;
final int dpAsPixels = (int) (paddingSizeDp * scale + 0.5f);
final TextView textView=new TextView(this);
final SpannableString text = new SpannableString(getString(R.string.about_dialog_text));
textView.setText(text);
textView.setAutoLinkMask(RESULT_OK);
textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setPadding(dpAsPixels, dpAsPixels, dpAsPixels, dpAsPixels);
Linkify.addLinks(text, Linkify.ALL);
new AlertDialog.Builder(this)
.setTitle(R.string.menu_about)
.setCancelable(false)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {}
})
.setView(textView)
.show();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
mDeviceStore = new BluetoothLeDeviceStore();
mBluetoothUtils = new BluetoothUtils(this);
mScanner = new BluetoothLeScanner(mLeScanCallback, mBluetoothUtils);
updateItemCount(0);
}
@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);
}
if(getListView().getCount() > 0){
menu.findItem(R.id.menu_share).setVisible(true);
} else {
menu.findItem(R.id.menu_share).setVisible(false);
}
return true;
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
final BluetoothLeDevice device = (BluetoothLeDevice) mLeDeviceListAdapter.getItem(position);
if (device == null) return;
final Intent intent = new Intent(this, DeviceDetailsActivity.class);
intent.putExtra(DeviceDetailsActivity.EXTRA_DEVICE, device);
startActivity(intent);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_scan:
startScan();
break;
case R.id.menu_stop:
mScanner.scanLeDevice(-1, false);
invalidateOptionsMenu();
break;
case R.id.menu_about:
displayAboutDialog();
break;
case R.id.menu_share:
mDeviceStore.shareDataAsEmail(this);
}
return true;
}
@Override
protected void onPause() {
super.onPause();
mScanner.scanLeDevice(-1, false);
}
@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);
}
invalidateOptionsMenu();
}
private void startScan(){
final boolean mIsBluetoothOn = mBluetoothUtils.isBluetoothOn();
final boolean mIsBluetoothLePresent = mBluetoothUtils.isBluetoothLeSupported();
mDeviceStore.clear();
updateItemCount(0);
mLeDeviceListAdapter = new LeDeviceListAdapter(this, mDeviceStore.getDeviceCursor());
setListAdapter(mLeDeviceListAdapter);
mBluetoothUtils.askUserToEnableBluetoothIfNeeded();
if(mIsBluetoothOn && mIsBluetoothLePresent){
mScanner.scanLeDevice(-1, true);
invalidateOptionsMenu();
}
}
}
@@ -0,0 +1,123 @@
package uk.co.alt236.btlescan.adapters;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.bluetoothlelib.device.IBeaconDevice;
import uk.co.alt236.bluetoothlelib.util.IBeaconUtils;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.util.Constants;
import uk.co.alt236.easycursor.objectcursor.EasyObjectCursor;
import android.app.Activity;
import android.support.v4.widget.SimpleCursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
// Adapter for holding devices found through scanning.
public class LeDeviceListAdapter extends SimpleCursorAdapter {
private final LayoutInflater mInflator;
private final Activity mActivity;
public LeDeviceListAdapter(Activity activity, EasyObjectCursor<BluetoothLeDevice> cursor) {
super(activity, R.layout.list_item_device, cursor, new String[0], new int[0], 0);
mInflator = activity.getLayoutInflater();
mActivity = activity;
}
@SuppressWarnings("unchecked")
@Override
public EasyObjectCursor<BluetoothLeDevice> getCursor(){
return ((EasyObjectCursor<BluetoothLeDevice>) super.getCursor());
}
@Override
public BluetoothLeDevice getItem(int i){
return getCursor().getItem(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);
viewHolder.deviceIcon = (ImageView) view.findViewById(R.id.device_icon);
viewHolder.deviceLastUpdated = (TextView) view.findViewById(R.id.device_last_update);
viewHolder.ibeaconMajor = (TextView) view.findViewById(R.id.ibeacon_major);
viewHolder.ibeaconMinor = (TextView) view.findViewById(R.id.ibeacon_minor);
viewHolder.ibeaconDistance = (TextView) view.findViewById(R.id.ibeacon_distance);
viewHolder.ibeaconUUID = (TextView) view.findViewById(R.id.ibeacon_uuid);
viewHolder.ibeaconTxPower = (TextView) view.findViewById(R.id.ibeacon_tx_power);
viewHolder.ibeaconSection = (View) view.findViewById(R.id.ibeacon_section);
viewHolder.ibeaconDistanceDescriptor = (TextView) view.findViewById(R.id.ibeacon_distance_descriptor);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
final BluetoothLeDevice device = getCursor().getItem(i);
final String deviceName = device.getName();
final double rssi = device.getRssi();
if (deviceName != null && deviceName.length() > 0){
viewHolder.deviceName.setText(deviceName);
} else{
viewHolder.deviceName.setText(R.string.unknown_device);
}
if (IBeaconUtils.isThisAnIBeacon(device)){
final IBeaconDevice iBeacon = new IBeaconDevice(device);
final String accuracy = Constants.DOUBLE_TWO_DIGIT_ACCURACY.format(iBeacon.getAccuracy());
viewHolder.deviceIcon.setImageResource(R.drawable.ic_device_ibeacon);
viewHolder.ibeaconSection.setVisibility(View.VISIBLE);
viewHolder.ibeaconMajor.setText(String.valueOf(iBeacon.getMajor()));
viewHolder.ibeaconMinor.setText(String.valueOf(iBeacon.getMinor()));
viewHolder.ibeaconTxPower.setText(String.valueOf(iBeacon.getCalibratedTxPower()));
viewHolder.ibeaconUUID.setText(iBeacon.getUUID());
viewHolder.ibeaconDistance.setText(
mActivity.getString(R.string.formatter_meters, accuracy));
viewHolder.ibeaconDistanceDescriptor.setText(iBeacon.getDistanceDescriptor().toString());
} else {
viewHolder.deviceIcon.setImageResource(R.drawable.ic_bluetooth);
viewHolder.ibeaconSection.setVisibility(View.GONE);
}
final String rssiString =
mActivity.getString(R.string.formatter_db, String.valueOf(rssi));
final String runningAverageRssiString =
mActivity.getString(R.string.formatter_db, String.valueOf(device.getRunningAverageRssi()));
viewHolder.deviceLastUpdated.setText(
android.text.format.DateFormat.format(
Constants.TIME_FORMAT, new java.util.Date(device.getTimestamp())));
viewHolder.deviceAddress.setText(device.getAddress());
viewHolder.deviceRssi.setText(rssiString + " / " + runningAverageRssiString);
return view;
}
static class ViewHolder {
TextView deviceName;
TextView deviceAddress;
TextView deviceRssi;
TextView ibeaconUUID;
TextView ibeaconMajor;
TextView ibeaconMinor;
TextView ibeaconTxPower;
TextView ibeaconDistance;
TextView ibeaconDistanceDescriptor;
TextView deviceLastUpdated;
View ibeaconSection;
ImageView deviceIcon;
}
}
@@ -0,0 +1,182 @@
package uk.co.alt236.btlescan.containers;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.bluetoothlelib.device.IBeaconDevice;
import uk.co.alt236.bluetoothlelib.util.ByteUtils;
import uk.co.alt236.bluetoothlelib.util.IBeaconUtils;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.util.CsvWriterHelper;
import uk.co.alt236.btlescan.util.TimeFormatter;
import uk.co.alt236.easycursor.objectcursor.EasyObjectCursor;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
public class BluetoothLeDeviceStore {
private final Map<String, BluetoothLeDevice> mDeviceMap;
public BluetoothLeDeviceStore(){
mDeviceMap = new HashMap<String, BluetoothLeDevice>();
}
public void addDevice(BluetoothLeDevice device){
if(mDeviceMap.containsKey(device.getAddress())){
mDeviceMap.get(device.getAddress()).updateRssiReading(device.getTimestamp(), device.getRssi());
} else {
mDeviceMap.put(device.getAddress(), device);
}
}
public void clear(){
mDeviceMap.clear();
}
private FileWriter generateFile(File file, String contents){
FileWriter writer = null;
try {
writer = new FileWriter(file);
writer.append(contents);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return writer;
}
public EasyObjectCursor<BluetoothLeDevice> getDeviceCursor(){
return new EasyObjectCursor<BluetoothLeDevice>(
BluetoothLeDevice.class,
getDeviceList(),
"address");
}
public List<BluetoothLeDevice> getDeviceList(){
final List<BluetoothLeDevice> methodResult = new ArrayList<BluetoothLeDevice>(mDeviceMap.values());
Collections.sort(methodResult, new Comparator<BluetoothLeDevice>() {
@Override
public int compare(BluetoothLeDevice arg0, BluetoothLeDevice arg1) {
return arg0.getAddress().compareToIgnoreCase(arg1.getAddress());
}
});
return methodResult;
}
private String getListAsCsv(){
final List<BluetoothLeDevice> list = getDeviceList();
final StringBuilder sb = new StringBuilder();
sb.append(CsvWriterHelper.addStuff("mac"));
sb.append(CsvWriterHelper.addStuff("name"));
sb.append(CsvWriterHelper.addStuff("firstTimestamp"));
sb.append(CsvWriterHelper.addStuff("firstRssi"));
sb.append(CsvWriterHelper.addStuff("currentTimestamp"));
sb.append(CsvWriterHelper.addStuff("currentRssi"));
sb.append(CsvWriterHelper.addStuff("adRecord"));
sb.append(CsvWriterHelper.addStuff("iBeacon"));
sb.append(CsvWriterHelper.addStuff("uuid"));
sb.append(CsvWriterHelper.addStuff("major"));
sb.append(CsvWriterHelper.addStuff("minor"));
sb.append(CsvWriterHelper.addStuff("txPower"));
sb.append(CsvWriterHelper.addStuff("distance"));
sb.append(CsvWriterHelper.addStuff("accuracy"));
sb.append('\n');
for(BluetoothLeDevice device : list){
sb.append(CsvWriterHelper.addStuff(device.getAddress()));
sb.append(CsvWriterHelper.addStuff(device.getName()));
sb.append(CsvWriterHelper.addStuff(TimeFormatter.getIsoDateTime(device.getFirstTimestamp())));
sb.append(CsvWriterHelper.addStuff(device.getFirstRssi()));
sb.append(CsvWriterHelper.addStuff(TimeFormatter.getIsoDateTime(device.getTimestamp())));
sb.append(CsvWriterHelper.addStuff(device.getRssi()));
sb.append(CsvWriterHelper.addStuff(ByteUtils.byteArrayToHexString(device.getScanRecord())));
final boolean isIBeacon = IBeaconUtils.isThisAnIBeacon(device);
final String uuid;
final String minor;
final String major;
final String txPower;
final String distance;
final String accuracy;
if(isIBeacon){
final IBeaconDevice beacon = new IBeaconDevice(device);
uuid = String.valueOf(beacon.getUUID());
minor = String.valueOf(beacon.getMinor());
major = String.valueOf(beacon.getMajor());
txPower = String.valueOf(beacon.getCalibratedTxPower());
distance = beacon.getDistanceDescriptor().toString().toLowerCase(Locale.US);
accuracy = String.valueOf(beacon.getAccuracy());
} else {
uuid = "";
minor = "";
major = "";
txPower = "";
distance = "";
accuracy = "";
}
sb.append(CsvWriterHelper.addStuff(isIBeacon));
sb.append(CsvWriterHelper.addStuff(uuid));
sb.append(CsvWriterHelper.addStuff(minor));
sb.append(CsvWriterHelper.addStuff(major));
sb.append(CsvWriterHelper.addStuff(txPower));
sb.append(CsvWriterHelper.addStuff(distance));
sb.append(CsvWriterHelper.addStuff(accuracy));
sb.append('\n');
}
return sb.toString();
}
public void shareDataAsEmail(Context context){
final long timeInMillis = System.currentTimeMillis();
final String to = null;
final String subject = context.getString(
R.string.exporter_email_device_list_subject,
TimeFormatter.getIsoDateTime(timeInMillis));
final String message = context.getString(R.string.exporter_email_device_list_body);
final Intent i = new Intent(Intent.ACTION_SEND);
i.setType("plain/text");
try {
final File outputDir = context.getCacheDir();
final File outputFile = File.createTempFile("bluetooth_le_" + timeInMillis, ".csv", outputDir);
outputFile.setReadable(true, false);
generateFile(outputFile, getListAsCsv());
i.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(outputFile));
i.putExtra(Intent.EXTRA_EMAIL, new String[] { to });
i.putExtra(Intent.EXTRA_SUBJECT, subject);
i.putExtra(Intent.EXTRA_TEXT, message);
context.startActivity(Intent.createChooser(i, context.getString(R.string.exporter_email_device_list_picker_text)));
} catch (IOException e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,279 @@
/*
* 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.
*/
package uk.co.alt236.btlescan.services;
import java.util.List;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
/**
* Service for managing connection and data communication with a GATT server hosted on a
* given Bluetooth LE device.
*/
public class BluetoothLeService extends Service {
private final static String TAG = BluetoothLeService.class.getSimpleName();
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private String mBluetoothDeviceAddress;
private BluetoothGatt mBluetoothGatt;
private int mConnectionState = STATE_DISCONNECTED;
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
private static final int STATE_CONNECTED = 2;
public final static String ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED";
public final static String ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
public final static String ACTION_GATT_SERVICES_DISCOVERED = "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
public final static String ACTION_DATA_AVAILABLE = "com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
public final static String EXTRA_DATA_RAW = "com.example.bluetooth.le.EXTRA_DATA_RAW";
public final static String EXTRA_UUID_CHAR = "com.example.bluetooth.le.EXTRA_UUID_CHAR";
// Implements callback methods for GATT events that the app cares about. For example,
// connection change and services discovered.
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
mConnectionState = STATE_CONNECTED;
broadcastUpdate(intentAction);
Log.i(TAG, "Connected to GATT server.");
// Attempts to discover services after successful connection.
Log.i(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices());
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_GATT_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
Log.i(TAG, "Disconnected from GATT server.");
broadcastUpdate(intentAction);
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
};
private void broadcastUpdate(final String action) {
final Intent intent = new Intent(action);
sendBroadcast(intent);
}
private void broadcastUpdate(final String action, final BluetoothGattCharacteristic characteristic) {
final Intent intent = new Intent(action);
intent.putExtra(EXTRA_UUID_CHAR, characteristic.getUuid().toString());
// Always try to add the RAW value
final byte[] data = characteristic.getValue();
if (data != null && data.length > 0) {
intent.putExtra(EXTRA_DATA_RAW, data);
}
sendBroadcast(intent);
}
public class LocalBinder extends Binder {
public BluetoothLeService getService() {
return BluetoothLeService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// After using a given device, you should make sure that BluetoothGatt.close() is called
// such that resources are cleaned up properly. In this particular example, close() is
// invoked when the UI is disconnected from the Service.
close();
return super.onUnbind(intent);
}
private final IBinder mBinder = new LocalBinder();
/**
* Initializes a reference to the local Bluetooth adapter.
*
* @return Return true if the initialization is successful.
*/
public boolean initialize() {
// For API level 18 and above, get a reference to BluetoothAdapter through
// BluetoothManager.
if (mBluetoothManager == null) {
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
Log.e(TAG, "Unable to initialize BluetoothManager.");
return false;
}
}
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
return false;
}
return true;
}
/**
* Connects to the GATT server hosted on the Bluetooth LE device.
*
* @param address The device address of the destination device.
*
* @return Return true if the connection is initiated successfully. The connection result
* is reported asynchronously through the
* {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
// Previously connected device. Try to reconnect.
if (mBluetoothDeviceAddress != null
&& address.equals(mBluetoothDeviceAddress)
&& mBluetoothGatt != null) {
Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
if (mBluetoothGatt.connect()) {
mConnectionState = STATE_CONNECTING;
return true;
} else {
return false;
}
}
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}
// We want to directly connect to the device, so we are setting the autoConnect
// parameter to false.
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
Log.d(TAG, "Trying to create a new connection.");
mBluetoothDeviceAddress = address;
mConnectionState = STATE_CONNECTING;
return true;
}
/**
* Disconnects an existing connection or cancel a pending connection. The disconnection result
* is reported asynchronously through the
* {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public void disconnect() {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.disconnect();
}
/**
* After using a given BLE device, the app must call this method to ensure resources are
* released properly.
*/
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}
/**
* Request a read on a given {@code BluetoothGattCharacteristic}. The read result is reported
* asynchronously through the {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
* callback.
*
* @param characteristic The characteristic to read from.
*/
public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.readCharacteristic(characteristic);
}
/**
* Enables or disables notification on a give characteristic.
*
* @param characteristic Characteristic to act on.
* @param enabled If true, enable notification. False otherwise.
*/
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
}
/**
* Retrieves a list of supported GATT services on the connected device. This should be
* invoked only after {@code BluetoothGatt#discoverServices()} completes successfully.
*
* @return A {@code List} of supported services.
*/
public List<BluetoothGattService> getSupportedGattServices() {
if (mBluetoothGatt == null) return null;
return mBluetoothGatt.getServices();
}
}
@@ -0,0 +1,46 @@
package uk.co.alt236.btlescan.util;
import android.bluetooth.BluetoothAdapter;
import android.os.Handler;
import android.util.Log;
public class BluetoothLeScanner {
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 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.
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 {
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();
}
}
}
@@ -0,0 +1,8 @@
package uk.co.alt236.btlescan.util;
import java.text.DecimalFormat;
public class Constants {
public static final DecimalFormat DOUBLE_TWO_DIGIT_ACCURACY = new DecimalFormat("#.##");
public static final String TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
}
@@ -0,0 +1,23 @@
package uk.co.alt236.btlescan.util;
public class CsvWriterHelper {
private static final String QUOTE = "\"";
public static String addStuff(Integer text){
return QUOTE + text + QUOTE + ",";
}
public static String addStuff(Long text){
return QUOTE + text + QUOTE + ",";
}
public static String addStuff(boolean value){
return QUOTE + value + QUOTE + ",";
}
public static String addStuff(String text){
if(text == null){text = "<blank>";}
text = text.replace(QUOTE, "'");
return QUOTE + text.trim() + QUOTE + ",";
}
}
@@ -0,0 +1,18 @@
package uk.co.alt236.btlescan.util;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class TimeFormatter {
private final static String ISO_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS zzz";
private final static SimpleDateFormat ISO_FORMATTER = new UtcDateFormatter(ISO_FORMAT, Locale.US);
public static String getIsoDateTime(Date date){
return ISO_FORMATTER.format(date);
}
public static String getIsoDateTime(long millis){
return getIsoDateTime(new Date(millis));
}
}
@@ -0,0 +1,72 @@
/*******************************************************************************
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* GenieConnect Ltd. ("COMPANY") CONFIDENTIAL
* Unpublished Copyright (c) 2010-2013 GenieConnect Ltd., All Rights Reserved.
*
* NOTICE:
* All information contained herein is, and remains the property of COMPANY.
* The intellectual and technical concepts contained herein are proprietary to
* COMPANY and may be covered by U.S. and Foreign Patents, patents in process, and
* are protected by trade secret or copyright law. Dissemination of this
* information or reproduction of this material is strictly forbidden unless prior
* written permission is obtained from COMPANY. Access to the source code
* contained herein is hereby forbidden to anyone except current COMPANY employees,
* managers or contractors who have executed Confidentiality and Non-disclosure
* agreements explicitly covering such access.
*
* The copyright notice above does not evidence any actual or intended publication
* or disclosure of this source code, which includes information that is
* confidential and/or proprietary, and is a trade secret, of COMPANY.
*
* ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, OR PUBLIC
* DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT THE EXPRESS WRITTEN
* CONSENT OF COMPANY IS STRICTLY PROHIBITED, AND IN VIOLATION OF APPLICABLE LAWS
* AND INTERNATIONAL TREATIES. THE RECEIPT OR POSSESSION OF THIS SOURCE CODE
* AND/OR RELATED INFORMATION DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE,
* DISCLOSE OR DISTRIBUTE ITS CONTENTS, OR TO MANUFACTURE, USE, OR SELL ANYTHING
* THAT IT MAY DESCRIBE, IN WHOLE OR IN PART.
******************************************************************************/
package uk.co.alt236.btlescan.util;
import java.text.DateFormatSymbols;
import java.util.Locale;
import java.util.TimeZone;
import android.annotation.SuppressLint;
public class UtcDateFormatter extends java.text.SimpleDateFormat{
private static final long serialVersionUID = 1L;
private static final String TIME_ZONE_STRING = "UTC";
private static final TimeZone TIME_ZONE_UTC = TimeZone.getTimeZone(TIME_ZONE_STRING);
@SuppressLint("SimpleDateFormat")
public UtcDateFormatter(String template){
super(template);
super.setTimeZone(TIME_ZONE_UTC);
}
@SuppressLint("SimpleDateFormat")
public UtcDateFormatter(String template, DateFormatSymbols symbols){
super(template, symbols);
super.setTimeZone(TIME_ZONE_UTC);
}
public UtcDateFormatter(String template, Locale locale){
super(template, locale);
super.setTimeZone(TIME_ZONE_UTC);
}
/*
* This function will throw an UnsupportedOperationException.
* You are not be able to change the TimeZone of this object
*
* (non-Javadoc)
* @see java.text.DateFormat#setTimeZone(java.util.TimeZone)
*/
@Override
public void setTimeZone(TimeZone timezone){
throw new UnsupportedOperationException("This SimpleDateFormat can only be in " + TIME_ZONE_STRING);
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 824 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

@@ -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>
@@ -0,0 +1,15 @@
<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" >
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
</ListView>
</RelativeLayout>
@@ -0,0 +1,125 @@
<?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.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin" >
<GridLayout
android:id="@+id/deviceInformation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:columnCount="2"
android:useDefaultMargins="true" >
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_device_address" />
<TextView
android:id="@+id/device_address"
style="@style/GridLayoutDataTextView" />
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_state" />
<TextView
android:id="@+id/connection_state"
style="@style/GridLayoutDataTextView" />
</GridLayout>
<View
android:id="@+id/upperSepparator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@id/deviceInformation"
android:background="@android:color/holo_blue_dark" />
<GridLayout
android:id="@+id/gattInformation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:columnCount="2"
android:useDefaultMargins="true" >
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_uuid" />
<TextView
android:id="@+id/uuid"
style="@style/GridLayoutDataTextViewMonospace" />
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_desc" />
<TextView
android:id="@+id/description"
style="@style/GridLayoutDataTextViewMonospace" />
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_as_string" />
<TextView
android:id="@+id/data_as_string"
style="@style/GridLayoutDataTextViewMonospace" />
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_as_array" />
<TextView
android:id="@+id/data_as_array"
style="@style/GridLayoutDataTextViewMonospace" />
</GridLayout>
<View
android:id="@+id/lowerSepparator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_above="@id/gattInformation"
android:background="@android:color/holo_blue_dark" />
<ExpandableListView
android:id="@+id/gatt_services_list"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_above="@id/lowerSepparator"
android:layout_below="@id/upperSepparator" />
</RelativeLayout>
@@ -0,0 +1,113 @@
<LinearLayout 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:orientation="vertical"
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:columnCount="2"
android:paddingBottom="@dimen/activity_vertical_margin" >
<TextView
android:layout_column="0"
android:layout_gravity="center_horizontal"
android:layout_row="0"
android:text="@string/label_bluetooth_le_status"
android:textSize="12sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvBluetoothLe"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="1"
android:layout_marginLeft="28dp"
android:layout_row="0"
android:gravity="right"
android:text="@string/not_supported"
android:textSize="12sp" />
<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="@string/label_bluetooth_status"
android:textSize="12sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvBluetoothStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="1"
android:layout_marginLeft="28dp"
android:layout_row="1"
android:gravity="right"
android:text="@string/off"
android:textSize="12sp" />
</GridLayout>
<View
android:id="@+id/upperSepparator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/holo_blue_dark" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:id="@+id/infoContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical" >
<View
android:id="@+id/lowerSepparator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/holo_blue_dark" />
<TextView
android:id="@+id/tvItemCount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/formatter_item_count" />
</LinearLayout>
<LinearLayout
android:id="@+id/listContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_above="@id/infoContainer"
android:orientation="vertical" >
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical|center_horizontal"
android:text="@string/no_data" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>
@@ -0,0 +1,204 @@
<?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:gravity="top"
android:orientation="horizontal" >
<ImageView
android:id="@+id/device_icon"
android:paddingTop="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:src="@drawable/ic_bluetooth" />
<LinearLayout
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" />
<GridLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:columnCount="2" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/label_mac"
android:textSize="12sp"
android:textStyle="bold" />
<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:paddingRight="5dp"
android:text="Updated:"
android:textSize="12sp"
android:textStyle="bold" />
<TextView
android:id="@+id/device_last_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:textSize="12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/label_rssi"
android:textSize="12sp"
android:textStyle="bold" />
<TextView
android:id="@+id/device_rssi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:textSize="12sp" />
</GridLayout>
<GridLayout
android:id="@+id/ibeacon_section"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@color/light_gray"
android:columnCount="4" >
<!-- ROW 1 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/label_uuid"
android:textSize="12sp"
android:textStyle="bold" />
<TextView
android:id="@+id/ibeacon_uuid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_columnSpan="3"
android:paddingRight="5dp"
android:textSize="12sp" />
<!-- ROW 2 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/label_major"
android:textSize="12sp"
android:textStyle="bold" />
<TextView
android:id="@+id/ibeacon_major"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:textSize="12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:text="@string/label_minor"
android:textSize="12sp"
android:textStyle="bold" />
<TextView
android:id="@+id/ibeacon_minor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:textSize="12sp" />
<!-- ROW 3 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/label_tx_power"
android:textSize="12sp"
android:textStyle="bold" />
<TextView
android:id="@+id/ibeacon_tx_power"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:textSize="12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:text="@string/label_distance"
android:textSize="12sp"
android:textStyle="bold" />
<TextView
android:id="@+id/ibeacon_distance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:textSize="12sp" />
<!-- ROW 4 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="Descriptor:"
android:textSize="12sp"
android:textStyle="bold" />
<TextView
android:id="@+id/ibeacon_distance_descriptor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_columnSpan="3"
android:paddingRight="5dp"
android:textSize="12sp" />
</GridLayout>
</LinearLayout>
</LinearLayout>
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
android:orientation="vertical"
android:paddingBottom="5dp" >
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textStyle="bold" />
<GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="2"
android:useDefaultMargins="true" >
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_as_string" />
<TextView
android:id="@+id/data_as_string"
style="@style/GridLayoutDataTextViewMonospace" />
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_as_array" />
<TextView
android:id="@+id/data_as_array"
style="@style/GridLayoutDataTextViewMonospace" />
</GridLayout>
</LinearLayout>
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
android:orientation="vertical"
android:paddingBottom="5dp" >
<GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="2"
android:useDefaultMargins="true" >
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_device_name" />
<TextView
android:id="@+id/deviceName"
style="@style/GridLayoutDataTextView" />
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_device_address" />
<TextView
android:id="@+id/deviceAddress"
style="@style/GridLayoutDataTextView" />
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_device_class" />
<TextView
android:id="@+id/deviceClass"
style="@style/GridLayoutDataTextView" />
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_bonding_state" />
<TextView
android:id="@+id/deviceBondingState"
style="@style/GridLayoutDataTextView" />
</GridLayout>
</LinearLayout>
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
android:orientation="vertical"
android:paddingBottom="5dp" >
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAllCaps="true"
android:textColor="@android:color/holo_blue_dark"
android:textSize="14sp"
android:textStyle="bold" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/holo_blue_dark" />
</LinearLayout>
@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
android:orientation="vertical"
android:paddingBottom="5dp" >
<GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="2"
android:useDefaultMargins="true" >
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_company_id" />
<TextView
android:id="@+id/companyId"
style="@style/GridLayoutDataTextView" />
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_advertisement" />
<TextView
android:id="@+id/advertisement"
style="@style/GridLayoutDataTextView" />
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_uuid" />
<TextView
android:id="@+id/uuid"
style="@style/GridLayoutDataTextView" />
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_major" />
<TextView
android:id="@+id/major"
style="@style/GridLayoutDataTextView" />
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_minor" />
<TextView
android:id="@+id/minor"
style="@style/GridLayoutDataTextView" />
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_tx_power" />
<TextView
android:id="@+id/txpower"
style="@style/GridLayoutDataTextView" />
</GridLayout>
</LinearLayout>
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
android:orientation="vertical"
android:paddingBottom="5dp" >
<GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="2"
android:useDefaultMargins="true" >
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_first_timestamp" />
<TextView
android:id="@+id/firstTimestamp"
style="@style/GridLayoutDataTextView" />
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_first_rssi" />
<TextView
android:id="@+id/firstRssi"
style="@style/GridLayoutDataTextView" />
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_last_timestamp" />
<TextView
android:id="@+id/lastTimestamp"
style="@style/GridLayoutDataTextView" />
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_last_rssi" />
<TextView
android:id="@+id/lastRssi"
style="@style/GridLayoutDataTextView" />
<TextView
style="@style/GridLayoutTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_running_average_rssi" />
<TextView
android:id="@+id/runningAverageRssi"
style="@style/GridLayoutDataTextView" />
</GridLayout>
</LinearLayout>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
android:orientation="vertical"
android:paddingBottom="5dp" >
<TextView
android:id="@+id/data"
style="@style/GridLayoutDataTextViewMonospace" />
</LinearLayout>
+27
View File
@@ -0,0 +1,27 @@
<?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/menu_connect"
android:orderInCategory="100"
android:showAsAction="always|withText"
android:title="@string/menu_connect"/>
</menu>
@@ -0,0 +1,41 @@
<?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/menu_refresh"
android:checkable="false"
android:orderInCategory="1"
android:showAsAction="ifRoom"/>
<item
android:id="@+id/menu_share"
android:icon="@drawable/ic_action_share"
android:orderInCategory="100"
android:showAsAction="ifRoom"
android:title="@string/menu_share"/>
<item
android:id="@+id/menu_connect"
android:orderInCategory="102"
android:showAsAction="ifRoom|withText"
android:title="@string/menu_connect"/>
<item
android:id="@+id/menu_disconnect"
android:orderInCategory="103"
android:showAsAction="ifRoom|withText"
android:title="@string/menu_disconnect"/>
</menu>
+46
View File
@@ -0,0 +1,46 @@
<?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/menu_refresh"
android:checkable="false"
android:orderInCategory="1"
android:showAsAction="ifRoom"/>
<item
android:id="@+id/menu_scan"
android:orderInCategory="100"
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"/>
<item
android:id="@+id/menu_share"
android:orderInCategory="102"
android:showAsAction="ifRoom"
android:icon="@drawable/ic_action_share"
android:title="@string/menu_share"/>
<item
android:id="@+id/menu_about"
android:orderInCategory="103"
android:showAsAction="never"
android:title="@string/menu_about"/>
</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,3 @@
<resources>
<color name="light_gray">#66e0e0e0</color>
</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,71 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="action_settings">Settings</string>
<string name="app_name">Bluetooth LE Scanner</string>
<string name="connected">Connected</string>
<string name="disconnected">Disconnected</string>
<string name="invalid_device_data">Invalid Device Data!</string>
<string name="no_data">No data</string>
<string name="not_supported">Not supported</string>
<string name="off">Off</string>
<string name="on">On</string>
<string name="supported">Supported</string>
<string name="unknown">unknown</string>
<string name="unknown_characteristic">Unknown characteristic</string>
<string name="unknown_device">Unknown device</string>
<string name="unknown_service">Unknown service</string>
<!-- Formatters -->
<string name="formatter_meters">%sm</string>
<string name="formatter_db">%sdb</string>
<string name="formatter_item_count">Items: %s</string>
<!-- Menu items -->
<string name="menu_about">About</string>
<string name="menu_connect">Connect</string>
<string name="menu_disconnect">Disconnect</string>
<string name="menu_scan">Scan</string>
<string name="menu_stop">Stop</string>
<string name="menu_share">Share</string>
<!-- Export Email Strings -->
<string name="exporter_email_device_list_subject">Bluetooth LE Scan Results (%s)</string>
<string name="exporter_email_device_services_subject" formatted="false">Bluetooth LE Device GATT Results (%s, %s)</string>
<string name="exporter_email_device_list_body">Please find attached the scan results.</string>
<string name="exporter_email_device_list_picker_text">Please select your email client:</string>
<!-- Labels -->
<string name="about_dialog_text">This is a sample application using the Bluetooth LE Library.\n\nGithub: https://github.com/alt236/Bluetooth-LE-Library---Android\n\nCopyright: Alexandros Schillings</string>
<string name="header_device_info">Device Info</string>
<string name="header_ibeacon_data">iBeacon Data</string>
<string name="header_raw_ad_records">Raw Ad Records</string>
<string name="header_rssi_info">RSSI Info</string>
<string name="header_scan_record">Scan Record</string>
<string name="label_advertisement">Advertisement:</string>
<string name="label_as_array">As Array:</string>
<string name="label_as_string">As String:</string>
<string name="label_bluetooth_le_status">Bluetooth LE:</string>
<string name="label_bluetooth_status">Bluetooth:</string>
<string name="label_bonding_state">Bonding State:</string>
<string name="label_company_id">Company ID:</string>
<string name="label_data">Data:</string>
<string name="label_desc">Desc:</string>
<string name="label_device_address">Device address:</string>
<string name="label_device_class">Device Class:</string>
<string name="label_device_name">Device Name:</string>
<string name="label_distance">Distance:</string>
<string name="label_first_rssi">First RSSI:</string>
<string name="label_first_timestamp">First Timestamp:</string>
<string name="label_last_rssi">Last RSSI:</string>
<string name="label_last_timestamp">Last Timestamp:</string>
<string name="label_mac">MAC:</string>
<string name="label_major">Major:</string>
<string name="label_minor">Minor:</string>
<string name="label_rssi">RSSI:</string>
<string name="label_running_average_rssi">Running Average RSSI:</string>
<string name="label_state">State:</string>
<string name="label_tx_power">TX Power:</string>
<string name="label_uuid">UUID:</string>
</resources>
+40
View File
@@ -0,0 +1,40 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!--
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>
<style name="GridLayoutDataTextView" parent="android:Widget.Holo.Light.TextView">
<item name="android:textSize">12sp</item>
<item name="android:layout_weight">1</item>
<item name="android:ellipsize">none</item>
<item name="android:singleLine">false</item>
<item name="android:scrollHorizontally">false</item>
<item name="android:maxLines">100</item>
<item name="android:inputType">textMultiLine</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">match_parent</item>
</style>
<style name="GridLayoutDataTextViewMonospace" parent="@style/GridLayoutDataTextView">
<item name="android:typeface">monospace</item>
</style>
<style name="GridLayoutTitleTextView" parent="android:Widget.Holo.Light.TextView">
<item name="android:textSize">12sp</item>
</style>
</resources>