diff --git a/.idea/.name b/.idea/.name
deleted file mode 100644
index 55fafa8..0000000
--- a/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-bluetooth-le-library
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 9a8b7e5..96cc43e 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,7 +1,6 @@
-
@@ -12,6 +11,7 @@
+
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index f841ede..561ff88 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -5,7 +5,6 @@
-
+
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 198ad64..0000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index 3b31283..0000000
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index c1b4dde..a58deef 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,6 +3,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Android
+
+
+ Android > Lint > Correctness
+
+
+ Android > Lint > Performance
+
+
+ CorrectnessLintAndroid
+
+
+ General
+
+
+ LintAndroid
+
+
+
+
+ Android
+
+
+
+
+
@@ -13,26 +71,10 @@
-
+
-
-
-
-
- Android API 18 Platform
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Run_Android_Tests.xml b/.idea/runConfigurations/Run_Android_Tests.xml
deleted file mode 100644
index 1f32fb7..0000000
--- a/.idea/runConfigurations/Run_Android_Tests.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations/Run_JUnit_Tests.xml b/.idea/runConfigurations/Run_JUnit_Tests.xml
deleted file mode 100644
index 09ab733..0000000
--- a/.idea/runConfigurations/Run_JUnit_Tests.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations/Run_Sample_App.xml b/.idea/runConfigurations/Run_Sample_App.xml
deleted file mode 100644
index 8873b95..0000000
--- a/.idea/runConfigurations/Run_Sample_App.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 35eb1dd..94a25f7 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/Bluetooth-LE-Library---Android.iml b/Bluetooth-LE-Library---Android.iml
new file mode 100644
index 0000000..dcc5098
--- /dev/null
+++ b/Bluetooth-LE-Library---Android.iml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bluetooth-le-library.iml b/bluetooth-le-library.iml
index cf2c73f..6c7aa80 100644
--- a/bluetooth-le-library.iml
+++ b/bluetooth-le-library.iml
@@ -8,12 +8,12 @@
-
+
-
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 327b2ba..1951a2d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,16 +1,16 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
-ext.compileSdkVersion = 22
-ext.buildToolsVersion = "22.0.1"
+ext.compileSdkVersion = 24
+ext.buildToolsVersion = "24.0.1"
ext.minSdkVersion = 18
-ext.targetSdkVersion = 22
+ext.targetSdkVersion = 24
buildscript {
repositories {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.2.3'
+ classpath 'com.android.tools.build:gradle:2.2.3'
}
}
diff --git a/circle.yml b/circle.yml
new file mode 100644
index 0000000..23d13c5
--- /dev/null
+++ b/circle.yml
@@ -0,0 +1,7 @@
+ test:
+ override:
+ - (echo "Running JUnit tests!")
+ - ./gradlew test -PdisablePreDex
+ post:
+ - mkdir -p $CIRCLE_TEST_REPORTS/junit/
+ - find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \;
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 0c71e76..edae9ca 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Wed Apr 10 15:27:10 PDT 2013
+#Wed Aug 24 14:15:35 BST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
diff --git a/library/library.iml b/library/library.iml
index 7656b88..c0cbb2b 100644
--- a/library/library.iml
+++ b/library/library.iml
@@ -12,19 +12,19 @@
-
-
-
+
+ generateDebugSources
+
-
+
-
+
@@ -33,8 +33,16 @@
+
-
+
+
+
+
+
+
+
+
@@ -42,6 +50,7 @@
+
@@ -49,6 +58,7 @@
+
@@ -56,6 +66,15 @@
+
+
+
+
+
+
+
+
+
@@ -63,32 +82,27 @@
+
+
-
-
-
-
-
-
+
+
-
-
-
-
+
+
-
+
-
diff --git a/sample_app/build.gradle b/sample_app/build.gradle
index 38e6eb0..5cbefef 100644
--- a/sample_app/build.gradle
+++ b/sample_app/build.gradle
@@ -13,14 +13,27 @@ repositories {
maven {
url "https://repo.commonsware.com.s3.amazonaws.com"
}
+
+ maven {
+ url "https://s3.amazonaws.com/repo.commonsware.com"
+ }
+
+ maven {
+ url "https://dl.bintray.com/alt236/maven"
+ }
}
dependencies {
compile 'com.jakewharton:butterknife:7.0.1'
- compile 'com.android.support:appcompat-v7:22.2.0'
- compile 'com.commonsware.cwac:merge:1.1.0'
+ compile 'com.android.support:appcompat-v7:24.2.0'
+ compile 'com.android.support:recyclerview-v7:24.2.0'
+ compile 'com.anthonycr.grant:permissions:1.0'
+ compile 'uk.co.alt236:easycursor-android:1.0.0'
compile fileTree(include: ['*.jar'], dir: 'libs')
compile project(':library')
+
+ testCompile 'junit:junit:4.12'
+ testCompile 'org.mockito:mockito-all:1.9.5'
}
android {
diff --git a/sample_app/ic_launcher-web.png b/sample_app/ic_launcher-web.png
deleted file mode 100644
index 73c663b..0000000
Binary files a/sample_app/ic_launcher-web.png and /dev/null differ
diff --git a/sample_app/libs/EasyCursor-0.1.1.jar b/sample_app/libs/EasyCursor-0.1.1.jar
deleted file mode 100644
index 2e043b5..0000000
Binary files a/sample_app/libs/EasyCursor-0.1.1.jar and /dev/null differ
diff --git a/sample_app/sample_app.iml b/sample_app/sample_app.iml
index 756895c..b140503 100644
--- a/sample_app/sample_app.iml
+++ b/sample_app/sample_app.iml
@@ -12,9 +12,9 @@
-
-
-
+
+ generateDebugSources
+
@@ -23,7 +23,7 @@
-
+
@@ -32,8 +32,16 @@
+
-
+
+
+
+
+
+
+
+
@@ -41,6 +49,7 @@
+
@@ -48,6 +57,7 @@
+
@@ -55,6 +65,15 @@
+
+
+
+
+
+
+
+
+
@@ -62,41 +81,64 @@
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
-
-
+
+
+
+
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample_app/src/main/AndroidManifest.xml b/sample_app/src/main/AndroidManifest.xml
index 7546b22..7eafc25 100644
--- a/sample_app/src/main/AndroidManifest.xml
+++ b/sample_app/src/main/AndroidManifest.xml
@@ -1,38 +1,36 @@
-
+
-
-
+
+
+
+ android:required="false" />
-
+
-
+
-
-
-
+
+
+
+ android:enabled="true" />
\ No newline at end of file
diff --git a/sample_app/src/main/java/uk/co/alt236/btlescan/activities/DeviceDetailsActivity.java b/sample_app/src/main/java/uk/co/alt236/btlescan/activities/DeviceDetailsActivity.java
deleted file mode 100644
index 1e3e10f..0000000
--- a/sample_app/src/main/java/uk/co/alt236/btlescan/activities/DeviceDetailsActivity.java
+++ /dev/null
@@ -1,240 +0,0 @@
-package uk.co.alt236.btlescan.activities;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v7.app.AppCompatActivity;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.commonsware.cwac.merge.MergeAdapter;
-
-import java.util.Collection;
-import java.util.Locale;
-
-import butterknife.Bind;
-import butterknife.ButterKnife;
-import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
-import uk.co.alt236.bluetoothlelib.device.BluetoothService;
-import uk.co.alt236.bluetoothlelib.device.adrecord.AdRecord;
-import uk.co.alt236.bluetoothlelib.device.beacon.BeaconType;
-import uk.co.alt236.bluetoothlelib.device.beacon.BeaconUtils;
-import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.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.btlescan.R;
-import uk.co.alt236.btlescan.util.TimeFormatter;
-
-public class DeviceDetailsActivity extends AppCompatActivity {
- public static final String EXTRA_DEVICE = "extra_device";
- @Bind(android.R.id.list)
- protected ListView mList;
- @Nullable
- @Bind(android.R.id.empty)
- protected View mEmpty;
- private BluetoothLeDevice mDevice;
-
- private void appendAdRecordView(final MergeAdapter adapter, final String title, final 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(final MergeAdapter adapter, final 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 tvMajorClass = (TextView) lt.findViewById(R.id.deviceMajorClass);
- final TextView tvServices = (TextView) lt.findViewById(R.id.deviceServiceList);
- final TextView tvBondingState = (TextView) lt.findViewById(R.id.deviceBondingState);
-
- tvName.setText(device.getName());
- tvAddress.setText(device.getAddress());
- tvClass.setText(device.getBluetoothDeviceClassName());
- tvMajorClass.setText(device.getBluetoothDeviceMajorClassName());
- tvBondingState.setText(device.getBluetoothDeviceBondState());
-
- final String supportedServices;
- if(device.getBluetoothDeviceKnownSupportedServices().isEmpty()){
- supportedServices = getString(R.string.no_known_services);
- } else {
- final StringBuilder sb = new StringBuilder();
-
- for(final BluetoothService service : device.getBluetoothDeviceKnownSupportedServices()){
- if(sb.length() > 0){
- sb.append(", ");
- }
-
- sb.append(service);
- }
- supportedServices = sb.toString();
- }
-
- tvServices.setText(supportedServices);
-
- adapter.addView(lt);
- }
-
- private void appendHeader(final MergeAdapter adapter, final 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(final MergeAdapter adapter, final 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());
- 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(final MergeAdapter adapter, final 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(final MergeAdapter adapter, final byte[] data) {
- appendSimpleText(adapter, ByteUtils.byteArrayToHexString(data));
- }
-
- private void appendSimpleText(final MergeAdapter adapter, final 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(final double rssi) {
- return getString(R.string.formatter_db, String.valueOf(rssi));
- }
-
- private String formatRssi(final int rssi) {
- return getString(R.string.formatter_db, String.valueOf(rssi));
- }
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_details);
- ButterKnife.bind(this);
-
- mList.setEmptyView(mEmpty);
-
- mDevice = getIntent().getParcelableExtra(EXTRA_DEVICE);
-
- pupulateDetails(mDevice);
- }
-
- @Override
- public boolean onCreateOptionsMenu(final Menu menu) {
- getMenuInflater().inflate(R.menu.details, menu);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(final 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(final 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 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 = BeaconUtils.getBeaconType(device) == BeaconType.IBEACON;
- if (isIBeacon) {
- final IBeaconManufacturerData iBeaconData = new IBeaconManufacturerData(device);
- appendHeader(adapter, getString(R.string.header_ibeacon_data));
- appendIBeaconInfo(adapter, iBeaconData);
- }
-
- }
- mList.setAdapter(adapter);
- }
-
- private static String formatTime(final long time) {
- return TimeFormatter.getIsoDateTime(time);
- }
-
- private static String hexEncode(final int integer) {
- return "0x" + Integer.toHexString(integer).toUpperCase(Locale.US);
- }
-}
diff --git a/sample_app/src/main/java/uk/co/alt236/btlescan/adapters/LeDeviceListAdapter.java b/sample_app/src/main/java/uk/co/alt236/btlescan/adapters/LeDeviceListAdapter.java
deleted file mode 100644
index 97cbdbc..0000000
--- a/sample_app/src/main/java/uk/co/alt236/btlescan/adapters/LeDeviceListAdapter.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package uk.co.alt236.btlescan.adapters;
-
-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;
-
-import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
-import uk.co.alt236.bluetoothlelib.device.beacon.BeaconType;
-import uk.co.alt236.bluetoothlelib.device.beacon.BeaconUtils;
-import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconDevice;
-import uk.co.alt236.btlescan.R;
-import uk.co.alt236.btlescan.util.Constants;
-import uk.co.alt236.easycursor.objectcursor.EasyObjectCursor;
-
-// Adapter for holding devices found through scanning.
-public class LeDeviceListAdapter extends SimpleCursorAdapter {
- private final LayoutInflater mInflator;
- private final Activity mActivity;
-
- public LeDeviceListAdapter(final Activity activity, final EasyObjectCursor 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 getCursor() {
- return ((EasyObjectCursor) super.getCursor());
- }
-
- @Override
- public BluetoothLeDevice getItem(final int i) {
- return getCursor().getItem(i);
- }
-
- @Override
- public long getItemId(final int i) {
- return i;
- }
-
- @Override
- public View getView(final int i, View view, final ViewGroup viewGroup) {
- final 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.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 (BeaconUtils.getBeaconType(device) == BeaconType.IBEACON) {
- 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;
- }
-
-}
\ No newline at end of file
diff --git a/sample_app/src/main/java/uk/co/alt236/btlescan/containers/BluetoothLeDeviceStore.java b/sample_app/src/main/java/uk/co/alt236/btlescan/containers/BluetoothLeDeviceStore.java
index 8b99033..c591de7 100644
--- a/sample_app/src/main/java/uk/co/alt236/btlescan/containers/BluetoothLeDeviceStore.java
+++ b/sample_app/src/main/java/uk/co/alt236/btlescan/containers/BluetoothLeDeviceStore.java
@@ -1,39 +1,26 @@
package uk.co.alt236.btlescan.containers;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
+import android.support.annotation.NonNull;
-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.beacon.BeaconType;
-import uk.co.alt236.bluetoothlelib.device.beacon.BeaconUtils;
-import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconDevice;
-import uk.co.alt236.bluetoothlelib.util.ByteUtils;
-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;
public class BluetoothLeDeviceStore {
+ private static final BluetoothLeDeviceComparator DEFAULT_COMPARATOR = new BluetoothLeDeviceComparator();
private final Map mDeviceMap;
-
public BluetoothLeDeviceStore() {
mDeviceMap = new HashMap<>();
}
- public void addDevice(final BluetoothLeDevice device) {
+ public void addDevice(@NonNull final BluetoothLeDevice device) {
if (mDeviceMap.containsKey(device.getAddress())) {
mDeviceMap.get(device.getAddress()).updateRssiReading(device.getTimestamp(), device.getRssi());
} else {
@@ -45,137 +32,42 @@ public class BluetoothLeDeviceStore {
mDeviceMap.clear();
}
+ public int getSize() {
+ return mDeviceMap.size();
+ }
+
+ @NonNull
public EasyObjectCursor getDeviceCursor() {
+ return getDeviceCursor(DEFAULT_COMPARATOR);
+ }
+
+ @NonNull
+ public EasyObjectCursor getDeviceCursor(@NonNull Comparator comparator) {
return new EasyObjectCursor<>(
BluetoothLeDevice.class,
- getDeviceList(),
+ getDeviceList(comparator),
"address");
}
+ @NonNull
public List getDeviceList() {
+ return getDeviceList(DEFAULT_COMPARATOR);
+ }
+
+ @NonNull
+ public List getDeviceList(@NonNull Comparator comparator) {
final List methodResult = new ArrayList<>(mDeviceMap.values());
- Collections.sort(methodResult, new Comparator() {
-
- @Override
- public int compare(final BluetoothLeDevice arg0, final BluetoothLeDevice arg1) {
- return arg0.getAddress().compareToIgnoreCase(arg1.getAddress());
- }
- });
+ Collections.sort(methodResult, comparator);
return methodResult;
}
- private String getListAsCsv() {
- final List 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');
+ private static class BluetoothLeDeviceComparator implements Comparator {
- for (final 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 = BeaconUtils.getBeaconType(device) == BeaconType.IBEACON;
- 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');
+ @Override
+ public int compare(final BluetoothLeDevice arg0, final BluetoothLeDevice arg1) {
+ return arg0.getAddress().compareTo(arg1.getAddress());
}
-
- return sb.toString();
- }
-
- public void shareDataAsEmail(final 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 (final IOException e) {
- e.printStackTrace();
- }
- }
-
- private static FileWriter generateFile(final File file, final String contents) {
- FileWriter writer = null;
- try {
- writer = new FileWriter(file);
- writer.append(contents);
- writer.flush();
-
- } catch (final IOException e) {
- e.printStackTrace();
- } finally {
- try {
- writer.close();
- } catch (final IOException e) {
- e.printStackTrace();
- }
- }
- return writer;
}
}
diff --git a/sample_app/src/main/java/uk/co/alt236/btlescan/services/BluetoothLeService.java b/sample_app/src/main/java/uk/co/alt236/btlescan/services/BluetoothLeService.java
index bc33610..2320a13 100644
--- a/sample_app/src/main/java/uk/co/alt236/btlescan/services/BluetoothLeService.java
+++ b/sample_app/src/main/java/uk/co/alt236/btlescan/services/BluetoothLeService.java
@@ -38,22 +38,21 @@ import java.util.List;
* given Bluetooth LE device.
*/
public class BluetoothLeService extends Service {
- 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";
+ public final static String ACTION_GATT_CONNECTED = BluetoothLeService.class.getName() + ".ACTION_GATT_CONNECTED";
+ public final static String ACTION_GATT_CONNECTING = BluetoothLeService.class.getName() + ".ACTION_GATT_CONNECTING";
+ public final static String ACTION_GATT_DISCONNECTED = BluetoothLeService.class.getName() + ".ACTION_GATT_DISCONNECTED";
+ public final static String ACTION_GATT_SERVICES_DISCOVERED = BluetoothLeService.class.getName() + ".ACTION_GATT_SERVICES_DISCOVERED";
+ public final static String ACTION_DATA_AVAILABLE = BluetoothLeService.class.getName() + ".ACTION_DATA_AVAILABLE";
+ public final static String EXTRA_DATA_RAW = BluetoothLeService.class.getName() + ".EXTRA_DATA_RAW";
+ public final static String EXTRA_UUID_CHAR = BluetoothLeService.class.getName() + ".EXTRA_UUID_CHAR";
private final static String TAG = BluetoothLeService.class.getSimpleName();
- private static final int STATE_DISCONNECTED = 0;
- private static final int STATE_CONNECTING = 1;
- private static final int STATE_CONNECTED = 2;
private final IBinder mBinder = new LocalBinder();
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private String mBluetoothDeviceAddress;
private BluetoothGatt mBluetoothGatt;
- private int mConnectionState = STATE_DISCONNECTED;
+ private State mConnectionState = State.DISCONNECTED;
+
// Implements callback methods for GATT events that the app cares about. For example,
// connection change and services discovered.
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@@ -63,28 +62,35 @@ public class BluetoothLeService extends Service {
}
@Override
- public void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
+ public void onCharacteristicRead(final BluetoothGatt gatt,
+ final BluetoothGattCharacteristic characteristic,
+ final int status) {
+
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
@Override
- public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
- final String intentAction;
+ public void onConnectionStateChange(final BluetoothGatt gatt,
+ final int status,
+ final int newState) {
+
+ Log.d(TAG, "onConnectionStateChange: status=" + status + ", newState=" + newState);
+
if (newState == BluetoothProfile.STATE_CONNECTED) {
- intentAction = ACTION_GATT_CONNECTED;
- mConnectionState = STATE_CONNECTED;
- broadcastUpdate(intentAction);
+ setConnectionState(State.CONNECTED, true);
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;
+ // Make sure we tidy up. On certain devices reusing a Gatt after a disconnection
+ // can cause problems.
+ disconnect();
+
+ setConnectionState(State.DISCONNECTED, true);
Log.i(TAG, "Disconnected from GATT server.");
- broadcastUpdate(intentAction);
}
}
@@ -138,37 +144,73 @@ public class BluetoothLeService extends Service {
* callback.
*/
public boolean connect(final String address) {
+
+ final boolean retVal;
if (mBluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
- return false;
- }
+ retVal = false;
- // Previously connected device. Try to reconnect.
- if (mBluetoothDeviceAddress != null
+ // Previously connected device. Try to reconnect.
+ } else 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;
+ Log.d(TAG, "Connection attempt OK.");
+ setConnectionState(State.CONNECTING, true);
+ retVal = true;
} else {
- return false;
+ Log.w(TAG, "Connection attempt failed.");
+ setConnectionState(State.DISCONNECTED, true);
+ retVal = false;
+ }
+ } else {
+
+ final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
+ if (device == null) {
+ Log.w(TAG, "Device not found. Unable to connect.");
+ retVal = false;
+ } else {
+ // We want to directly connect to the device, so we are setting the autoConnect
+ // parameter to false.
+
+ Log.d(TAG, "Trying to create a new connection.");
+ mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
+ mBluetoothDeviceAddress = address;
+ setConnectionState(State.CONNECTING, true);
+ retVal = true;
}
}
- final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
- if (device == null) {
- Log.w(TAG, "Device not found. Unable to connect.");
- return false;
+ return retVal;
+ }
+
+ private synchronized void setConnectionState(final State newState, final boolean broadCast) {
+ Log.i(TAG, "Setting internal state to " + newState);
+ mConnectionState = newState;
+
+ final String broadcastAction;
+
+ switch (newState) {
+ case CONNECTED:
+ broadcastAction = ACTION_GATT_CONNECTED;
+ break;
+ case CONNECTING:
+ broadcastAction = ACTION_GATT_CONNECTING;
+ break;
+ case DISCONNECTED:
+ broadcastAction = ACTION_GATT_DISCONNECTED;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown state: " + newState);
}
- // 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;
+
+ if (broadCast) {
+ Log.i(TAG, "Broadcasting " + broadcastAction);
+ broadcastUpdate(broadcastAction);
+ }
+
}
/**
@@ -183,6 +225,9 @@ public class BluetoothLeService extends Service {
return;
}
mBluetoothGatt.disconnect();
+
+ // Reusing a Gatt after disconnecting can cause problems
+ mBluetoothGatt = null;
}
/**
@@ -270,4 +315,10 @@ public class BluetoothLeService extends Service {
return BluetoothLeService.this;
}
}
+
+ private enum State {
+ DISCONNECTED,
+ CONNECTING,
+ CONNECTED
+ }
}
\ No newline at end of file
diff --git a/sample_app/src/main/java/uk/co/alt236/btlescan/ui/common/Navigation.java b/sample_app/src/main/java/uk/co/alt236/btlescan/ui/common/Navigation.java
new file mode 100644
index 0000000..95846d6
--- /dev/null
+++ b/sample_app/src/main/java/uk/co/alt236/btlescan/ui/common/Navigation.java
@@ -0,0 +1,50 @@
+package uk.co.alt236.btlescan.ui.common;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.support.v4.app.ActivityCompat;
+
+import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
+import uk.co.alt236.btlescan.R;
+import uk.co.alt236.btlescan.ui.control.DeviceControlActivity;
+import uk.co.alt236.btlescan.ui.details.DeviceDetailsActivity;
+
+public class Navigation {
+
+ private final Activity mActivity;
+
+ public Navigation(final Activity activity) {
+ mActivity = activity;
+ }
+
+ public void openDetailsActivity(final BluetoothLeDevice device) {
+ final Intent intent = DeviceDetailsActivity.createIntent(mActivity, device);
+
+ startActivity(intent);
+ }
+
+ public void startControlActivity(final BluetoothLeDevice device) {
+ final Intent intent = DeviceControlActivity.createIntent(mActivity, device);
+
+ startActivity(intent);
+ }
+
+ public void shareFileViaEmail(final Uri fileUri, final String[] recipient, final String subject, final String message) {
+ final Intent intent = new Intent(Intent.ACTION_SEND);
+
+ intent.setType("plain/text");
+ intent.putExtra(Intent.EXTRA_STREAM, fileUri);
+ intent.putExtra(Intent.EXTRA_EMAIL, recipient);
+ intent.putExtra(Intent.EXTRA_SUBJECT, subject);
+ intent.putExtra(Intent.EXTRA_TEXT, message);
+
+ startActivity(Intent.createChooser(intent,
+ mActivity.getString(R.string.exporter_email_device_list_picker_text)));
+ }
+
+
+ private void startActivity(final Intent intent) {
+ ActivityCompat.startActivity(mActivity, intent, null);
+ }
+}
diff --git a/sample_app/src/main/java/uk/co/alt236/btlescan/ui/common/recyclerview/BaseRecyclerViewAdapter.java b/sample_app/src/main/java/uk/co/alt236/btlescan/ui/common/recyclerview/BaseRecyclerViewAdapter.java
new file mode 100644
index 0000000..4181345
--- /dev/null
+++ b/sample_app/src/main/java/uk/co/alt236/btlescan/ui/common/recyclerview/BaseRecyclerViewAdapter.java
@@ -0,0 +1,69 @@
+package uk.co.alt236.btlescan.ui.common.recyclerview;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public abstract class BaseRecyclerViewAdapter extends RecyclerView.Adapter> {
+
+ private final List mItemList;
+ private final RecyclerViewBinderCore mCore;
+
+ public BaseRecyclerViewAdapter(final RecyclerViewBinderCore core,
+ final List items) {
+ mItemList = new ArrayList<>();
+ mCore = core;
+ mItemList.addAll(items);
+ }
+
+ public BaseRecyclerViewAdapter(final RecyclerViewBinderCore core) {
+ this(core, new ArrayList());
+ }
+
+ @Override
+ public BaseViewHolder extends RecyclerViewItem> onCreateViewHolder(final ViewGroup parent,
+ final int viewType) {
+ return mCore.create(parent, viewType);
+ }
+
+ @Override
+ public void onBindViewHolder(final BaseViewHolder extends RecyclerViewItem> holder,
+ final int position) {
+
+ final int viewType = getItemViewType(position);
+ final BaseViewBinder extends RecyclerViewItem> binder = mCore.getBinder(viewType);
+ bind(binder, holder, getItem(position));
+ }
+
+ @Override
+ public int getItemCount() {
+ return mItemList.size();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return mCore.getViewType(getItem(position));
+ }
+
+ public RecyclerViewItem getItem(final int position) {
+ return mItemList.get(position);
+ }
+
+ public void setData(Collection extends RecyclerViewItem> data) {
+
+ mItemList.clear();
+ mItemList.addAll(data);
+ notifyDataSetChanged();
+ }
+
+ private static void bind(final BaseViewBinder binder,
+ final BaseViewHolder> holder,
+ final RecyclerViewItem item) {
+
+ //noinspection unchecked
+ binder.bind((BaseViewHolder) holder, (T) item);
+ }
+}
diff --git a/sample_app/src/main/java/uk/co/alt236/btlescan/ui/common/recyclerview/BaseViewBinder.java b/sample_app/src/main/java/uk/co/alt236/btlescan/ui/common/recyclerview/BaseViewBinder.java
new file mode 100644
index 0000000..ef4b23c
--- /dev/null
+++ b/sample_app/src/main/java/uk/co/alt236/btlescan/ui/common/recyclerview/BaseViewBinder.java
@@ -0,0 +1,20 @@
+package uk.co.alt236.btlescan.ui.common.recyclerview;
+
+import android.content.Context;
+
+public abstract class BaseViewBinder {
+
+ private final Context mContext;
+
+ public BaseViewBinder(final Context context) {
+ mContext = context;
+ }
+
+ public abstract void bind(BaseViewHolder holder, T item);
+
+ public abstract boolean canBind(RecyclerViewItem item);
+
+ protected Context getContext() {
+ return mContext;
+ }
+}
diff --git a/sample_app/src/main/java/uk/co/alt236/btlescan/ui/common/recyclerview/BaseViewHolder.java b/sample_app/src/main/java/uk/co/alt236/btlescan/ui/common/recyclerview/BaseViewHolder.java
new file mode 100644
index 0000000..b8617c6
--- /dev/null
+++ b/sample_app/src/main/java/uk/co/alt236/btlescan/ui/common/recyclerview/BaseViewHolder.java
@@ -0,0 +1,18 @@
+package uk.co.alt236.btlescan.ui.common.recyclerview;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+public abstract class BaseViewHolder extends RecyclerView.ViewHolder {
+
+ private final View mItemView;
+
+ public BaseViewHolder(final View itemView) {
+ super(itemView);
+ mItemView = itemView;
+ }
+
+ public View getView() {
+ return mItemView;
+ }
+}
diff --git a/sample_app/src/main/java/uk/co/alt236/btlescan/ui/common/recyclerview/RecyclerViewBinderCore.java b/sample_app/src/main/java/uk/co/alt236/btlescan/ui/common/recyclerview/RecyclerViewBinderCore.java
new file mode 100644
index 0000000..acadb5c
--- /dev/null
+++ b/sample_app/src/main/java/uk/co/alt236/btlescan/ui/common/recyclerview/RecyclerViewBinderCore.java
@@ -0,0 +1,99 @@
+package uk.co.alt236.btlescan.ui.common.recyclerview;
+
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class RecyclerViewBinderCore {
+ public static final int INVALID_VIEWTYPE = -1;
+
+ private static final String TAG = RecyclerViewBinderCore.class.getSimpleName();
+ private final List>> mViewHolderClasses;
+ private final List> mViewBinders;
+ private final List mLayoutIds;
+
+ public RecyclerViewBinderCore() {
+ mViewBinders = new ArrayList<>();
+ mViewHolderClasses = new ArrayList<>();
+ mLayoutIds = new ArrayList<>();
+ }
+
+ public void clear() {
+ mViewBinders.clear();
+ mViewHolderClasses.clear();
+ mLayoutIds.clear();
+ }
+
+ public void add(
+ final BaseViewBinder binder,
+ final Class extends BaseViewHolder> viewHolder,
+ final int layoutId) {
+
+ mViewBinders.add(binder);
+ mViewHolderClasses.add(viewHolder);
+ mLayoutIds.add(layoutId);
+ }
+
+ public BaseViewHolder extends RecyclerViewItem> create(ViewGroup parent, final int viewType) {
+ if (viewType == INVALID_VIEWTYPE) {
+ throw new IllegalArgumentException("Invalid viewType: " + viewType);
+ }
+
+ final Class> clazz = mViewHolderClasses.get(viewType);
+ final int layoutId = mLayoutIds.get(viewType);
+ final View itemView = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
+
+ return (BaseViewHolder extends RecyclerViewItem>) instantiate(clazz, itemView);
+ }
+
+ public int getViewType(final T item) {
+ int result = INVALID_VIEWTYPE;
+ int count = 0;
+
+ for (final BaseViewBinder extends RecyclerViewItem> binder : mViewBinders) {
+
+ if (binder.canBind(item)) {
+ result = count;
+ break;
+ }
+
+ count++;
+ }
+
+ if (result == INVALID_VIEWTYPE) {
+ Log.w(TAG, "Could not get viewType for " + item);
+ }
+
+ return result;
+ }
+
+ public BaseViewBinder extends RecyclerViewItem> getBinder(int viewType) {
+ if (viewType == INVALID_VIEWTYPE) {
+ throw new IllegalArgumentException("Invalid viewType: " + viewType);
+ }
+
+ return mViewBinders.get(viewType);
+ }
+
+ @SuppressWarnings("TryWithIdenticalCatches")
+ private static Object instantiate(
+ final Class> clazz, View parentView) {
+ try {
+ final Constructor> constructor
+ = clazz.getDeclaredConstructors()[0];
+ return constructor.newInstance(parentView);
+ } catch (InstantiationException e) {
+ throw new IllegalStateException(e);
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException(e);
+ } catch (InvocationTargetException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git a/sample_app/src/main/java/uk/co/alt236/btlescan/ui/common/recyclerview/RecyclerViewItem.java b/sample_app/src/main/java/uk/co/alt236/btlescan/ui/common/recyclerview/RecyclerViewItem.java
new file mode 100644
index 0000000..32b5df9
--- /dev/null
+++ b/sample_app/src/main/java/uk/co/alt236/btlescan/ui/common/recyclerview/RecyclerViewItem.java
@@ -0,0 +1,4 @@
+package uk.co.alt236.btlescan.ui.common.recyclerview;
+
+public interface RecyclerViewItem {
+}
diff --git a/sample_app/src/main/java/uk/co/alt236/btlescan/activities/DeviceControlActivity.java b/sample_app/src/main/java/uk/co/alt236/btlescan/ui/control/DeviceControlActivity.java
similarity index 58%
rename from sample_app/src/main/java/uk/co/alt236/btlescan/activities/DeviceControlActivity.java
rename to sample_app/src/main/java/uk/co/alt236/btlescan/ui/control/DeviceControlActivity.java
index 5e3753b..bce6683 100644
--- a/sample_app/src/main/java/uk/co/alt236/btlescan/activities/DeviceControlActivity.java
+++ b/sample_app/src/main/java/uk/co/alt236/btlescan/ui/control/DeviceControlActivity.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package uk.co.alt236.btlescan.activities;
+package uk.co.alt236.btlescan.ui.control;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
@@ -26,6 +26,7 @@ import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
+import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
@@ -35,10 +36,7 @@ import android.widget.ExpandableListView;
import android.widget.SimpleExpandableListAdapter;
import android.widget.TextView;
-import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import butterknife.Bind;
import butterknife.ButterKnife;
@@ -55,10 +53,8 @@ import uk.co.alt236.btlescan.services.BluetoothLeService;
* Bluetooth LE API.
*/
public class DeviceControlActivity extends AppCompatActivity {
- public static final String EXTRA_DEVICE = "extra_device";
+ private static final String EXTRA_DEVICE = DeviceControlActivity.class.getName() + ".EXTRA_DEVICE";
private final static String TAG = DeviceControlActivity.class.getSimpleName();
- private static final String LIST_NAME = "NAME";
- private static final String LIST_UUID = "UUID";
@Bind(R.id.gatt_services_list)
protected ExpandableListView mGattServicesList;
@Bind(R.id.connection_state)
@@ -71,9 +67,10 @@ public class DeviceControlActivity extends AppCompatActivity {
protected TextView mDataAsString;
@Bind(R.id.data_as_array)
protected TextView mDataAsArray;
+ private Exporter mExporter;
private BluetoothGattCharacteristic mNotifyCharacteristic;
private BluetoothLeService mBluetoothLeService;
- private List> mGattCharacteristics = new ArrayList<>();
+
// 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
@@ -81,28 +78,30 @@ public class DeviceControlActivity extends AppCompatActivity {
private final ExpandableListView.OnChildClickListener servicesListClickListner = new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(final ExpandableListView parent, final View v, final int groupPosition, final int childPosition, final 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);
+ final GattDataAdapterFactory.GattDataAdapter adapter =
+ (GattDataAdapterFactory.GattDataAdapter) parent.getExpandableListAdapter();
+
+ final BluetoothGattCharacteristic characteristic =
+ adapter.getBluetoothGattCharacteristic(groupPosition, 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;
}
- if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
- mNotifyCharacteristic = characteristic;
- mBluetoothLeService.setCharacteristicNotification(characteristic, true);
- }
- return true;
+ mBluetoothLeService.readCharacteristic(characteristic);
}
- return false;
+ if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
+ mNotifyCharacteristic = characteristic;
+ mBluetoothLeService.setCharacteristicNotification(characteristic, true);
+ }
+ return true;
}
};
- private String mDeviceAddress;
+
// Code to manage Service lifecycle.
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
@@ -113,7 +112,7 @@ public class DeviceControlActivity extends AppCompatActivity {
finish();
}
// Automatically connects to the device upon successful start-up initialization.
- mBluetoothLeService.connect(mDeviceAddress);
+ mBluetoothLeService.connect(mDevice.getAddress());
}
@Override
@@ -121,8 +120,9 @@ public class DeviceControlActivity extends AppCompatActivity {
mBluetoothLeService = null;
}
};
- private String mDeviceName;
- private boolean mConnected = false;
+ private BluetoothLeDevice mDevice;
+ private State mCurrentState = State.DISCONNECTED;
+
private String mExportString;
// Handles various events fired by the Service.
// ACTION_GATT_CONNECTED: connected to a GATT server.
@@ -135,14 +135,16 @@ public class DeviceControlActivity extends AppCompatActivity {
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
- mConnected = true;
- updateConnectionState(R.string.connected);
+ updateConnectionState(State.CONNECTED);
invalidateOptionsMenu();
} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
- mConnected = false;
- updateConnectionState(R.string.disconnected);
- invalidateOptionsMenu();
clearUI();
+ updateConnectionState(State.DISCONNECTED);
+ invalidateOptionsMenu();
+ } else if (BluetoothLeService.ACTION_GATT_CONNECTING.equals(action)) {
+ clearUI();
+ updateConnectionState(State.CONNECTING);
+ invalidateOptionsMenu();
} else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
// Show all the supported services and characteristics on the user interface.
displayGattServices(mBluetoothLeService.getSupportedGattServices());
@@ -160,6 +162,7 @@ public class DeviceControlActivity extends AppCompatActivity {
};
private void clearUI() {
+ mExportString = null;
mGattServicesList.setAdapter((SimpleExpandableListAdapter) null);
mGattUUID.setText(R.string.no_data);
mGattUUIDDesc.setText(R.string.no_data);
@@ -172,125 +175,34 @@ public class DeviceControlActivity extends AppCompatActivity {
// on the UI.
private void displayGattServices(final List gattServices) {
if (gattServices == null) return;
- generateExportString(gattServices);
+ mExportString = mExporter.generateExportString(
+ mDevice.getName(),
+ mDevice.getAddress(),
+ gattServices);
- String uuid = null;
- final String unknownServiceString = getResources().getString(R.string.unknown_service);
- final String unknownCharaString = getResources().getString(R.string.unknown_characteristic);
- final List