Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 40a181cc3e | |||
|
|
8871fc5d20 | ||
|
|
e5ed6f1dbb | ||
|
|
e838cacad1 | ||
|
|
ebc44c7537 | ||
|
|
f2f9ac38f4 | ||
|
|
be2583ce4e | ||
|
|
0d1085730b | ||
|
|
47a4c124f6 | ||
|
|
81d984efac | ||
|
|
a7312ad51c | ||
|
|
9ba9f2728b | ||
|
|
3a234b8080 | ||
|
|
1d3dd85eeb | ||
|
|
1639e1a22d | ||
|
|
ef5b84387f | ||
|
|
6f092344f5 | ||
|
|
7f15ea3d1e | ||
|
|
727f2d8953 | ||
|
|
b54a56b15b | ||
|
|
e43bfc1b82 | ||
|
|
e9c2f0d486 | ||
|
|
65b597ab29 | ||
|
|
92d6ee5241 | ||
|
|
54d0caf6c8 | ||
|
|
ec33fd7618 | ||
|
|
c6cece6acc | ||
|
|
1647ca1ec2 | ||
|
|
112bc00054 | ||
|
|
f483a0e24a |
1
.idea/.name
generated
@@ -1 +0,0 @@
|
||||
bluetooth-le-library
|
||||
2
.idea/compiler.xml
generated
@@ -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
@@ -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>
|
||||
|
||||
16
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -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>
|
||||
7
.idea/inspectionProfiles/profiles_settings.xml
generated
@@ -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
@@ -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 > Lint > Correctness</id>
|
||||
</State>
|
||||
<State>
|
||||
<id>Android > Lint > 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
@@ -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>
|
||||
27
.idea/runConfigurations/Run_Android_Tests.xml
generated
@@ -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>
|
||||
23
.idea/runConfigurations/Run_JUnit_Tests.xml
generated
@@ -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>
|
||||
26
.idea/runConfigurations/Run_Sample_App.xml
generated
@@ -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
@@ -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>
|
||||
19
Bluetooth-LE-Library---Android.iml
Normal 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>
|
||||
61
README.md
@@ -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
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## 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.
|
||||
|
||||
@@ -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>
|
||||
@@ -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
@@ -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/ \;
|
||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
|
||||
|
||||
BIN
image_assets/feature_graphic.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
image_assets/screenshots/phone_screenshot_1.png
Normal file
|
After Width: | Height: | Size: 200 KiB |
BIN
image_assets/screenshots/phone_screenshot_2.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
image_assets/screenshots/phone_screenshot_3.png
Normal file
|
After Width: | Height: | Size: 151 KiB |
BIN
image_assets/screenshots/phone_screenshot_4.png
Normal file
|
After Width: | Height: | Size: 170 KiB |
BIN
image_assets/web_hi_res_512.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
@@ -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>
|
||||
@@ -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 {
|
||||
|
||||
|
Before Width: | Height: | Size: 41 KiB |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package uk.co.alt236.btlescan.ui.common.recyclerview;
|
||||
|
||||
public interface RecyclerViewItem {
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
// }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 445 B After Width: | Height: | Size: 710 B |
BIN
sample_app/src/main/res/drawable-hdpi/ic_bluetooth.png
Normal file
|
After Width: | Height: | Size: 508 B |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 487 B |
BIN
sample_app/src/main/res/drawable-mdpi/ic_bluetooth.png
Normal file
|
After Width: | Height: | Size: 374 B |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 548 B After Width: | Height: | Size: 899 B |
|
Before Width: | Height: | Size: 824 B After Width: | Height: | Size: 662 B |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 911 B After Width: | Height: | Size: 1.3 KiB |
BIN
sample_app/src/main/res/drawable-xxhdpi/ic_bluetooth.png
Normal file
|
After Width: | Height: | Size: 885 B |
|
Before Width: | Height: | Size: 8.7 KiB |
BIN
sample_app/src/main/res/drawable-xxxhdpi/ic_action_share.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
sample_app/src/main/res/drawable-xxxhdpi/ic_bluetooth.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
12
sample_app/src/main/res/layout/dialog_textview.xml
Normal 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>
|
||||
@@ -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"
|
||||
21
sample_app/src/main/res/layout/list_item_device_le.xml
Normal 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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||