30 Commits

Author SHA1 Message Date
40a181cc3e Optional refresh call through reflection
Add a method to call refresh on GATT through reflection and set
auto-reconnect to true.
2017-03-13 15:19:43 +00:00
Alexandros Schillings
8871fc5d20 Merge pull request #25 from alt236/release/app_v1_1_1
Preparing for App relase v1.1.1
2017-02-03 12:32:30 +00:00
Alexandros Schillings
e5ed6f1dbb Preparing for App relase v1.1.1 2017-02-03 12:16:08 +00:00
Alexandros Schillings
e838cacad1 Merge pull request #24 from alt236/bugfix/dont-ask-for-location-permission-pre-API23
App no longer tries to ask for COARSE_LOCATION permission pre API 23 …
2017-02-03 12:12:48 +00:00
Alexandros Schillings
ebc44c7537 App no longer tries to ask for COARSE_LOCATION permission pre API 23 as it is not needed 2017-02-03 12:09:20 +00:00
Alexandros Schillings
f2f9ac38f4 Bumped app version, updated manifest and README 2017-01-24 16:48:28 +00:00
Alexandros Schillings
be2583ce4e Merge pull request #21 from alt236/app-material-redisign-and-optimisations
App material redisign and optimisations
2017-01-19 17:24:06 +00:00
Alexandros Schillings
0d1085730b Bumped tools to 2.2.3 2017-01-19 17:15:49 +00:00
Alexandros Schillings
47a4c124f6 Added unit tests for BluetoothLeDeviceStore.java 2017-01-19 17:11:21 +00:00
Alexandros Schillings
81d984efac The dound device list is now implemented as a recyclerview. 2017-01-17 17:31:53 +00:00
Alexandros Schillings
a7312ad51c Added circle.yml 2017-01-17 17:07:16 +00:00
Alexandros Schillings
9ba9f2728b Updated app icon (again) 2016-09-04 20:50:37 +01:00
Alexandros Schillings
3a234b8080 Fixed crash when selecting GATT service 2016-09-04 20:42:48 +01:00
Alexandros Schillings
1d3dd85eeb Updated the app icon 2016-08-31 20:35:59 +01:00
Alexandros Schillings
1639e1a22d Updated share and bluetooth icons 2016-08-31 20:21:31 +01:00
Alexandros Schillings
ef5b84387f Fixed bug where the device class name was returned instead of the major class name 2016-08-31 20:19:18 +01:00
Alexandros Schillings
6f092344f5 Fixed CSV sharing 2016-08-31 19:56:09 +01:00
Alexandros Schillings
7f15ea3d1e We now have a "connecting" state in the ControlActivity. 2016-08-31 19:09:38 +01:00
Alexandros Schillings
727f2d8953 Moved the list adapter creation out of DeviceControlActivity 2016-08-31 18:04:17 +01:00
Alexandros Schillings
b54a56b15b Updated app colours and margins between sections 2016-08-31 17:51:24 +01:00
Alexandros Schillings
e43bfc1b82 Added navigation class to abstract activity launching 2016-08-31 17:50:31 +01:00
Alexandros Schillings
e9c2f0d486 The extras in BluetoothLeService are now properly unique 2016-08-31 13:16:14 +01:00
Alexandros Schillings
65b597ab29 Now using EasyCursor off maven instead of the bundled jar 2016-08-30 18:14:26 +01:00
Alexandros Schillings
92d6ee5241 Removed merge adapter, details screen now uses a recyclerview 2016-08-30 18:05:44 +01:00
Alexandros Schillings
54d0caf6c8 Rearranged app packages and started splitting classes 2016-08-24 16:23:22 +01:00
Alexandros Schillings
ec33fd7618 Materialized the UI, added runtme permissions support, now compiling against 24 2016-08-24 14:39:26 +01:00
Alexandros Schillings
c6cece6acc Merge pull request #13 from ravidsrk/patch-1
Added code syntax highlighting to Readme
2016-02-20 20:51:00 +00:00
Ravindra Kumar
1647ca1ec2 Added code syntax highlighting to Readme 2016-02-20 21:29:31 +05:30
Alexandros Schillings
112bc00054 Merge pull request #12 from orthographic-pedant/spell_check/advertisement
Fixed typographical error, changed advertisment to advertisement in README.
2015-10-01 10:13:09 +01:00
orthographic-pedant
f483a0e24a Fixed typographical error, changed advertisment to advertisement in README. 2015-09-30 17:08:24 -04:00
125 changed files with 2676 additions and 1105 deletions

1
.idea/.name generated
View File

@@ -1 +0,0 @@
bluetooth-le-library

2
.idea/compiler.xml generated
View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<option name="DEFAULT_COMPILER" value="Javac" />
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
@@ -12,6 +11,7 @@
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">

2
.idea/gradle.xml generated
View File

@@ -5,7 +5,6 @@
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="1.8" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
@@ -13,6 +12,7 @@
<option value="$PROJECT_DIR$/sample_app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>

View File

@@ -1,16 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<option name="myLocal" value="true" />
<inspection_tool class="GroovyVariableCanBeFinal" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false">
<option name="loggerClassName" value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger" />
<option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
</inspection_tool>
<inspection_tool class="MissingDeprecatedAnnotation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MissingOverrideAnnotation" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreObjectMethods" value="true" />
<option name="ignoreAnonymousClassMethods" value="false" />
</inspection_tool>
</profile>
</component>

View File

@@ -1,7 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Project Default" />
<option name="USE_PROJECT_PROFILE" value="true" />
<version value="1.0" />
</settings>
</component>

76
.idea/misc.xml generated
View File

@@ -3,6 +3,64 @@
<component name="EntryPointsManager">
<entry_points version="2.0" />
</component>
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectInspectionProfilesVisibleTreeState">
<entry key="Project Default">
<profile-state>
<expanded-state>
<State>
<id />
</State>
<State>
<id>Android</id>
</State>
<State>
<id>Android &gt; Lint &gt; Correctness</id>
</State>
<State>
<id>Android &gt; Lint &gt; Performance</id>
</State>
<State>
<id>CorrectnessLintAndroid</id>
</State>
<State>
<id>General</id>
</State>
<State>
<id>LintAndroid</id>
</State>
</expanded-state>
<selected-state>
<State>
<id>Android</id>
</State>
</selected-state>
</profile-state>
</entry>
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
@@ -13,26 +71,10 @@
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
<component name="masterDetails">
<states>
<state key="ProjectJDKs.UI">
<settings>
<last-edited>Android API 18 Platform</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
</states>
</component>
</project>

12
.idea/runConfigurations.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@@ -1,27 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run Android Tests" type="AndroidTestRunConfigurationType" factoryName="Android Tests">
<module name="library" />
<option name="TESTING_TYPE" value="0" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="" />
<option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
<option name="USE_LAST_SELECTED_DEVICE" value="true" />
<option name="PREFERRED_AVD" value="" />
<option name="USE_COMMAND_LINE" value="true" />
<option name="COMMAND_LINE" value="" />
<option name="WIPE_USER_DATA" value="false" />
<option name="DISABLE_BOOT_ANIMATION" value="false" />
<option name="NETWORK_SPEED" value="full" />
<option name="NETWORK_LATENCY" value="none" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="true" />
<option name="FILTER_LOGCAT_AUTOMATICALLY" value="true" />
<option name="SELECTED_MATRIX_CONFIGURATION_ID" value="-1" />
<option name="SELECTED_CLOUD_PROJECT_ID" value="Please select a project..." />
<option name="IS_VALID_CLOUD_SELECTION" value="false" />
<option name="INVALID_CLOUD_SELECTION_ERROR" value="Matrix configuration not specified" />
<method />
</configuration>
</component>

View File

@@ -1,23 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run JUnit Tests" type="JUnit" factoryName="JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="library" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" value="" />
<option name="PACKAGE_NAME" value="uk.co.alt236.bluetoothlelib" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="moduleWithDependencies" />
</option>
<envs />
<patterns />
<method />
</configuration>
</component>

View File

@@ -1,26 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run Sample App" type="AndroidRunConfigurationType" factoryName="Android Application">
<module name="sample_app" />
<option name="ACTIVITY_CLASS" value="" />
<option name="MODE" value="default_activity" />
<option name="DEPLOY" value="true" />
<option name="ARTIFACT_NAME" value="" />
<option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
<option name="USE_LAST_SELECTED_DEVICE" value="true" />
<option name="PREFERRED_AVD" value="" />
<option name="USE_COMMAND_LINE" value="true" />
<option name="COMMAND_LINE" value="" />
<option name="WIPE_USER_DATA" value="false" />
<option name="DISABLE_BOOT_ANIMATION" value="false" />
<option name="NETWORK_SPEED" value="full" />
<option name="NETWORK_LATENCY" value="none" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="true" />
<option name="FILTER_LOGCAT_AUTOMATICALLY" value="true" />
<option name="SELECTED_MATRIX_CONFIGURATION_ID" value="-1" />
<option name="SELECTED_CLOUD_PROJECT_ID" value="Please select a project..." />
<option name="IS_VALID_CLOUD_SELECTION" value="false" />
<option name="INVALID_CLOUD_SELECTION_ERROR" value="Matrix configuration not specified" />
<method />
</configuration>
</component>

2
.idea/vcs.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="Bluetooth-LE-Library---Android" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="java-gradle" name="Java-Gradle">
<configuration>
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
<option name="BUILDABLE" value="false" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
</content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -11,12 +11,14 @@ It also offers:
This will only work on devices with Android 4.3 (API Level 18) and above.
Sample app available on the [Play Store](https://play.google.com/store/apps/details?id=uk.co.alt236.btlescan)
<a href='https://play.google.com/store/apps/details?id=uk.co.alt236.btlescan&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'>
<img alt='Get it on Google Play' height=100 src='https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png'/>
</a>
## Including the Library in Your Project
This project is available as an artifact for use with Gradle. To use that, add the following blocks to your build.gradle file:
```
```groovy
repositories {
maven {
url "https://dl.bintray.com/alt236/maven"
@@ -35,7 +37,7 @@ In the `onLeScan()` method of your `BluetoothAdapter.LeScanCallback()` create a
For example:
```
```java
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
@@ -73,7 +75,7 @@ Once you have created a device, you can access the following methods:
**Note:** The Running Average RSSI is not updated automatically (i.e. the library does not monitor on its own in the background). To add another measurement, you need to call `updateRssiReading(long timestamp, int rssiReading)`.
### Accessing the Advertisment (Ad) Records
### Accessing the Advertisement (Ad) Records
Once you've created a BluetoothLe device, you can access the AdRecord store via the `leDevice.getAdRecordStore()`. Once you have the AdRecordStore you can use the following methods:
@@ -88,7 +90,7 @@ They are also declared as constants in `AdRecord.java`.
You can check if a device is an iBeacon by using `BeaconUtils.getBeaconType(BluetootLeDevice device)`. Once you have confirmed that it is, you can create a new IBeaconDevice via the IBeaconDevice constructor.
Example Flow:
```
```java
final BluetoothLeDevice device = ... // A generic BLE device
if (BeaconUtils.getBeaconType(device) == BeaconType.IBEACON) {
@@ -115,32 +117,37 @@ You can also lookup values and convert them to human friendly strings:
* `CompanyIdentifierResolver.getCompanyName(int companyId, String fallback)`: Will try to resolve a Company identifier to the company name
* `GattAttributeResolver.getAttributeName(String uuid, String fallback)`: Will try to convert a UUID to its name.
**Note:** The data can be found as ODS (Open Office Spreadsheets) in the documents folder.
**Note:** The data can be found as ODS (Open Office Spreadsheets) in the documents folder.
## Library Changelog
* v0.0.1
* v0.0.1
* First public release
* v0.0.2:
* v0.0.2:
* Attempting to create an iBeaconDevice from a device which is not an iBeacon will now throw an IllegalArgumentException exception.
* Fixed a ConcurrentModificationException on getRunningAverageRssi()
* Added some Estimote UUIDs
* v1.0.0:
* Migrated project to Android Studio/ gradle
* Note that the API has slightly changed in this version.
* We now use the more generic `BeaconUtils.getBeaconType()` method instead of `IBeaconUtils.isThisAnIBeacon()`
* Fix for [issue 5](https://github.com/alt236/Bluetooth-LE-Library---Android/issues/5)
* Migrated project to Android Studio/ gradle
* Note that the API has slightly changed in this version.
* We now use the more generic `BeaconUtils.getBeaconType()` method instead of `IBeaconUtils.isThisAnIBeacon()`
* Fix for [issue 5](https://github.com/alt236/Bluetooth-LE-Library---Android/issues/5)
* Fix for [issue 9](https://github.com/alt236/Bluetooth-LE-Library---Android/issues/9)
## Sample Application Changelog
* v0.0.1
* First public release
* v0.0.2:
* Can now export scanned devices as a CSV file.
* v0.0.3:
* UI Refresh.
* v0.0.1
* First public release
* v0.0.2:
* Can now export scanned devices as a CSV file.
* v0.0.3:
* UI Refresh.
* v1.0.0:
* Migrated project to Android Studio/ gradle
* Using version v1.0.0 of the library project
* Migrated project to Android Studio/ gradle
* Using version v1.0.0 of the library project
* v1.1.0:
* App refactor and materialisation.
* Added runtime permissions.
* v1.1.1:
* Fix for [issue 23](https://github.com/alt236/Bluetooth-LE-Library---Android/issues/23)
## Permission Explanation
You will need the following permissions to access the Bluetooth Hardware
@@ -148,18 +155,15 @@ You will need the following permissions to access the Bluetooth Hardware
* `android.permission.BLUETOOTH`
* `android.permission.BLUETOOTH_ADMIN`
In addition one of the following is needed from API 23 and above to scan for BT LE devices:
* `android.permission.ACCESS_COARSE_LOCATION`
* `android.permission.ACCESS_FINE_LOCATION `
## TODO
* Tidy up Javadoc. There is quite a lot of it that is template
* Add parsers for common Ad Records.
## Sample App Screenshots
![screenshot1](https://github.com/alt236/Bluetooth-LE-Library---Android/raw/master/screenshots/screenshot_1.png)
![screenshot2](https://github.com/alt236/Bluetooth-LE-Library---Android/raw/master/screenshots/screenshot_2.png)
![screenshot3](https://github.com/alt236/Bluetooth-LE-Library---Android/raw/master/screenshots/screenshot_3.png)
![screenshot4](https://github.com/alt236/Bluetooth-LE-Library---Android/raw/master/screenshots/screenshot_4.png)
## Links
* Github: [https://github.com/alt236/Bluetooth-LE-Library---Android]()
@@ -169,9 +173,10 @@ Author: [Alexandros Schillings](https://github.com/alt236).
* The Accuracy calculation algorithm was taken from: http://stackoverflow.com/questions/20416218/understanding-ibeacon-distancing
* The AdRecord parser was taken from: https://github.com/devunwired/accessory-samples
* The sample application has been adapted from Android's Bluetooth LE example
* Google Play and the Google Play logo are trademarks of Google Inc.
All logos are the property of their respective owners.
The code in this project is licensed under the Apache Software License 2.0.
Copyright (c) 2014 Alexandros Schillings.
Copyright (c) 2014-2017 Alexandros Schillings.

View File

@@ -8,12 +8,12 @@
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
</content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -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'
}
}

7
circle.yml Normal file
View File

@@ -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/ \;

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -12,19 +12,19 @@
<option name="SELECTED_TEST_ARTIFACT" value="_unit_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugUnitTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugUnitTestSources" />
<afterSyncTasks>
<task>generateDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
<option name="LIBRARY_PROJECT" value="true" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="false">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
<exclude-output />
@@ -33,8 +33,16 @@
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
@@ -42,6 +50,7 @@
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
@@ -49,6 +58,7 @@
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
@@ -56,6 +66,7 @@
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
@@ -63,34 +74,38 @@
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" />
<orderEntry type="jdk" jdkName="Android API 24 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" scope="TEST" name="objenesis-1.0" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="mockable-android-22" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="hamcrest-core-1.3" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="junit-4.12" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="mockito-core-1.9.5" level="project" />
<orderEntry type="library" exported="" name="android-android-24" level="project" />
</component>
</module>

View File

@@ -1,9 +1,9 @@
apply plugin: 'com.android.application'
final int versionMajor = 1
final int versionMinor = 0
final int versionPatch = 0
final int androidVersionCode = 5
final int versionMinor = 1
final int versionPatch = 1
final int androidVersionCode = 7
final int targetSdk = rootProject.targetSdkVersion;
final int minSdkRed = rootProject.minSdkVersion;
@@ -13,14 +13,28 @@ 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 fileTree(include: ['*.jar'], dir: 'libs')
compile project(':library')
compile 'com.jakewharton:butterknife:7.0.1'
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'
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-all:1.9.5'
}
android {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

View File

@@ -12,9 +12,9 @@
<option name="SELECTED_TEST_ARTIFACT" value="_unit_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugUnitTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugUnitTestSources" />
<afterSyncTasks>
<task>generateDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
@@ -23,7 +23,7 @@
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="false">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
<exclude-output />
@@ -32,8 +32,16 @@
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
@@ -41,6 +49,7 @@
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
@@ -48,6 +57,7 @@
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
@@ -55,6 +65,15 @@
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
@@ -62,41 +81,64 @@
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/builds" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/22.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/22.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.commonsware.cwac/merge/1.1.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.commonsware.cwac/sacklist/1.0.2/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/animated-vector-drawable/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-compat/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-core-ui/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-core-utils/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-fragment/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-media-compat/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-vector-drawable/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.anthonycr.grant/permissions/1.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-runtime-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-verifier" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-resources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-support" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/reload-dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/restart-dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" />
<orderEntry type="jdk" jdkName="Android API 24 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" scope="TEST" name="mockable-android-22" level="project" />
<orderEntry type="library" exported="" name="butterknife-7.0.1" level="project" />
<orderEntry type="library" exported="" name="merge-1.1.0" level="project" />
<orderEntry type="library" exported="" name="sacklist-1.0.2" level="project" />
<orderEntry type="library" exported="" name="EasyCursor-0.1.1" level="project" />
<orderEntry type="library" exported="" name="support-annotations-22.2.0" level="project" />
<orderEntry type="library" exported="" name="support-v4-22.2.0" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-22.2.0" level="project" />
<orderEntry type="library" exported="" name="support-media-compat-24.2.0" level="project" />
<orderEntry type="library" exported="" name="recyclerview-v7-24.2.0" level="project" />
<orderEntry type="library" exported="" name="support-compat-24.2.0" level="project" />
<orderEntry type="library" exported="" name="support-v4-24.2.0" level="project" />
<orderEntry type="library" exported="" name="support-core-ui-24.2.0" level="project" />
<orderEntry type="library" exported="" name="support-core-utils-24.2.0" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-24.2.0" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="mockito-all-1.9.5" level="project" />
<orderEntry type="library" exported="" name="support-fragment-24.2.0" level="project" />
<orderEntry type="library" exported="" name="support-annotations-24.2.0" level="project" />
<orderEntry type="library" exported="" name="support-vector-drawable-24.2.0" level="project" />
<orderEntry type="library" exported="" name="animated-vector-drawable-24.2.0" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="hamcrest-core-1.3" level="project" />
<orderEntry type="library" exported="" name="permissions-1.0" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="junit-4.12" level="project" />
<orderEntry type="library" exported="" name="easycursor-android-1.0.0" level="project" />
<orderEntry type="module" module-name="library" exported="" />
<orderEntry type="library" exported="" name="android-android-24" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="objenesis-1.0" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="mockito-core-1.9.5" level="project" />
</component>
</module>

View File

@@ -1,38 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="uk.co.alt236.btlescan"
xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="uk.co.alt236.btlescan">
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="false"/>
android:required="false" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name="uk.co.alt236.btlescan.activities.MainActivity"
android:name=".ui.main.MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
<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"/>
<activity android:name=".ui.details.DeviceDetailsActivity" />
<activity android:name=".ui.control.DeviceControlActivity" />
<service
android:name="uk.co.alt236.btlescan.services.BluetoothLeService"
android:enabled="true"/>
android:enabled="true" />
</application>
</manifest>

View File

@@ -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<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 = 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);
}
}

View File

@@ -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<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(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;
}
}

View File

@@ -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<String, BluetoothLeDevice> 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<BluetoothLeDevice> getDeviceCursor() {
return getDeviceCursor(DEFAULT_COMPARATOR);
}
@NonNull
public EasyObjectCursor<BluetoothLeDevice> getDeviceCursor(@NonNull Comparator<BluetoothLeDevice> comparator) {
return new EasyObjectCursor<>(
BluetoothLeDevice.class,
getDeviceList(),
getDeviceList(comparator),
"address");
}
@NonNull
public List<BluetoothLeDevice> getDeviceList() {
return getDeviceList(DEFAULT_COMPARATOR);
}
@NonNull
public List<BluetoothLeDevice> getDeviceList(@NonNull Comparator<BluetoothLeDevice> comparator) {
final List<BluetoothLeDevice> methodResult = new ArrayList<>(mDeviceMap.values());
Collections.sort(methodResult, new Comparator<BluetoothLeDevice>() {
@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<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');
private static class BluetoothLeDeviceComparator implements Comparator<BluetoothLeDevice> {
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;
}
}

View File

@@ -31,6 +31,7 @@ import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import java.lang.reflect.Method;
import java.util.List;
/**
@@ -38,22 +39,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 +63,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 +145,89 @@ 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, true, mGattCallback);
//boolean v = refreshDeviceCache(mBluetoothGatt);
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 boolean refreshDeviceCache(BluetoothGatt gatt){
try {
BluetoothGatt localBluetoothGatt = gatt;
Method localMethod = localBluetoothGatt.getClass().getMethod("refresh", new Class[0]);
if (localMethod != null) {
boolean bool = ((Boolean) localMethod.invoke(localBluetoothGatt, new Object[0])).booleanValue();
return bool;
}
}
// 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;
catch (Exception localException) {
Log.e(TAG, "An exception occured while refreshing device");
}
return false;
}
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);
}
if (broadCast) {
Log.i(TAG, "Broadcasting " + broadcastAction);
broadcastUpdate(broadcastAction);
}
}
/**
@@ -183,6 +242,9 @@ public class BluetoothLeService extends Service {
return;
}
mBluetoothGatt.disconnect();
// Reusing a Gatt after disconnecting can cause problems
mBluetoothGatt = null;
}
/**
@@ -270,4 +332,10 @@ public class BluetoothLeService extends Service {
return BluetoothLeService.this;
}
}
private enum State {
DISCONNECTED,
CONNECTING,
CONNECTED
}
}

View File

@@ -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);
}
}

View File

@@ -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<BaseViewHolder<? extends RecyclerViewItem>> {
private final List<RecyclerViewItem> mItemList;
private final RecyclerViewBinderCore mCore;
public BaseRecyclerViewAdapter(final RecyclerViewBinderCore core,
final List<RecyclerViewItem> items) {
mItemList = new ArrayList<>();
mCore = core;
mItemList.addAll(items);
}
public BaseRecyclerViewAdapter(final RecyclerViewBinderCore core) {
this(core, new ArrayList<RecyclerViewItem>());
}
@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 <T extends RecyclerViewItem> void bind(final BaseViewBinder<T> binder,
final BaseViewHolder<?> holder,
final RecyclerViewItem item) {
//noinspection unchecked
binder.bind((BaseViewHolder<T>) holder, (T) item);
}
}

View File

@@ -0,0 +1,20 @@
package uk.co.alt236.btlescan.ui.common.recyclerview;
import android.content.Context;
public abstract class BaseViewBinder<T extends RecyclerViewItem> {
private final Context mContext;
public BaseViewBinder(final Context context) {
mContext = context;
}
public abstract void bind(BaseViewHolder<T> holder, T item);
public abstract boolean canBind(RecyclerViewItem item);
protected Context getContext() {
return mContext;
}
}

View File

@@ -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<T extends RecyclerViewItem> extends RecyclerView.ViewHolder {
private final View mItemView;
public BaseViewHolder(final View itemView) {
super(itemView);
mItemView = itemView;
}
public View getView() {
return mItemView;
}
}

View File

@@ -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<Class<? extends BaseViewHolder<? extends RecyclerViewItem>>> mViewHolderClasses;
private final List<BaseViewBinder<? extends RecyclerViewItem>> mViewBinders;
private final List<Integer> mLayoutIds;
public RecyclerViewBinderCore() {
mViewBinders = new ArrayList<>();
mViewHolderClasses = new ArrayList<>();
mLayoutIds = new ArrayList<>();
}
public void clear() {
mViewBinders.clear();
mViewHolderClasses.clear();
mLayoutIds.clear();
}
public <T extends RecyclerViewItem> void add(
final BaseViewBinder<T> binder,
final Class<? extends BaseViewHolder<T>> 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 <T extends RecyclerViewItem> 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);
}
}
}

View File

@@ -0,0 +1,4 @@
package uk.co.alt236.btlescan.ui.common.recyclerview;
public interface RecyclerViewItem {
}

View File

@@ -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<List<BluetoothGattCharacteristic>> 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<BluetoothGattService> 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<Map<String, String>> gattServiceData = new ArrayList<>();
final List<List<Map<String, String>>> gattCharacteristicData = new ArrayList<>();
mGattCharacteristics = new ArrayList<>();
// Loops through available GATT Services.
for (final BluetoothGattService gattService : gattServices) {
final Map<String, String> currentServiceData = new HashMap<>();
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<>();
final List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
final List<BluetoothGattCharacteristic> charas = new ArrayList<>();
// Loops through available Characteristics.
for (final BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
charas.add(gattCharacteristic);
final Map<String, String> currentCharaData = new HashMap<>();
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);
final GattDataAdapterFactory.GattDataAdapter adapter = GattDataAdapterFactory.createAdapter(this, gattServices);
mGattServicesList.setAdapter(adapter);
invalidateOptionsMenu();
}
private void generateExportString(final 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 (final 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();
}
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gatt_services);
final Intent intent = getIntent();
final BluetoothLeDevice device = intent.getParcelableExtra(EXTRA_DEVICE);
mDeviceName = device.getName();
mDeviceAddress = device.getAddress();
mDevice = intent.getParcelableExtra(EXTRA_DEVICE);
ButterKnife.bind(this);
// Sets up UI references.
((TextView) findViewById(R.id.device_address)).setText(mDeviceAddress);
((TextView) findViewById(R.id.device_address)).setText(mDevice.getAddress());
mGattServicesList.setOnChildClickListener(servicesListClickListner);
getSupportActionBar().setTitle(mDeviceName);
getSupportActionBar().setTitle(mDevice.getName());
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mExporter = new Exporter(this);
final Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
}
@@ -298,12 +210,26 @@ public class DeviceControlActivity extends AppCompatActivity {
@Override
public boolean onCreateOptionsMenu(final 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);
switch (mCurrentState) {
case DISCONNECTED:
menu.findItem(R.id.menu_connect).setVisible(true);
menu.findItem(R.id.menu_disconnect).setVisible(false);
menu.findItem(R.id.menu_refresh).setActionView(null);
break;
case CONNECTING:
menu.findItem(R.id.menu_connect).setVisible(false);
menu.findItem(R.id.menu_disconnect).setVisible(false);
menu.findItem(R.id.menu_refresh).setActionView(R.layout.actionbar_progress_indeterminate);
break;
case CONNECTED:
menu.findItem(R.id.menu_connect).setVisible(false);
menu.findItem(R.id.menu_disconnect).setVisible(true);
menu.findItem(R.id.menu_refresh).setActionView(null);
break;
default:
throw new IllegalStateException("Don't know how to handle: " + mCurrentState);
}
if (mExportString == null) {
@@ -326,7 +252,7 @@ public class DeviceControlActivity extends AppCompatActivity {
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_connect:
mBluetoothLeService.connect(mDeviceAddress);
mBluetoothLeService.connect(mDevice.getAddress());
return true;
case R.id.menu_disconnect:
mBluetoothLeService.disconnect();
@@ -336,7 +262,10 @@ public class DeviceControlActivity extends AppCompatActivity {
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);
final String subject = getString(
R.string.exporter_email_device_services_subject,
mDevice.getName(),
mDevice.getAddress());
intent.setType("text/plain");
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject);
@@ -362,31 +291,41 @@ public class DeviceControlActivity extends AppCompatActivity {
super.onResume();
registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
if (mBluetoothLeService != null) {
final boolean result = mBluetoothLeService.connect(mDeviceAddress);
final boolean result = mBluetoothLeService.connect(mDevice.getAddress());
Log.d(TAG, "Connect request result=" + result);
}
}
private void updateConnectionState(final int resourceId) {
private void updateConnectionState(final State state) {
mCurrentState = state;
runOnUiThread(new Runnable() {
@Override
public void run() {
final int colourId;
final int resId;
switch (resourceId) {
case R.string.connected:
switch (state) {
case CONNECTED:
colourId = android.R.color.holo_green_dark;
resId = R.string.connected;
break;
case R.string.disconnected:
case DISCONNECTED:
colourId = android.R.color.holo_red_dark;
resId = R.string.disconnected;
break;
case CONNECTING:
colourId = android.R.color.black;
resId = R.string.connecting;
break;
default:
colourId = android.R.color.black;
resId = 0;
break;
}
mConnectionState.setText(resourceId);
mConnectionState.setTextColor(getResources().getColor(colourId));
mConnectionState.setText(resId);
mConnectionState.setTextColor(ContextCompat.getColor(DeviceControlActivity.this, colourId));
}
});
}
@@ -397,6 +336,7 @@ public class DeviceControlActivity extends AppCompatActivity {
intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);
intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE);
intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTING);
return intentFilter;
}
@@ -407,4 +347,16 @@ public class DeviceControlActivity extends AppCompatActivity {
return string;
}
}
public static Intent createIntent(final Context context, final BluetoothLeDevice device) {
final Intent intent = new Intent(context, DeviceControlActivity.class);
intent.putExtra(DeviceControlActivity.EXTRA_DEVICE, device);
return intent;
}
private enum State {
DISCONNECTED,
CONNECTING,
CONNECTED
}
}

View File

@@ -0,0 +1,70 @@
package uk.co.alt236.btlescan.ui.control;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import java.util.List;
import uk.co.alt236.bluetoothlelib.resolvers.GattAttributeResolver;
import uk.co.alt236.btlescan.R;
/*package*/ class Exporter {
private final Context mContext;
public Exporter(final Context context) {
mContext = context.getApplicationContext();
}
public String generateExportString(final String deviceName,
final String deviceAddress,
final List<BluetoothGattService> gattServices) {
final String unknownServiceString = mContext.getString(R.string.unknown_service);
final String unknownCharaString = mContext.getString(R.string.unknown_characteristic);
final StringBuilder exportBuilder = new StringBuilder();
exportBuilder.append("Device Name: ");
exportBuilder.append(deviceName);
exportBuilder.append('\n');
exportBuilder.append("Device Address: ");
exportBuilder.append(deviceAddress);
exportBuilder.append('\n');
exportBuilder.append('\n');
exportBuilder.append("Services:");
exportBuilder.append("--------------------------");
exportBuilder.append('\n');
String uuid = null;
for (final 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');
return exportBuilder.toString();
}
}

View File

@@ -0,0 +1,94 @@
package uk.co.alt236.btlescan.ui.control;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import android.widget.SimpleExpandableListAdapter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import uk.co.alt236.bluetoothlelib.resolvers.GattAttributeResolver;
import uk.co.alt236.btlescan.R;
/*package*/ class GattDataAdapterFactory {
private static final String LIST_NAME = "NAME";
private static final String LIST_UUID = "UUID";
public static GattDataAdapter createAdapter(final Context context,
final List<BluetoothGattService> gattServices) {
final String unknownServiceString = context.getString(R.string.unknown_service);
final String unknownCharaString = context.getString(R.string.unknown_characteristic);
final List<Map<String, String>> gattServiceData = new ArrayList<>();
final List<List<Map<String, String>>> gattCharacteristicData = new ArrayList<>();
final List<List<BluetoothGattCharacteristic>> fullGattCharacteristics = new ArrayList<>();
// Loops through available GATT Services.
String uuid;
for (final BluetoothGattService gattService : gattServices) {
final Map<String, String> currentServiceData = new HashMap<>();
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<>();
final List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
final List<BluetoothGattCharacteristic> charas = new ArrayList<>();
// Loops through available Characteristics.
for (final BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
charas.add(gattCharacteristic);
final Map<String, String> currentCharaData = new HashMap<>();
uuid = gattCharacteristic.getUuid().toString();
currentCharaData.put(LIST_NAME, GattAttributeResolver.getAttributeName(uuid, unknownCharaString));
currentCharaData.put(LIST_UUID, uuid);
gattCharacteristicGroupData.add(currentCharaData);
}
fullGattCharacteristics.add(charas);
gattCharacteristicData.add(gattCharacteristicGroupData);
}
return new GattDataAdapter(
context,
fullGattCharacteristics,
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}
);
}
public static class GattDataAdapter extends SimpleExpandableListAdapter {
private final List<List<BluetoothGattCharacteristic>> mGattCharacteristics;
public GattDataAdapter(Context context,
List<List<BluetoothGattCharacteristic>> gattCharacteristics,
List<Map<String, String>> groupData,
int groupLayout, String[] groupFrom,
int[] groupTo,
List<List<Map<String, String>>> childData,
int childLayout,
String[] childFrom,
int[] childTo) {
super(context, groupData, groupLayout, groupFrom, groupTo, childData, childLayout, childFrom, childTo);
mGattCharacteristics = gattCharacteristics;
}
public BluetoothGattCharacteristic getBluetoothGattCharacteristic(final int groupPosition, final int childPosition) {
return mGattCharacteristics.get(groupPosition).get(childPosition);
}
}
}

View File

@@ -0,0 +1,25 @@
package uk.co.alt236.btlescan.ui.details;
import java.util.List;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseRecyclerViewAdapter;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewBinderCore;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
/*package*/ class DetailsRecyclerAdapter extends BaseRecyclerViewAdapter {
public DetailsRecyclerAdapter(RecyclerViewBinderCore core, List<RecyclerViewItem> items) {
super(core, items);
}
// private static List<RecyclerViewItem> validate(RecyclerViewBinderCore core, List<RecyclerViewItem> items) {
// final List<RecyclerViewItem> retVal = new ArrayList<>();
//
// for (final RecyclerViewItem item : items) {
// if (core.getViewType(item) != RecyclerViewBinderCore.INVALID_VIEWTYPE) {
// retVal.add(item);
// }
// }
//
// return retVal;
// }
}

View File

@@ -0,0 +1,125 @@
package uk.co.alt236.btlescan.ui.details;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuItem;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
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.util.ByteUtils;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.Navigation;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewBinderCore;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.AdRecordItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.DeviceInfoItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.HeaderItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.IBeaconItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.RssiItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.TextItem;
public class DeviceDetailsActivity extends AppCompatActivity {
private static final String EXTRA_DEVICE = DeviceDetailsActivity.class.getName() + ".EXTRA_DEVICE";
private static final int LAYOUT_ID = R.layout.activity_details;
@Bind(R.id.recycler)
protected RecyclerView mRecycler;
private BluetoothLeDevice mDevice;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(LAYOUT_ID);
ButterKnife.bind(this);
mRecycler.setLayoutManager(new LinearLayoutManager(this));
mDevice = getIntent().getParcelableExtra(EXTRA_DEVICE);
getSupportActionBar().setTitle(mDevice.getName());
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
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:
new Navigation(this).startControlActivity(mDevice);
return true;
case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
private void pupulateDetails(final BluetoothLeDevice device) {
final DetailsRecyclerAdapter detailsRecyclerAdapter;
final List<RecyclerViewItem> list = new ArrayList<>();
if (device == null) {
list.add(new HeaderItem(getString(R.string.header_device_info)));
list.add(new TextItem(getString(R.string.invalid_device_data)));
} else {
list.add(new HeaderItem(getString(R.string.header_device_info)));
list.add(new DeviceInfoItem(device));
list.add(new HeaderItem(getString(R.string.header_rssi_info)));
list.add(new RssiItem(device));
list.add(new HeaderItem(getString(R.string.header_scan_record)));
list.add(new TextItem(ByteUtils.byteArrayToHexString(device.getScanRecord())));
final Collection<AdRecord> adRecords = device.getAdRecordStore().getRecordsAsCollection();
if (adRecords.size() > 0) {
list.add(new HeaderItem(getString(R.string.header_raw_ad_records)));
for (final AdRecord record : adRecords) {
final String title = "#" + record.getType() + " " + record.getHumanReadableType();
list.add(new AdRecordItem(title, record));
}
}
final boolean isIBeacon = BeaconUtils.getBeaconType(device) == BeaconType.IBEACON;
if (isIBeacon) {
final IBeaconManufacturerData iBeaconData = new IBeaconManufacturerData(device);
list.add(new HeaderItem(getString(R.string.header_ibeacon_data)));
list.add(new IBeaconItem(iBeaconData));
}
}
final RecyclerViewBinderCore core = RecyclerViewCoreFactory.create(this);
mRecycler.setAdapter(new DetailsRecyclerAdapter(core, list));
}
public static Intent createIntent(Context context, BluetoothLeDevice device) {
final Intent intent = new Intent(context, DeviceDetailsActivity.class);
intent.putExtra(DeviceDetailsActivity.EXTRA_DEVICE, device);
return intent;
}
}

View File

@@ -0,0 +1,35 @@
package uk.co.alt236.btlescan.ui.details;
import android.content.Context;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewBinderCore;
import uk.co.alt236.btlescan.ui.details.recyclerview.binder.AdRecordBinder;
import uk.co.alt236.btlescan.ui.details.recyclerview.binder.DeviceInfoBinder;
import uk.co.alt236.btlescan.ui.details.recyclerview.binder.HeaderBinder;
import uk.co.alt236.btlescan.ui.details.recyclerview.binder.IBeaconBinder;
import uk.co.alt236.btlescan.ui.details.recyclerview.binder.RssiBinder;
import uk.co.alt236.btlescan.ui.details.recyclerview.binder.TextBinder;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.AdRecordHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.DeviceInfoHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.HeaderHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.IBeaconHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.RssiInfoHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.TextHolder;
/*protected*/ final class RecyclerViewCoreFactory {
public static RecyclerViewBinderCore create(final Context context) {
final RecyclerViewBinderCore core = new RecyclerViewBinderCore();
core.add(new TextBinder(context), TextHolder.class, R.layout.list_item_view_textview);
core.add(new HeaderBinder(context), HeaderHolder.class, R.layout.list_item_view_header);
core.add(new AdRecordBinder(context), AdRecordHolder.class, R.layout.list_item_view_adrecord);
core.add(new RssiBinder(context), RssiInfoHolder.class, R.layout.list_item_view_rssi_info);
core.add(new DeviceInfoBinder(context), DeviceInfoHolder.class, R.layout.list_item_view_device_info);
core.add(new IBeaconBinder(context), IBeaconHolder.class, R.layout.list_item_view_ibeacon_details);
return core;
}
}

View File

@@ -0,0 +1,38 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.binder;
import android.content.Context;
import uk.co.alt236.bluetoothlelib.util.ByteUtils;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewBinder;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.AdRecordHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.AdRecordItem;
public class AdRecordBinder extends BaseViewBinder<AdRecordItem> {
public AdRecordBinder(Context context) {
super(context);
}
@Override
public void bind(BaseViewHolder<AdRecordItem> holder, AdRecordItem item) {
final AdRecordHolder actualHolder = (AdRecordHolder) holder;
actualHolder.getTitleTextView().setText(item.getTitle());
actualHolder.getStringTextView().setText(
getContext().getString(R.string.formatter_single_quoted_string,
item.getDataAsString()));
actualHolder.getArrayTextView().setText(
getContext().getString(R.string.formatter_single_quoted_string,
ByteUtils.byteArrayToHexString(item.getData())));
}
@Override
public boolean canBind(RecyclerViewItem item) {
return item instanceof AdRecordItem;
}
}

View File

@@ -0,0 +1,57 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.binder;
import android.content.Context;
import uk.co.alt236.bluetoothlelib.device.BluetoothService;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewBinder;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.DeviceInfoHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.DeviceInfoItem;
public class DeviceInfoBinder extends BaseViewBinder<DeviceInfoItem> {
public DeviceInfoBinder(Context context) {
super(context);
}
@Override
public void bind(BaseViewHolder<DeviceInfoItem> holder, DeviceInfoItem item) {
final DeviceInfoHolder actualHolder = (DeviceInfoHolder) holder;
actualHolder.getName().setText(item.getName());
actualHolder.getAddress().setText(item.getAddress());
actualHolder.getDeviceClass().setText(item.getBluetoothDeviceClassName());
actualHolder.getMajorClass().setText(item.getBluetoothDeviceMajorClassName());
actualHolder.getBondingState().setText(item.getBluetoothDeviceBondState());
actualHolder.getServices().setText(createSupportedDevicesString(item));
}
private String createSupportedDevicesString(DeviceInfoItem item) {
final String retVal;
if (item.getBluetoothDeviceKnownSupportedServices().isEmpty()) {
retVal = getContext().getString(R.string.no_known_services);
} else {
final StringBuilder sb = new StringBuilder();
for (final BluetoothService service : item.getBluetoothDeviceKnownSupportedServices()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(service);
}
retVal = sb.toString();
}
return retVal;
}
@Override
public boolean canBind(RecyclerViewItem item) {
return item instanceof DeviceInfoItem;
}
}

View File

@@ -0,0 +1,27 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.binder;
import android.content.Context;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewBinder;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.HeaderHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.HeaderItem;
public class HeaderBinder extends BaseViewBinder<HeaderItem> {
public HeaderBinder(Context context) {
super(context);
}
@Override
public void bind(BaseViewHolder<HeaderItem> holder, HeaderItem item) {
final HeaderHolder actualHolder = (HeaderHolder) holder;
actualHolder.getTextView().setText(item.getText());
}
@Override
public boolean canBind(RecyclerViewItem item) {
return item instanceof HeaderItem;
}
}

View File

@@ -0,0 +1,66 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.binder;
import android.content.Context;
import java.util.Locale;
import uk.co.alt236.bluetoothlelib.resolvers.CompanyIdentifierResolver;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewBinder;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.IBeaconHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.IBeaconItem;
import uk.co.alt236.btlescan.util.TimeFormatter;
public class IBeaconBinder extends BaseViewBinder<IBeaconItem> {
private static final String STRING_FORMAT = "%s (%s)";
public IBeaconBinder(Context context) {
super(context);
}
private static String formatTime(final long time) {
return TimeFormatter.getIsoDateTime(time);
}
private static String getWithHexEncode(final String first, final int value) {
return createLine(first, hexEncode(value));
}
private static String getWithHexEncode(final int value) {
return createLine(String.valueOf(value), hexEncode(value));
}
private static String createLine(final String first, final String second) {
return String.format(Locale.US, STRING_FORMAT, first, second);
}
private static String hexEncode(final int integer) {
return "0x" + Integer.toHexString(integer).toUpperCase(Locale.US);
}
@Override
public void bind(BaseViewHolder<IBeaconItem> holder, IBeaconItem item) {
final IBeaconHolder actualHolder = (IBeaconHolder) holder;
final String companyName = CompanyIdentifierResolver.getCompanyName(
item.getCompanyIdentifier(),
getContext().getString(R.string.unknown));
actualHolder.getCompanyId().setText(
getWithHexEncode(companyName, item.getCompanyIdentifier()));
actualHolder.getAdvert().setText(getWithHexEncode(item.getIBeaconAdvertisement()));
actualHolder.getUuid().setText(item.getUuid());
actualHolder.getMajor().setText(getWithHexEncode(item.getMajor()));
actualHolder.getMinor().setText(getWithHexEncode(item.getMinor()));
actualHolder.getTxPower().setText(getWithHexEncode(item.getCalibratedTxPower()));
}
@Override
public boolean canBind(RecyclerViewItem item) {
return item instanceof IBeaconItem;
}
}

View File

@@ -0,0 +1,46 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.binder;
import android.content.Context;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewBinder;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.RssiInfoHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.RssiItem;
import uk.co.alt236.btlescan.util.TimeFormatter;
public class RssiBinder extends BaseViewBinder<RssiItem> {
public RssiBinder(Context context) {
super(context);
}
private static String formatTime(final long time) {
return TimeFormatter.getIsoDateTime(time);
}
@Override
public void bind(BaseViewHolder<RssiItem> holder, RssiItem item) {
final RssiInfoHolder actualHolder = (RssiInfoHolder) holder;
actualHolder.getFirstTimestamp().setText(formatTime(item.getFirstTimestamp()));
actualHolder.getFirstRssi().setText(formatRssi(item.getFirstRssi()));
actualHolder.getLastTimestamp().setText(formatTime(item.getTimestamp()));
actualHolder.getLastRssi().setText(formatRssi(item.getRssi()));
actualHolder.getRunningAverageRssi().setText(formatRssi(item.getRunningAverageRssi()));
}
@Override
public boolean canBind(RecyclerViewItem item) {
return item instanceof RssiItem;
}
private String formatRssi(final double rssi) {
return getContext().getString(R.string.formatter_db, String.valueOf(rssi));
}
private String formatRssi(final int rssi) {
return getContext().getString(R.string.formatter_db, String.valueOf(rssi));
}
}

View File

@@ -0,0 +1,27 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.binder;
import android.content.Context;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewBinder;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.TextHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.TextItem;
public class TextBinder extends BaseViewBinder<TextItem> {
public TextBinder(Context context) {
super(context);
}
@Override
public void bind(BaseViewHolder<TextItem> holder, TextItem item) {
final TextHolder actualHolder = (TextHolder) holder;
actualHolder.getTextView().setText(item.getText());
}
@Override
public boolean canBind(RecyclerViewItem item) {
return item instanceof TextItem;
}
}

View File

@@ -0,0 +1,35 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.holder;
import android.view.View;
import android.widget.TextView;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.AdRecordItem;
public class AdRecordHolder extends BaseViewHolder<AdRecordItem> {
private final TextView mStringTextView;
private final TextView mArrayTextView;
private final TextView mTitleTextView;
public AdRecordHolder(View itemView) {
super(itemView);
mStringTextView = (TextView) itemView.findViewById(R.id.data_as_string);
mArrayTextView = (TextView) itemView.findViewById(R.id.data_as_array);
mTitleTextView = (TextView) itemView.findViewById(R.id.title);
}
public TextView getStringTextView() {
return mStringTextView;
}
public TextView getArrayTextView() {
return mArrayTextView;
}
public TextView getTitleTextView() {
return mTitleTextView;
}
}

View File

@@ -0,0 +1,53 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.holder;
import android.view.View;
import android.widget.TextView;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.DeviceInfoItem;
public class DeviceInfoHolder extends BaseViewHolder<DeviceInfoItem> {
private final TextView mName;
private final TextView mAddress;
private final TextView mClass;
private final TextView mMajorClass;
private final TextView mServices;
private final TextView mBondingState;
public DeviceInfoHolder(View itemView) {
super(itemView);
mName = (TextView) itemView.findViewById(R.id.deviceName);
mAddress = (TextView) itemView.findViewById(R.id.deviceAddress);
mClass = (TextView) itemView.findViewById(R.id.deviceClass);
mMajorClass = (TextView) itemView.findViewById(R.id.deviceMajorClass);
mServices = (TextView) itemView.findViewById(R.id.deviceServiceList);
mBondingState = (TextView) itemView.findViewById(R.id.deviceBondingState);
}
public TextView getName() {
return mName;
}
public TextView getAddress() {
return mAddress;
}
public TextView getDeviceClass() {
return mClass;
}
public TextView getMajorClass() {
return mMajorClass;
}
public TextView getServices() {
return mServices;
}
public TextView getBondingState() {
return mBondingState;
}
}

View File

@@ -0,0 +1,23 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.holder;
import android.view.View;
import android.widget.TextView;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.HeaderItem;
public class HeaderHolder extends BaseViewHolder<HeaderItem> {
private final TextView mText;
public HeaderHolder(View itemView) {
super(itemView);
mText = (TextView) itemView.findViewById(R.id.text);
}
public TextView getTextView() {
return mText;
}
}

View File

@@ -0,0 +1,53 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.holder;
import android.view.View;
import android.widget.TextView;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.IBeaconItem;
public class IBeaconHolder extends BaseViewHolder<IBeaconItem> {
private final TextView mCompanyId;
private final TextView mAdvert;
private final TextView mUuid;
private final TextView mMajor;
private final TextView mMinor;
private final TextView mTxPower;
public IBeaconHolder(View itemView) {
super(itemView);
mCompanyId = (TextView) itemView.findViewById(R.id.companyId);
mAdvert = (TextView) itemView.findViewById(R.id.advertisement);
mUuid = (TextView) itemView.findViewById(R.id.uuid);
mMajor = (TextView) itemView.findViewById(R.id.major);
mMinor = (TextView) itemView.findViewById(R.id.minor);
mTxPower = (TextView) itemView.findViewById(R.id.txpower);
}
public TextView getCompanyId() {
return mCompanyId;
}
public TextView getAdvert() {
return mAdvert;
}
public TextView getUuid() {
return mUuid;
}
public TextView getMajor() {
return mMajor;
}
public TextView getMinor() {
return mMinor;
}
public TextView getTxPower() {
return mTxPower;
}
}

View File

@@ -0,0 +1,47 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.holder;
import android.view.View;
import android.widget.TextView;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.RssiItem;
public class RssiInfoHolder extends BaseViewHolder<RssiItem> {
private final TextView mTvFirstTimestamp;
private final TextView mTvFirstRssi;
private final TextView mTvLastTimestamp;
private final TextView mTvLastRssi;
private final TextView mTvRunningAverageRssi;
public RssiInfoHolder(View itemView) {
super(itemView);
mTvFirstTimestamp = (TextView) itemView.findViewById(R.id.firstTimestamp);
mTvFirstRssi = (TextView) itemView.findViewById(R.id.firstRssi);
mTvLastTimestamp = (TextView) itemView.findViewById(R.id.lastTimestamp);
mTvLastRssi = (TextView) itemView.findViewById(R.id.lastRssi);
mTvRunningAverageRssi = (TextView) itemView.findViewById(R.id.runningAverageRssi);
}
public TextView getFirstTimestamp() {
return mTvFirstTimestamp;
}
public TextView getFirstRssi() {
return mTvFirstRssi;
}
public TextView getLastTimestamp() {
return mTvLastTimestamp;
}
public TextView getLastRssi() {
return mTvLastRssi;
}
public TextView getRunningAverageRssi() {
return mTvRunningAverageRssi;
}
}

View File

@@ -0,0 +1,23 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.holder;
import android.view.View;
import android.widget.TextView;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.TextItem;
public class TextHolder extends BaseViewHolder<TextItem> {
private final TextView mText;
public TextHolder(View itemView) {
super(itemView);
mText = (TextView) itemView.findViewById(R.id.text);
}
public TextView getTextView() {
return mText;
}
}

View File

@@ -0,0 +1,31 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.model;
import uk.co.alt236.bluetoothlelib.device.adrecord.AdRecord;
import uk.co.alt236.bluetoothlelib.util.AdRecordUtils;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
public class AdRecordItem implements RecyclerViewItem {
private final String mTitle;
private final byte[] mData;
private final String mDataAsString;
public AdRecordItem(final String title,
final AdRecord record) {
mTitle = title;
mData = record.getData();
mDataAsString = AdRecordUtils.getRecordDataAsString(record);
}
public String getTitle() {
return mTitle;
}
public byte[] getData() {
return mData;
}
public String getDataAsString() {
return mDataAsString;
}
}

View File

@@ -0,0 +1,40 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.model;
import java.util.Set;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.bluetoothlelib.device.BluetoothService;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
public class DeviceInfoItem implements RecyclerViewItem {
private final BluetoothLeDevice mDevice;
public DeviceInfoItem(BluetoothLeDevice device) {
mDevice = device;
}
public Set<BluetoothService> getBluetoothDeviceKnownSupportedServices() {
return mDevice.getBluetoothDeviceKnownSupportedServices();
}
public String getBluetoothDeviceBondState() {
return mDevice.getBluetoothDeviceBondState();
}
public String getBluetoothDeviceMajorClassName() {
return mDevice.getBluetoothDeviceMajorClassName();
}
public String getBluetoothDeviceClassName() {
return mDevice.getBluetoothDeviceClassName();
}
public String getAddress() {
return mDevice.getAddress();
}
public String getName() {
return mDevice.getName();
}
}

View File

@@ -0,0 +1,15 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.model;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
public class HeaderItem implements RecyclerViewItem {
private final CharSequence mText;
public HeaderItem(CharSequence text) {
mText = text;
}
public CharSequence getText() {
return mText;
}
}

View File

@@ -0,0 +1,47 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.model;
import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconManufacturerData;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
public class IBeaconItem implements RecyclerViewItem {
private final int mMajor;
private final int mMinor;
private final String mUuid;
private final int mCompanyIdentifier;
private final int mIBeaconAdvertisement;
private final int mCalibratedTxPower;
public IBeaconItem(final IBeaconManufacturerData iBeaconData) {
mMajor = iBeaconData.getMajor();
mMinor = iBeaconData.getMinor();
mUuid = iBeaconData.getUUID();
mCompanyIdentifier = iBeaconData.getCompanyIdentifier();
mIBeaconAdvertisement = iBeaconData.getIBeaconAdvertisement();
mCalibratedTxPower = iBeaconData.getCalibratedTxPower();
}
public int getCompanyIdentifier() {
return mCompanyIdentifier;
}
public int getMajor() {
return mMajor;
}
public int getMinor() {
return mMinor;
}
public String getUuid() {
return mUuid;
}
public int getIBeaconAdvertisement() {
return mIBeaconAdvertisement;
}
public int getCalibratedTxPower() {
return mCalibratedTxPower;
}
}

View File

@@ -0,0 +1,33 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.model;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
public class RssiItem implements RecyclerViewItem {
private final BluetoothLeDevice mDevice;
public RssiItem(BluetoothLeDevice device) {
mDevice = device;
}
public int getRssi() {
return mDevice.getRssi();
}
public double getRunningAverageRssi() {
return mDevice.getRunningAverageRssi();
}
public int getFirstRssi() {
return mDevice.getFirstRssi();
}
public long getFirstTimestamp() {
return mDevice.getFirstTimestamp();
}
public long getTimestamp() {
return mDevice.getTimestamp();
}
}

View File

@@ -0,0 +1,15 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.model;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
public class TextItem implements RecyclerViewItem {
private final CharSequence mText;
public TextItem(CharSequence text) {
mText = text;
}
public CharSequence getText() {
return mText;
}
}

View File

@@ -1,6 +1,6 @@
package uk.co.alt236.btlescan.util;
package uk.co.alt236.btlescan.ui.main;
public class CsvWriterHelper {
/*package*/ class CsvWriterHelper {
private static final String QUOTE = "\"";
public static String addStuff(final Integer text) {

View File

@@ -0,0 +1,10 @@
package uk.co.alt236.btlescan.ui.main;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseRecyclerViewAdapter;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewBinderCore;
/*package*/ class DeviceRecyclerAdapter extends BaseRecyclerViewAdapter {
public DeviceRecyclerAdapter(RecyclerViewBinderCore core) {
super(core);
}
}

View File

@@ -0,0 +1,47 @@
package uk.co.alt236.btlescan.ui.main;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.support.v7.app.AlertDialog;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.text.util.Linkify;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import uk.co.alt236.btlescan.R;
/*package*/ final class DialogFactory {
private DialogFactory() {
// NOOP
}
public static Dialog createAboutDialog(final Context context) {
final View view = LayoutInflater.from(context).inflate(R.layout.dialog_textview, null);
final TextView textView = (TextView) view.findViewById(R.id.text);
final SpannableString text = new SpannableString(context.getString(R.string.about_dialog_text));
textView.setText(text);
textView.setAutoLinkMask(Activity.RESULT_OK);
textView.setMovementMethod(LinkMovementMethod.getInstance());
Linkify.addLinks(text, Linkify.ALL);
final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
public void onClick(final DialogInterface dialog, final int id) {
}
};
return new AlertDialog.Builder(context)
.setTitle(R.string.menu_about)
.setCancelable(false)
.setPositiveButton(android.R.string.ok, listener)
.setView(view)
.create();
}
}

View File

@@ -1,33 +1,43 @@
package uk.co.alt236.btlescan.activities;
package uk.co.alt236.btlescan.ui.main;
import android.app.AlertDialog;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.text.util.Linkify;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.anthonycr.grant.PermissionsManager;
import com.anthonycr.grant.PermissionsResultAction;
import java.util.ArrayList;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
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.adapters.LeDeviceListAdapter;
import uk.co.alt236.btlescan.containers.BluetoothLeDeviceStore;
import uk.co.alt236.btlescan.ui.common.Navigation;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewBinderCore;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.main.recyclerview.model.IBeaconItem;
import uk.co.alt236.btlescan.ui.main.recyclerview.model.LeDeviceItem;
import uk.co.alt236.btlescan.util.BluetoothLeScanner;
import uk.co.alt236.btlescan.util.BluetoothUtils;
import uk.co.alt236.easycursor.objectcursor.EasyObjectCursor;
public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
public class MainActivity extends AppCompatActivity {
@Bind(R.id.tvBluetoothLe)
protected TextView mTvBluetoothLeStatus;
@Bind(R.id.tvBluetoothStatus)
@@ -35,14 +45,15 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
@Bind(R.id.tvItemCount)
protected TextView mTvItemCount;
@Bind(android.R.id.list)
protected ListView mList;
protected RecyclerView mList;
@Bind(android.R.id.empty)
protected View mEmpty;
private RecyclerViewBinderCore mCore;
private BluetoothUtils mBluetoothUtils;
private BluetoothLeScanner mScanner;
private LeDeviceListAdapter mLeDeviceListAdapter;
private BluetoothLeDeviceStore mDeviceStore;
private DeviceRecyclerAdapter mRecyclerAdapter;
private final BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
@@ -50,51 +61,33 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
final BluetoothLeDevice deviceLe = new BluetoothLeDevice(device, rssi, scanRecord, System.currentTimeMillis());
mDeviceStore.addDevice(deviceLe);
final EasyObjectCursor<BluetoothLeDevice> c = mDeviceStore.getDeviceCursor();
final List<RecyclerViewItem> itemList = new ArrayList<>();
for (final BluetoothLeDevice leDevice : mDeviceStore.getDeviceList()) {
if (BeaconUtils.getBeaconType(leDevice) == BeaconType.IBEACON) {
itemList.add(new IBeaconItem(new IBeaconDevice(leDevice)));
} else {
itemList.add(new LeDeviceItem(leDevice));
}
}
runOnUiThread(new Runnable() {
@Override
public void run() {
mLeDeviceListAdapter.swapCursor(c);
updateItemCount(mLeDeviceListAdapter.getCount());
mRecyclerAdapter.setData(itemList);
updateItemCount(mRecyclerAdapter.getItemCount());
}
});
}
};
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(final DialogInterface dialog, final int id) {
}
})
.setView(textView)
.show();
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mList.setEmptyView(mEmpty);
mList.setOnItemClickListener(this);
mCore = RecyclerViewCoreFactory.create(this, new Navigation(this));
mList.setLayoutManager(new LinearLayoutManager(this));
mDeviceStore = new BluetoothLeDeviceStore();
mBluetoothUtils = new BluetoothUtils(this);
mScanner = new BluetoothLeScanner(mLeScanCallback, mBluetoothUtils);
@@ -114,7 +107,7 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
menu.findItem(R.id.menu_refresh).setActionView(R.layout.actionbar_progress_indeterminate);
}
if (mList.getCount() > 0) {
if (mRecyclerAdapter != null && mRecyclerAdapter.getItemCount() > 0) {
menu.findItem(R.id.menu_share).setVisible(true);
} else {
menu.findItem(R.id.menu_share).setVisible(false);
@@ -123,32 +116,21 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
return true;
}
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
final BluetoothLeDevice device = 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(final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_scan:
startScan();
startScanPrepare();
break;
case R.id.menu_stop:
mScanner.scanLeDevice(-1, false);
invalidateOptionsMenu();
break;
case R.id.menu_about:
displayAboutDialog();
DialogFactory.createAboutDialog(this).show();
break;
case R.id.menu_share:
mDeviceStore.shareDataAsEmail(this);
new Sharer().shareDataAsEmail(this, mDeviceStore);
}
return true;
}
@@ -162,16 +144,14 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
@Override
public void onResume() {
super.onResume();
final boolean mIsBluetoothOn = mBluetoothUtils.isBluetoothOn();
final boolean mIsBluetoothLePresent = mBluetoothUtils.isBluetoothLeSupported();
if (mIsBluetoothOn) {
if (mBluetoothUtils.isBluetoothOn()) {
mTvBluetoothStatus.setText(R.string.on);
} else {
mTvBluetoothStatus.setText(R.string.off);
}
if (mIsBluetoothLePresent) {
if (mBluetoothUtils.isBluetoothLeSupported()) {
mTvBluetoothLeStatus.setText(R.string.supported);
} else {
mTvBluetoothLeStatus.setText(R.string.not_supported);
@@ -180,17 +160,44 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
invalidateOptionsMenu();
}
private void startScanPrepare() {
//
// The COARSE_LOCATION permission is only needed after API 23 to do a BTLE scan
//
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(this,
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, new PermissionsResultAction() {
@Override
public void onGranted() {
startScan();
}
@Override
public void onDenied(String permission) {
Toast.makeText(MainActivity.this,
R.string.permission_not_granted_coarse_location,
Toast.LENGTH_SHORT)
.show();
}
});
} else {
startScan();
}
}
private void startScan() {
final boolean mIsBluetoothOn = mBluetoothUtils.isBluetoothOn();
final boolean mIsBluetoothLePresent = mBluetoothUtils.isBluetoothLeSupported();
final boolean isBluetoothOn = mBluetoothUtils.isBluetoothOn();
final boolean isBluetoothLePresent = mBluetoothUtils.isBluetoothLeSupported();
mDeviceStore.clear();
updateItemCount(0);
mLeDeviceListAdapter = new LeDeviceListAdapter(this, mDeviceStore.getDeviceCursor());
mList.setAdapter(mLeDeviceListAdapter);
mRecyclerAdapter = new DeviceRecyclerAdapter(mCore);
mList.setAdapter(mRecyclerAdapter);
mBluetoothUtils.askUserToEnableBluetoothIfNeeded();
if (mIsBluetoothOn && mIsBluetoothLePresent) {
if (isBluetoothOn && isBluetoothLePresent) {
mScanner.scanLeDevice(-1, true);
invalidateOptionsMenu();
}
@@ -203,4 +210,10 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
String.valueOf(count)));
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
PermissionsManager.getInstance().notifyPermissionsChange(permissions, grantResults);
}
}

View File

@@ -0,0 +1,24 @@
package uk.co.alt236.btlescan.ui.main;
import android.content.Context;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.Navigation;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewBinderCore;
import uk.co.alt236.btlescan.ui.main.recyclerview.binder.IBeaconBinder;
import uk.co.alt236.btlescan.ui.main.recyclerview.binder.LeDeviceBinder;
import uk.co.alt236.btlescan.ui.main.recyclerview.holder.IBeaconHolder;
import uk.co.alt236.btlescan.ui.main.recyclerview.holder.LeDeviceHolder;
/*protected*/ final class RecyclerViewCoreFactory {
public static RecyclerViewBinderCore create(final Context context, final Navigation navigation) {
final RecyclerViewBinderCore core = new RecyclerViewBinderCore();
core.add(new IBeaconBinder(context, navigation), IBeaconHolder.class, R.layout.list_item_device_ibeacon);
core.add(new LeDeviceBinder(context, navigation), LeDeviceHolder.class, R.layout.list_item_device_le);
return core;
}
}

View File

@@ -0,0 +1,161 @@
package uk.co.alt236.btlescan.ui.main;
import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import android.support.v4.content.ContextCompat;
import android.widget.Toast;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
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.containers.BluetoothLeDeviceStore;
import uk.co.alt236.btlescan.ui.common.Navigation;
import uk.co.alt236.btlescan.util.TimeFormatter;
/*package*/ class Sharer {
private static final String CSV_FILENAME_PREFIX = "bluetooth_le_%d";
private static final String CSV_FILENAME_SUFFIX = ".csv";
private static File getExternalCacheDir(final Context context) {
final File[] files = ContextCompat.getExternalCacheDirs(context);
final File retVal;
if (files == null || files.length == 0 || files[0] == null) {
retVal = null;
} else {
retVal = files[0];
}
return retVal;
}
private static String getListAsCsv(List<BluetoothLeDevice> deviceList) {
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 (final BluetoothLeDevice device : deviceList) {
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');
}
return sb.toString();
}
private static FileWriter saveToFile(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 {
if (writer != null) {
writer.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
return writer;
}
public void shareDataAsEmail(final Activity activity,
final BluetoothLeDeviceStore store) {
final long timeInMillis = System.currentTimeMillis();
final String filename = String.format(Locale.US, CSV_FILENAME_PREFIX, timeInMillis);
final String to = null;
final String subject = activity.getString(
R.string.exporter_email_device_list_subject,
TimeFormatter.getIsoDateTime(timeInMillis));
final String message = activity.getString(R.string.exporter_email_device_list_body);
final String contents = getListAsCsv(store.getDeviceList());
final File outputDir = getExternalCacheDir(activity);
if (outputDir == null) {
Toast.makeText(activity, R.string.error_unable_to_access_external_storage, Toast.LENGTH_SHORT).show();
} else {
try {
final File outputFile = File.createTempFile(filename, CSV_FILENAME_SUFFIX, outputDir);
saveToFile(outputFile, contents);
final Uri uri = Uri.fromFile(outputFile);
new Navigation(activity)
.shareFileViaEmail(uri, new String[]{to}, subject, message);
} catch (final IOException e) {
e.printStackTrace();
}
}
}
}

View File

@@ -0,0 +1,36 @@
package uk.co.alt236.btlescan.ui.main.recyclerview.binder;
import android.content.Context;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.main.recyclerview.holder.CommonDeviceHolder;
import uk.co.alt236.btlescan.util.Constants;
/*package*/ class CommonBinding {
public static void bind(final Context context,
final CommonDeviceHolder holder,
final BluetoothLeDevice device) {
final String deviceName = device.getName();
final double rssi = device.getRssi();
if (deviceName != null && deviceName.length() > 0) {
holder.getDeviceName().setText(deviceName);
} else {
holder.getDeviceName().setText(R.string.unknown_device);
}
final String rssiString =
context.getString(R.string.formatter_db, String.valueOf(rssi));
final String runningAverageRssiString =
context.getString(R.string.formatter_db, String.valueOf(device.getRunningAverageRssi()));
holder.getDeviceLastUpdated().setText(
android.text.format.DateFormat.format(
Constants.TIME_FORMAT, new java.util.Date(device.getTimestamp())));
holder.getDeviceAddress().setText(device.getAddress());
holder.getDeviceRssi().setText(rssiString + " / " + runningAverageRssiString);
}
}

View File

@@ -0,0 +1,54 @@
package uk.co.alt236.btlescan.ui.main.recyclerview.binder;
import android.content.Context;
import android.view.View;
import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconDevice;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.Navigation;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewBinder;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.main.recyclerview.holder.IBeaconHolder;
import uk.co.alt236.btlescan.ui.main.recyclerview.model.IBeaconItem;
import uk.co.alt236.btlescan.util.Constants;
public class IBeaconBinder extends BaseViewBinder<IBeaconItem> {
private final Navigation navigation;
public IBeaconBinder(Context context, Navigation navigation) {
super(context);
this.navigation = navigation;
}
@Override
public void bind(BaseViewHolder<IBeaconItem> holder, IBeaconItem item) {
final IBeaconHolder actualHolder = (IBeaconHolder) holder;
final IBeaconDevice device = item.getDevice();
final String accuracy = Constants.DOUBLE_TWO_DIGIT_ACCURACY.format(device.getAccuracy());
actualHolder.getIbeaconMajor().setText(String.valueOf(device.getMajor()));
actualHolder.getIbeaconMinor().setText(String.valueOf(device.getMinor()));
actualHolder.getIbeaconTxPower().setText(String.valueOf(device.getCalibratedTxPower()));
actualHolder.getIbeaconUUID().setText(device.getUUID());
actualHolder.getIbeaconDistance().setText(
getContext().getString(R.string.formatter_meters, accuracy));
actualHolder.getIbeaconDistanceDescriptor().setText(device.getDistanceDescriptor().toString());
CommonBinding.bind(getContext(), actualHolder, device);
actualHolder.getView().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
navigation.openDetailsActivity(device);
}
});
}
@Override
public boolean canBind(RecyclerViewItem item) {
return item instanceof IBeaconItem;
}
}

View File

@@ -0,0 +1,42 @@
package uk.co.alt236.btlescan.ui.main.recyclerview.binder;
import android.content.Context;
import android.view.View;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.btlescan.ui.common.Navigation;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewBinder;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.main.recyclerview.holder.LeDeviceHolder;
import uk.co.alt236.btlescan.ui.main.recyclerview.model.LeDeviceItem;
public class LeDeviceBinder extends BaseViewBinder<LeDeviceItem> {
private final Navigation navigation;
public LeDeviceBinder(Context context, Navigation navigation) {
super(context);
this.navigation = navigation;
}
@Override
public void bind(BaseViewHolder<LeDeviceItem> holder, LeDeviceItem item) {
final LeDeviceHolder actualHolder = (LeDeviceHolder) holder;
final BluetoothLeDevice device = item.getDevice();
CommonBinding.bind(getContext(), actualHolder, device);
actualHolder.getView().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
navigation.openDetailsActivity(device);
}
});
}
@Override
public boolean canBind(RecyclerViewItem item) {
return item instanceof LeDeviceItem;
}
}

View File

@@ -0,0 +1,13 @@
package uk.co.alt236.btlescan.ui.main.recyclerview.holder;
import android.widget.TextView;
public interface CommonDeviceHolder {
TextView getDeviceName();
TextView getDeviceAddress();
TextView getDeviceRssi();
TextView getDeviceLastUpdated();
}

View File

@@ -0,0 +1,83 @@
package uk.co.alt236.btlescan.ui.main.recyclerview.holder;
import android.view.View;
import android.widget.TextView;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.main.recyclerview.model.IBeaconItem;
public class IBeaconHolder extends BaseViewHolder<IBeaconItem> implements CommonDeviceHolder {
private final TextView ibeaconUUID;
private final TextView ibeaconMajor;
private final TextView ibeaconMinor;
private final TextView ibeaconTxPower;
private final TextView ibeaconDistance;
private final TextView ibeaconDistanceDescriptor;
private final TextView deviceName;
private final TextView deviceAddress;
private final TextView deviceRssi;
private final TextView deviceLastUpdated;
public IBeaconHolder(View itemView) {
super(itemView);
this.deviceAddress = (TextView) itemView.findViewById(R.id.device_address);
this.deviceName = (TextView) itemView.findViewById(R.id.device_name);
this.deviceRssi = (TextView) itemView.findViewById(R.id.device_rssi);
this.deviceLastUpdated = (TextView) itemView.findViewById(R.id.device_last_update);
this.ibeaconMajor = (TextView) itemView.findViewById(R.id.ibeacon_major);
this.ibeaconMinor = (TextView) itemView.findViewById(R.id.ibeacon_minor);
this.ibeaconDistance = (TextView) itemView.findViewById(R.id.ibeacon_distance);
this.ibeaconUUID = (TextView) itemView.findViewById(R.id.ibeacon_uuid);
this.ibeaconTxPower = (TextView) itemView.findViewById(R.id.ibeacon_tx_power);
this.ibeaconDistanceDescriptor = (TextView) itemView.findViewById(R.id.ibeacon_distance_descriptor);
}
@Override
public TextView getDeviceName() {
return deviceName;
}
@Override
public TextView getDeviceAddress() {
return deviceAddress;
}
@Override
public TextView getDeviceRssi() {
return deviceRssi;
}
@Override
public TextView getDeviceLastUpdated() {
return deviceLastUpdated;
}
public TextView getIbeaconUUID() {
return ibeaconUUID;
}
public TextView getIbeaconMajor() {
return ibeaconMajor;
}
public TextView getIbeaconMinor() {
return ibeaconMinor;
}
public TextView getIbeaconTxPower() {
return ibeaconTxPower;
}
public TextView getIbeaconDistance() {
return ibeaconDistance;
}
public TextView getIbeaconDistanceDescriptor() {
return ibeaconDistanceDescriptor;
}
}

View File

@@ -0,0 +1,45 @@
package uk.co.alt236.btlescan.ui.main.recyclerview.holder;
import android.view.View;
import android.widget.TextView;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.main.recyclerview.model.LeDeviceItem;
public class LeDeviceHolder extends BaseViewHolder<LeDeviceItem> implements CommonDeviceHolder {
private final TextView deviceName;
private final TextView deviceAddress;
private final TextView deviceRssi;
private final TextView deviceLastUpdated;
public LeDeviceHolder(View itemView) {
super(itemView);
this.deviceAddress = (TextView) itemView.findViewById(R.id.device_address);
this.deviceName = (TextView) itemView.findViewById(R.id.device_name);
this.deviceRssi = (TextView) itemView.findViewById(R.id.device_rssi);
this.deviceLastUpdated = (TextView) itemView.findViewById(R.id.device_last_update);
}
@Override
public TextView getDeviceName() {
return deviceName;
}
@Override
public TextView getDeviceAddress() {
return deviceAddress;
}
@Override
public TextView getDeviceRssi() {
return deviceRssi;
}
@Override
public TextView getDeviceLastUpdated() {
return deviceLastUpdated;
}
}

View File

@@ -0,0 +1,18 @@
package uk.co.alt236.btlescan.ui.main.recyclerview.model;
import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconDevice;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
public class IBeaconItem implements RecyclerViewItem {
private final IBeaconDevice device;
public IBeaconItem(final IBeaconDevice device) {
this.device = device;
}
public IBeaconDevice getDevice() {
return device;
}
}

View File

@@ -0,0 +1,17 @@
package uk.co.alt236.btlescan.ui.main.recyclerview.model;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
public class LeDeviceItem implements RecyclerViewItem {
private final BluetoothLeDevice device;
public LeDeviceItem(final BluetoothLeDevice device) {
this.device = device;
}
public BluetoothLeDevice getDevice() {
return device;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 445 B

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 548 B

After

Width:  |  Height:  |  Size: 899 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 824 B

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 911 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -6,9 +6,9 @@
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>
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>

View File

@@ -57,7 +57,7 @@
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@id/deviceInformation"
android:background="@android:color/holo_blue_dark"/>
android:background="@color/colorSeparator" />
<GridLayout
android:id="@+id/gattInformation"
@@ -113,7 +113,7 @@
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_above="@id/gattInformation"
android:background="@android:color/holo_blue_dark"/>
android:background="@color/colorSeparator" />
<ExpandableListView
android:id="@+id/gatt_services_list"

View File

@@ -7,7 +7,7 @@
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
tools:context=".ui.main.MainActivity">
<GridLayout
android:id="@+id/gridLayout1"
@@ -61,7 +61,7 @@
android:id="@+id/upperSepparator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/holo_blue_dark"/>
android:background="@color/colorSeparator" />
<RelativeLayout
android:layout_width="match_parent"
@@ -79,7 +79,7 @@
android:id="@+id/lowerSepparator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/holo_blue_dark"/>
android:background="@color/colorSeparator" />
<TextView
android:id="@+id/tvItemCount"
@@ -96,7 +96,7 @@
android:layout_alignParentTop="true"
android:orientation="vertical">
<ListView
<android.support.v7.widget.RecyclerView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@@ -0,0 +1,12 @@
<?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="match_parent"
android:orientation="vertical"
android:padding="?dialogPreferredPadding">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?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");
@@ -17,78 +16,24 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:gravity="top"
android:orientation="horizontal">
<ImageView
android:id="@+id/device_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="36dp"
android:layout_height="36dp"
android:paddingRight="5dp"
android:paddingTop="5dp"
android:src="@drawable/ic_bluetooth"/>
android:src="@drawable/ic_device_ibeacon"/>
<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="@string/label_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>
<include layout="@layout/viewpart_list_item_device_common"/>
<GridLayout
android:id="@+id/ibeacon_section"

View File

@@ -0,0 +1,21 @@
<?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:background="?attr/selectableItemBackground"
android:gravity="top"
android:orientation="horizontal">
<ImageView
android:id="@+id/device_icon"
android:layout_width="36dp"
android:layout_height="36dp"
android:paddingRight="5dp"
android:paddingTop="5dp"
android:src="@drawable/ic_bluetooth"/>
<include layout="@layout/viewpart_list_item_device_common"/>
</LinearLayout>

View File

@@ -4,7 +4,7 @@
android:layout_height="wrap_content"
android:gravity="top"
android:orientation="vertical"
android:paddingBottom="5dp">
android:paddingBottom="@dimen/space_between_sections">
<TextView
android:id="@+id/title"

View File

@@ -4,7 +4,7 @@
android:layout_height="wrap_content"
android:gravity="top"
android:orientation="vertical"
android:paddingBottom="5dp">
android:paddingBottom="@dimen/space_between_sections">
<GridLayout
android:layout_width="match_parent"

View File

@@ -4,20 +4,15 @@
android:layout_height="wrap_content"
android:gravity="top"
android:orientation="vertical"
android:paddingBottom="5dp">
android:paddingBottom="6dp">
<TextView
android:id="@+id/title"
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAllCaps="true"
android:textColor="@android:color/holo_blue_dark"
android:textColor="@color/colorSeparator"
android:textSize="14sp"
android:textStyle="bold"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/holo_blue_dark"/>
</LinearLayout>

Some files were not shown because too many files have changed in this diff Show More