Migrated project from Eclipse to Android Studio
@@ -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);
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 445 B |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 548 B |
|
After Width: | Height: | Size: 824 B |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 911 B |
|
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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||