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 +0,0 @@
|
|||||||
bluetooth-le-library
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="CompilerConfiguration">
|
<component name="CompilerConfiguration">
|
||||||
<option name="DEFAULT_COMPILER" value="Javac" />
|
|
||||||
<resourceExtensions />
|
<resourceExtensions />
|
||||||
<wildcardResourcePatterns>
|
<wildcardResourcePatterns>
|
||||||
<entry name="!?*.java" />
|
<entry name="!?*.java" />
|
||||||
@@ -12,6 +11,7 @@
|
|||||||
<entry name="!?*.flex" />
|
<entry name="!?*.flex" />
|
||||||
<entry name="!?*.kt" />
|
<entry name="!?*.kt" />
|
||||||
<entry name="!?*.clj" />
|
<entry name="!?*.clj" />
|
||||||
|
<entry name="!?*.aj" />
|
||||||
</wildcardResourcePatterns>
|
</wildcardResourcePatterns>
|
||||||
<annotationProcessing>
|
<annotationProcessing>
|
||||||
<profile default="true" name="Default" enabled="false">
|
<profile default="true" name="Default" enabled="false">
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleJvm" value="1.8" />
|
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
@@ -13,6 +12,7 @@
|
|||||||
<option value="$PROJECT_DIR$/sample_app" />
|
<option value="$PROJECT_DIR$/sample_app" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
|
<option name="resolveModulePerSourceSet" value="false" />
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -3,6 +3,64 @@
|
|||||||
<component name="EntryPointsManager">
|
<component name="EntryPointsManager">
|
||||||
<entry_points version="2.0" />
|
<entry_points version="2.0" />
|
||||||
</component>
|
</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">
|
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
|
||||||
<OptionsSetting value="true" id="Add" />
|
<OptionsSetting value="true" id="Add" />
|
||||||
<OptionsSetting value="true" id="Remove" />
|
<OptionsSetting value="true" id="Remove" />
|
||||||
@@ -13,26 +71,10 @@
|
|||||||
<ConfirmationsSetting value="0" id="Add" />
|
<ConfirmationsSetting value="0" id="Add" />
|
||||||
<ConfirmationsSetting value="0" id="Remove" />
|
<ConfirmationsSetting value="0" id="Remove" />
|
||||||
</component>
|
</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" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
<option name="id" value="Android" />
|
<option name="id" value="Android" />
|
||||||
</component>
|
</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>
|
</project>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -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>
|
||||||
@@ -11,12 +11,14 @@ It also offers:
|
|||||||
|
|
||||||
This will only work on devices with Android 4.3 (API Level 18) and above.
|
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
|
## 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:
|
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 {
|
repositories {
|
||||||
maven {
|
maven {
|
||||||
url "https://dl.bintray.com/alt236/maven"
|
url "https://dl.bintray.com/alt236/maven"
|
||||||
@@ -35,7 +37,7 @@ In the `onLeScan()` method of your `BluetoothAdapter.LeScanCallback()` create a
|
|||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
```
|
```java
|
||||||
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
|
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
|
||||||
|
|
||||||
@Override
|
@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)`.
|
**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:
|
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.
|
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:
|
Example Flow:
|
||||||
```
|
```java
|
||||||
final BluetoothLeDevice device = ... // A generic BLE device
|
final BluetoothLeDevice device = ... // A generic BLE device
|
||||||
|
|
||||||
if (BeaconUtils.getBeaconType(device) == BeaconType.IBEACON) {
|
if (BeaconUtils.getBeaconType(device) == BeaconType.IBEACON) {
|
||||||
@@ -125,22 +127,27 @@ You can also lookup values and convert them to human friendly strings:
|
|||||||
* Fixed a ConcurrentModificationException on getRunningAverageRssi()
|
* Fixed a ConcurrentModificationException on getRunningAverageRssi()
|
||||||
* Added some Estimote UUIDs
|
* Added some Estimote UUIDs
|
||||||
* v1.0.0:
|
* v1.0.0:
|
||||||
* Migrated project to Android Studio/ gradle
|
* Migrated project to Android Studio/ gradle
|
||||||
* Note that the API has slightly changed in this version.
|
* Note that the API has slightly changed in this version.
|
||||||
* We now use the more generic `BeaconUtils.getBeaconType()` method instead of `IBeaconUtils.isThisAnIBeacon()`
|
* 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 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)
|
* Fix for [issue 9](https://github.com/alt236/Bluetooth-LE-Library---Android/issues/9)
|
||||||
|
|
||||||
## Sample Application Changelog
|
## Sample Application Changelog
|
||||||
* v0.0.1
|
* v0.0.1
|
||||||
* First public release
|
* First public release
|
||||||
* v0.0.2:
|
* v0.0.2:
|
||||||
* Can now export scanned devices as a CSV file.
|
* Can now export scanned devices as a CSV file.
|
||||||
* v0.0.3:
|
* v0.0.3:
|
||||||
* UI Refresh.
|
* UI Refresh.
|
||||||
* v1.0.0:
|
* v1.0.0:
|
||||||
* Migrated project to Android Studio/ gradle
|
* Migrated project to Android Studio/ gradle
|
||||||
* Using version v1.0.0 of the library project
|
* 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
|
## Permission Explanation
|
||||||
You will need the following permissions to access the Bluetooth Hardware
|
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`
|
||||||
* `android.permission.BLUETOOTH_ADMIN`
|
* `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
|
## TODO
|
||||||
|
|
||||||
* Tidy up Javadoc. There is quite a lot of it that is template
|
* Tidy up Javadoc. There is quite a lot of it that is template
|
||||||
* Add parsers for common Ad Records.
|
* Add parsers for common Ad Records.
|
||||||
|
|
||||||
## Sample App Screenshots
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
* Github: [https://github.com/alt236/Bluetooth-LE-Library---Android]()
|
* 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 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 AdRecord parser was taken from: https://github.com/devunwired/accessory-samples
|
||||||
* The sample application has been adapted from Android's Bluetooth LE example
|
* 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.
|
All logos are the property of their respective owners.
|
||||||
|
|
||||||
The code in this project is licensed under the Apache Software License 2.0.
|
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>
|
</configuration>
|
||||||
</facet>
|
</facet>
|
||||||
</component>
|
</component>
|
||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
|
||||||
<exclude-output />
|
<exclude-output />
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
ext.compileSdkVersion = 22
|
ext.compileSdkVersion = 24
|
||||||
ext.buildToolsVersion = "22.0.1"
|
ext.buildToolsVersion = "24.0.1"
|
||||||
ext.minSdkVersion = 18
|
ext.minSdkVersion = 18
|
||||||
ext.targetSdkVersion = 22
|
ext.targetSdkVersion = 24
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:1.2.3'
|
classpath 'com.android.tools.build:gradle:2.2.3'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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/ \;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#Wed Apr 10 15:27:10 PDT 2013
|
#Wed Aug 24 14:15:35 BST 2016
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
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
|
||||||
|
|||||||
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 162 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 26 KiB |
@@ -12,19 +12,19 @@
|
|||||||
<option name="SELECTED_TEST_ARTIFACT" value="_unit_test_" />
|
<option name="SELECTED_TEST_ARTIFACT" value="_unit_test_" />
|
||||||
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
|
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
|
||||||
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
|
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
|
||||||
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
|
<afterSyncTasks>
|
||||||
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugUnitTest" />
|
<task>generateDebugSources</task>
|
||||||
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugUnitTestSources" />
|
</afterSyncTasks>
|
||||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
||||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
<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="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
|
||||||
<option name="LIBRARY_PROJECT" value="true" />
|
<option name="LIBRARY_PROJECT" value="true" />
|
||||||
</configuration>
|
</configuration>
|
||||||
</facet>
|
</facet>
|
||||||
</component>
|
</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 url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
|
||||||
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
|
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
|
||||||
<exclude-output />
|
<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/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/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/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/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/res" type="java-resource" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" 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" />
|
<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/java" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" 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/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/res" type="java-test-resource" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" 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" />
|
<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/java" isTestSource="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/jni" 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/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/res" type="java-resource" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" 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" />
|
<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/java" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" 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/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/res" type="java-test-resource" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" 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" />
|
<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/java" isTestSource="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/test/jni" 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/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/bundles" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
|
<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/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/incremental" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
|
<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/res" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
|
<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/symbols" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
|
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
|
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
|
||||||
</content>
|
</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="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" exported="" scope="TEST" name="objenesis-1.0" level="project" />
|
<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="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="junit-4.12" level="project" />
|
||||||
<orderEntry type="library" exported="" scope="TEST" name="mockito-core-1.9.5" 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>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
final int versionMajor = 1
|
final int versionMajor = 1
|
||||||
final int versionMinor = 0
|
final int versionMinor = 1
|
||||||
final int versionPatch = 0
|
final int versionPatch = 1
|
||||||
final int androidVersionCode = 5
|
final int androidVersionCode = 7
|
||||||
|
|
||||||
final int targetSdk = rootProject.targetSdkVersion;
|
final int targetSdk = rootProject.targetSdkVersion;
|
||||||
final int minSdkRed = rootProject.minSdkVersion;
|
final int minSdkRed = rootProject.minSdkVersion;
|
||||||
@@ -13,14 +13,28 @@ repositories {
|
|||||||
maven {
|
maven {
|
||||||
url "https://repo.commonsware.com.s3.amazonaws.com"
|
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 {
|
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 fileTree(include: ['*.jar'], dir: 'libs')
|
||||||
compile project(':library')
|
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 {
|
android {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 41 KiB |
@@ -12,9 +12,9 @@
|
|||||||
<option name="SELECTED_TEST_ARTIFACT" value="_unit_test_" />
|
<option name="SELECTED_TEST_ARTIFACT" value="_unit_test_" />
|
||||||
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
|
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
|
||||||
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
|
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
|
||||||
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
|
<afterSyncTasks>
|
||||||
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugUnitTest" />
|
<task>generateDebugSources</task>
|
||||||
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugUnitTestSources" />
|
</afterSyncTasks>
|
||||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
||||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
</configuration>
|
</configuration>
|
||||||
</facet>
|
</facet>
|
||||||
</component>
|
</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 url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
|
||||||
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
|
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
|
||||||
<exclude-output />
|
<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/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/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/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/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/res" type="java-resource" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" 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" />
|
<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/java" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" 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/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/res" type="java-test-resource" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" 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" />
|
<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/java" isTestSource="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/jni" 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/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/res" type="java-resource" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" 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" />
|
<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/java" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" 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/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/res" type="java-test-resource" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" 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" />
|
<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/java" isTestSource="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/test/jni" 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/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/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/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/dependency-cache" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
|
<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/dex-cache" />
|
<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/appcompat-v7/22.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-v4/22.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.commonsware.cwac/merge/1.1.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.commonsware.cwac/sacklist/1.0.2/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/incremental" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-classes" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-runtime-classes" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
|
<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/manifests" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/reload-dex" />
|
||||||
<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/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/rs" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
|
<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/outputs" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
|
||||||
</content>
|
</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="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="butterknife-7.0.1" level="project" />
|
||||||
<orderEntry type="library" exported="" name="merge-1.1.0" level="project" />
|
<orderEntry type="library" exported="" name="support-media-compat-24.2.0" level="project" />
|
||||||
<orderEntry type="library" exported="" name="sacklist-1.0.2" level="project" />
|
<orderEntry type="library" exported="" name="recyclerview-v7-24.2.0" level="project" />
|
||||||
<orderEntry type="library" exported="" name="EasyCursor-0.1.1" level="project" />
|
<orderEntry type="library" exported="" name="support-compat-24.2.0" level="project" />
|
||||||
<orderEntry type="library" exported="" name="support-annotations-22.2.0" level="project" />
|
<orderEntry type="library" exported="" name="support-v4-24.2.0" level="project" />
|
||||||
<orderEntry type="library" exported="" name="support-v4-22.2.0" level="project" />
|
<orderEntry type="library" exported="" name="support-core-ui-24.2.0" level="project" />
|
||||||
<orderEntry type="library" exported="" name="appcompat-v7-22.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="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>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
@@ -1,38 +1,37 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="uk.co.alt236.btlescan"
|
package="uk.co.alt236.btlescan">
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH"/>
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||||
|
|
||||||
|
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||||
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.bluetooth_le"
|
android:name="android.hardware.bluetooth_le"
|
||||||
android:required="false"/>
|
android:required="false" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
<activity
|
<activity
|
||||||
android:name="uk.co.alt236.btlescan.activities.MainActivity"
|
android:name=".ui.main.MainActivity"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
<intent-filter>
|
<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>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity android:name=".ui.details.DeviceDetailsActivity" />
|
||||||
android:name="uk.co.alt236.btlescan.activities.DeviceDetailsActivity"
|
|
||||||
android:label="@string/app_name">
|
<activity android:name=".ui.control.DeviceControlActivity" />
|
||||||
</activity>
|
|
||||||
<activity android:name="uk.co.alt236.btlescan.activities.DeviceControlActivity"/>
|
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name="uk.co.alt236.btlescan.services.BluetoothLeService"
|
android:name="uk.co.alt236.btlescan.services.BluetoothLeService"
|
||||||
android:enabled="true"/>
|
android:enabled="true" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</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;
|
package uk.co.alt236.btlescan.containers;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.support.annotation.NonNull;
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
|
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;
|
import uk.co.alt236.easycursor.objectcursor.EasyObjectCursor;
|
||||||
|
|
||||||
public class BluetoothLeDeviceStore {
|
public class BluetoothLeDeviceStore {
|
||||||
|
private static final BluetoothLeDeviceComparator DEFAULT_COMPARATOR = new BluetoothLeDeviceComparator();
|
||||||
private final Map<String, BluetoothLeDevice> mDeviceMap;
|
private final Map<String, BluetoothLeDevice> mDeviceMap;
|
||||||
|
|
||||||
|
|
||||||
public BluetoothLeDeviceStore() {
|
public BluetoothLeDeviceStore() {
|
||||||
mDeviceMap = new HashMap<>();
|
mDeviceMap = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addDevice(final BluetoothLeDevice device) {
|
public void addDevice(@NonNull final BluetoothLeDevice device) {
|
||||||
if (mDeviceMap.containsKey(device.getAddress())) {
|
if (mDeviceMap.containsKey(device.getAddress())) {
|
||||||
mDeviceMap.get(device.getAddress()).updateRssiReading(device.getTimestamp(), device.getRssi());
|
mDeviceMap.get(device.getAddress()).updateRssiReading(device.getTimestamp(), device.getRssi());
|
||||||
} else {
|
} else {
|
||||||
@@ -45,137 +32,42 @@ public class BluetoothLeDeviceStore {
|
|||||||
mDeviceMap.clear();
|
mDeviceMap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getSize() {
|
||||||
|
return mDeviceMap.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public EasyObjectCursor<BluetoothLeDevice> getDeviceCursor() {
|
public EasyObjectCursor<BluetoothLeDevice> getDeviceCursor() {
|
||||||
|
return getDeviceCursor(DEFAULT_COMPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public EasyObjectCursor<BluetoothLeDevice> getDeviceCursor(@NonNull Comparator<BluetoothLeDevice> comparator) {
|
||||||
return new EasyObjectCursor<>(
|
return new EasyObjectCursor<>(
|
||||||
BluetoothLeDevice.class,
|
BluetoothLeDevice.class,
|
||||||
getDeviceList(),
|
getDeviceList(comparator),
|
||||||
"address");
|
"address");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public List<BluetoothLeDevice> getDeviceList() {
|
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());
|
final List<BluetoothLeDevice> methodResult = new ArrayList<>(mDeviceMap.values());
|
||||||
|
|
||||||
Collections.sort(methodResult, new Comparator<BluetoothLeDevice>() {
|
Collections.sort(methodResult, comparator);
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(final BluetoothLeDevice arg0, final BluetoothLeDevice arg1) {
|
|
||||||
return arg0.getAddress().compareToIgnoreCase(arg1.getAddress());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return methodResult;
|
return methodResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getListAsCsv() {
|
private static class BluetoothLeDeviceComparator implements Comparator<BluetoothLeDevice> {
|
||||||
final List<BluetoothLeDevice> list = getDeviceList();
|
|
||||||
final StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append(CsvWriterHelper.addStuff("mac"));
|
|
||||||
sb.append(CsvWriterHelper.addStuff("name"));
|
|
||||||
sb.append(CsvWriterHelper.addStuff("firstTimestamp"));
|
|
||||||
sb.append(CsvWriterHelper.addStuff("firstRssi"));
|
|
||||||
sb.append(CsvWriterHelper.addStuff("currentTimestamp"));
|
|
||||||
sb.append(CsvWriterHelper.addStuff("currentRssi"));
|
|
||||||
sb.append(CsvWriterHelper.addStuff("adRecord"));
|
|
||||||
sb.append(CsvWriterHelper.addStuff("iBeacon"));
|
|
||||||
sb.append(CsvWriterHelper.addStuff("uuid"));
|
|
||||||
sb.append(CsvWriterHelper.addStuff("major"));
|
|
||||||
sb.append(CsvWriterHelper.addStuff("minor"));
|
|
||||||
sb.append(CsvWriterHelper.addStuff("txPower"));
|
|
||||||
sb.append(CsvWriterHelper.addStuff("distance"));
|
|
||||||
sb.append(CsvWriterHelper.addStuff("accuracy"));
|
|
||||||
sb.append('\n');
|
|
||||||
|
|
||||||
for (final BluetoothLeDevice device : list) {
|
@Override
|
||||||
sb.append(CsvWriterHelper.addStuff(device.getAddress()));
|
public int compare(final BluetoothLeDevice arg0, final BluetoothLeDevice arg1) {
|
||||||
sb.append(CsvWriterHelper.addStuff(device.getName()));
|
return arg0.getAddress().compareTo(arg1.getAddress());
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
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.os.IBinder;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,22 +39,21 @@ import java.util.List;
|
|||||||
* given Bluetooth LE device.
|
* given Bluetooth LE device.
|
||||||
*/
|
*/
|
||||||
public class BluetoothLeService extends Service {
|
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_CONNECTED = BluetoothLeService.class.getName() + ".ACTION_GATT_CONNECTED";
|
||||||
public final static String ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
|
public final static String ACTION_GATT_CONNECTING = BluetoothLeService.class.getName() + ".ACTION_GATT_CONNECTING";
|
||||||
public final static String ACTION_GATT_SERVICES_DISCOVERED = "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
|
public final static String ACTION_GATT_DISCONNECTED = BluetoothLeService.class.getName() + ".ACTION_GATT_DISCONNECTED";
|
||||||
public final static String ACTION_DATA_AVAILABLE = "com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
|
public final static String ACTION_GATT_SERVICES_DISCOVERED = BluetoothLeService.class.getName() + ".ACTION_GATT_SERVICES_DISCOVERED";
|
||||||
public final static String EXTRA_DATA_RAW = "com.example.bluetooth.le.EXTRA_DATA_RAW";
|
public final static String ACTION_DATA_AVAILABLE = BluetoothLeService.class.getName() + ".ACTION_DATA_AVAILABLE";
|
||||||
public final static String EXTRA_UUID_CHAR = "com.example.bluetooth.le.EXTRA_UUID_CHAR";
|
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 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 final IBinder mBinder = new LocalBinder();
|
||||||
private BluetoothManager mBluetoothManager;
|
private BluetoothManager mBluetoothManager;
|
||||||
private BluetoothAdapter mBluetoothAdapter;
|
private BluetoothAdapter mBluetoothAdapter;
|
||||||
private String mBluetoothDeviceAddress;
|
private String mBluetoothDeviceAddress;
|
||||||
private BluetoothGatt mBluetoothGatt;
|
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,
|
// Implements callback methods for GATT events that the app cares about. For example,
|
||||||
// connection change and services discovered.
|
// connection change and services discovered.
|
||||||
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
|
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
|
||||||
@@ -63,28 +63,35 @@ public class BluetoothLeService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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) {
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
|
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
|
public void onConnectionStateChange(final BluetoothGatt gatt,
|
||||||
final String intentAction;
|
final int status,
|
||||||
|
final int newState) {
|
||||||
|
|
||||||
|
Log.d(TAG, "onConnectionStateChange: status=" + status + ", newState=" + newState);
|
||||||
|
|
||||||
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
||||||
intentAction = ACTION_GATT_CONNECTED;
|
setConnectionState(State.CONNECTED, true);
|
||||||
mConnectionState = STATE_CONNECTED;
|
|
||||||
broadcastUpdate(intentAction);
|
|
||||||
Log.i(TAG, "Connected to GATT server.");
|
Log.i(TAG, "Connected to GATT server.");
|
||||||
// Attempts to discover services after successful connection.
|
// Attempts to discover services after successful connection.
|
||||||
Log.i(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices());
|
Log.i(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices());
|
||||||
|
|
||||||
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
||||||
intentAction = ACTION_GATT_DISCONNECTED;
|
// Make sure we tidy up. On certain devices reusing a Gatt after a disconnection
|
||||||
mConnectionState = STATE_DISCONNECTED;
|
// can cause problems.
|
||||||
|
disconnect();
|
||||||
|
|
||||||
|
setConnectionState(State.DISCONNECTED, true);
|
||||||
Log.i(TAG, "Disconnected from GATT server.");
|
Log.i(TAG, "Disconnected from GATT server.");
|
||||||
broadcastUpdate(intentAction);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,37 +145,89 @@ public class BluetoothLeService extends Service {
|
|||||||
* callback.
|
* callback.
|
||||||
*/
|
*/
|
||||||
public boolean connect(final String address) {
|
public boolean connect(final String address) {
|
||||||
|
|
||||||
|
final boolean retVal;
|
||||||
if (mBluetoothAdapter == null || address == null) {
|
if (mBluetoothAdapter == null || address == null) {
|
||||||
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
|
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
|
||||||
return false;
|
retVal = false;
|
||||||
}
|
|
||||||
|
|
||||||
// Previously connected device. Try to reconnect.
|
// Previously connected device. Try to reconnect.
|
||||||
if (mBluetoothDeviceAddress != null
|
} else if (mBluetoothDeviceAddress != null
|
||||||
&& address.equals(mBluetoothDeviceAddress)
|
&& address.equals(mBluetoothDeviceAddress)
|
||||||
&& mBluetoothGatt != null) {
|
&& mBluetoothGatt != null) {
|
||||||
|
|
||||||
Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
|
Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
|
||||||
if (mBluetoothGatt.connect()) {
|
if (mBluetoothGatt.connect()) {
|
||||||
mConnectionState = STATE_CONNECTING;
|
Log.d(TAG, "Connection attempt OK.");
|
||||||
return true;
|
setConnectionState(State.CONNECTING, true);
|
||||||
|
retVal = true;
|
||||||
} else {
|
} 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);
|
return retVal;
|
||||||
if (device == null) {
|
}
|
||||||
Log.w(TAG, "Device not found. Unable to connect.");
|
|
||||||
return false;
|
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
|
catch (Exception localException) {
|
||||||
// parameter to false.
|
Log.e(TAG, "An exception occured while refreshing device");
|
||||||
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
|
}
|
||||||
Log.d(TAG, "Trying to create a new connection.");
|
return false;
|
||||||
mBluetoothDeviceAddress = address;
|
}
|
||||||
mConnectionState = STATE_CONNECTING;
|
|
||||||
return true;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
mBluetoothGatt.disconnect();
|
mBluetoothGatt.disconnect();
|
||||||
|
|
||||||
|
// Reusing a Gatt after disconnecting can cause problems
|
||||||
|
mBluetoothGatt = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -270,4 +332,10 @@ public class BluetoothLeService extends Service {
|
|||||||
return BluetoothLeService.this;
|
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.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package uk.co.alt236.btlescan.activities;
|
package uk.co.alt236.btlescan.ui.control;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
import android.bluetooth.BluetoothGattService;
|
import android.bluetooth.BluetoothGattService;
|
||||||
@@ -26,6 +26,7 @@ import android.content.IntentFilter;
|
|||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -35,10 +36,7 @@ import android.widget.ExpandableListView;
|
|||||||
import android.widget.SimpleExpandableListAdapter;
|
import android.widget.SimpleExpandableListAdapter;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import butterknife.Bind;
|
import butterknife.Bind;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
@@ -55,10 +53,8 @@ import uk.co.alt236.btlescan.services.BluetoothLeService;
|
|||||||
* Bluetooth LE API.
|
* Bluetooth LE API.
|
||||||
*/
|
*/
|
||||||
public class DeviceControlActivity extends AppCompatActivity {
|
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 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)
|
@Bind(R.id.gatt_services_list)
|
||||||
protected ExpandableListView mGattServicesList;
|
protected ExpandableListView mGattServicesList;
|
||||||
@Bind(R.id.connection_state)
|
@Bind(R.id.connection_state)
|
||||||
@@ -71,9 +67,10 @@ public class DeviceControlActivity extends AppCompatActivity {
|
|||||||
protected TextView mDataAsString;
|
protected TextView mDataAsString;
|
||||||
@Bind(R.id.data_as_array)
|
@Bind(R.id.data_as_array)
|
||||||
protected TextView mDataAsArray;
|
protected TextView mDataAsArray;
|
||||||
|
private Exporter mExporter;
|
||||||
private BluetoothGattCharacteristic mNotifyCharacteristic;
|
private BluetoothGattCharacteristic mNotifyCharacteristic;
|
||||||
private BluetoothLeService mBluetoothLeService;
|
private BluetoothLeService mBluetoothLeService;
|
||||||
private List<List<BluetoothGattCharacteristic>> mGattCharacteristics = new ArrayList<>();
|
|
||||||
// If a given GATT characteristic is selected, check for supported features. This sample
|
// If a given GATT characteristic is selected, check for supported features. This sample
|
||||||
// demonstrates 'Read' and 'Notify' features. See
|
// demonstrates 'Read' and 'Notify' features. See
|
||||||
// http://d.android.com/reference/android/bluetooth/BluetoothGatt.html for the complete
|
// 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() {
|
private final ExpandableListView.OnChildClickListener servicesListClickListner = new ExpandableListView.OnChildClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onChildClick(final ExpandableListView parent, final View v, final int groupPosition, final int childPosition, final long id) {
|
public boolean onChildClick(final ExpandableListView parent, final View v, final int groupPosition, final int childPosition, final long id) {
|
||||||
if (mGattCharacteristics != null) {
|
final GattDataAdapterFactory.GattDataAdapter adapter =
|
||||||
final BluetoothGattCharacteristic characteristic = mGattCharacteristics.get(groupPosition).get(childPosition);
|
(GattDataAdapterFactory.GattDataAdapter) parent.getExpandableListAdapter();
|
||||||
final int charaProp = characteristic.getProperties();
|
|
||||||
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
|
final BluetoothGattCharacteristic characteristic =
|
||||||
// If there is an active notification on a characteristic, clear
|
adapter.getBluetoothGattCharacteristic(groupPosition, childPosition);
|
||||||
// it first so it doesn't update the data field on the user interface.
|
|
||||||
if (mNotifyCharacteristic != null) {
|
final int charaProp = characteristic.getProperties();
|
||||||
mBluetoothLeService.setCharacteristicNotification(mNotifyCharacteristic, false);
|
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
|
||||||
mNotifyCharacteristic = null;
|
// If there is an active notification on a characteristic, clear
|
||||||
}
|
// it first so it doesn't update the data field on the user interface.
|
||||||
mBluetoothLeService.readCharacteristic(characteristic);
|
if (mNotifyCharacteristic != null) {
|
||||||
|
mBluetoothLeService.setCharacteristicNotification(mNotifyCharacteristic, false);
|
||||||
|
mNotifyCharacteristic = null;
|
||||||
}
|
}
|
||||||
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
|
mBluetoothLeService.readCharacteristic(characteristic);
|
||||||
mNotifyCharacteristic = characteristic;
|
|
||||||
mBluetoothLeService.setCharacteristicNotification(characteristic, true);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
|
||||||
|
mNotifyCharacteristic = characteristic;
|
||||||
|
mBluetoothLeService.setCharacteristicNotification(characteristic, true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private String mDeviceAddress;
|
|
||||||
// Code to manage Service lifecycle.
|
// Code to manage Service lifecycle.
|
||||||
private final ServiceConnection mServiceConnection = new ServiceConnection() {
|
private final ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||||
@Override
|
@Override
|
||||||
@@ -113,7 +112,7 @@ public class DeviceControlActivity extends AppCompatActivity {
|
|||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
// Automatically connects to the device upon successful start-up initialization.
|
// Automatically connects to the device upon successful start-up initialization.
|
||||||
mBluetoothLeService.connect(mDeviceAddress);
|
mBluetoothLeService.connect(mDevice.getAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -121,8 +120,9 @@ public class DeviceControlActivity extends AppCompatActivity {
|
|||||||
mBluetoothLeService = null;
|
mBluetoothLeService = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private String mDeviceName;
|
private BluetoothLeDevice mDevice;
|
||||||
private boolean mConnected = false;
|
private State mCurrentState = State.DISCONNECTED;
|
||||||
|
|
||||||
private String mExportString;
|
private String mExportString;
|
||||||
// Handles various events fired by the Service.
|
// Handles various events fired by the Service.
|
||||||
// ACTION_GATT_CONNECTED: connected to a GATT server.
|
// 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) {
|
public void onReceive(final Context context, final Intent intent) {
|
||||||
final String action = intent.getAction();
|
final String action = intent.getAction();
|
||||||
if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
|
if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
|
||||||
mConnected = true;
|
updateConnectionState(State.CONNECTED);
|
||||||
updateConnectionState(R.string.connected);
|
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
|
} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
|
||||||
mConnected = false;
|
|
||||||
updateConnectionState(R.string.disconnected);
|
|
||||||
invalidateOptionsMenu();
|
|
||||||
clearUI();
|
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)) {
|
} else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
|
||||||
// Show all the supported services and characteristics on the user interface.
|
// Show all the supported services and characteristics on the user interface.
|
||||||
displayGattServices(mBluetoothLeService.getSupportedGattServices());
|
displayGattServices(mBluetoothLeService.getSupportedGattServices());
|
||||||
@@ -160,6 +162,7 @@ public class DeviceControlActivity extends AppCompatActivity {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private void clearUI() {
|
private void clearUI() {
|
||||||
|
mExportString = null;
|
||||||
mGattServicesList.setAdapter((SimpleExpandableListAdapter) null);
|
mGattServicesList.setAdapter((SimpleExpandableListAdapter) null);
|
||||||
mGattUUID.setText(R.string.no_data);
|
mGattUUID.setText(R.string.no_data);
|
||||||
mGattUUIDDesc.setText(R.string.no_data);
|
mGattUUIDDesc.setText(R.string.no_data);
|
||||||
@@ -172,125 +175,34 @@ public class DeviceControlActivity extends AppCompatActivity {
|
|||||||
// on the UI.
|
// on the UI.
|
||||||
private void displayGattServices(final List<BluetoothGattService> gattServices) {
|
private void displayGattServices(final List<BluetoothGattService> gattServices) {
|
||||||
if (gattServices == null) return;
|
if (gattServices == null) return;
|
||||||
generateExportString(gattServices);
|
mExportString = mExporter.generateExportString(
|
||||||
|
mDevice.getName(),
|
||||||
|
mDevice.getAddress(),
|
||||||
|
gattServices);
|
||||||
|
|
||||||
String uuid = null;
|
final GattDataAdapterFactory.GattDataAdapter adapter = GattDataAdapterFactory.createAdapter(this, gattServices);
|
||||||
final String unknownServiceString = getResources().getString(R.string.unknown_service);
|
mGattServicesList.setAdapter(adapter);
|
||||||
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);
|
|
||||||
invalidateOptionsMenu();
|
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
|
@Override
|
||||||
public void onCreate(final Bundle savedInstanceState) {
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_gatt_services);
|
setContentView(R.layout.activity_gatt_services);
|
||||||
|
|
||||||
final Intent intent = getIntent();
|
final Intent intent = getIntent();
|
||||||
final BluetoothLeDevice device = intent.getParcelableExtra(EXTRA_DEVICE);
|
mDevice = intent.getParcelableExtra(EXTRA_DEVICE);
|
||||||
mDeviceName = device.getName();
|
|
||||||
mDeviceAddress = device.getAddress();
|
|
||||||
|
|
||||||
ButterKnife.bind(this);
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
// Sets up UI references.
|
// Sets up UI references.
|
||||||
((TextView) findViewById(R.id.device_address)).setText(mDeviceAddress);
|
((TextView) findViewById(R.id.device_address)).setText(mDevice.getAddress());
|
||||||
mGattServicesList.setOnChildClickListener(servicesListClickListner);
|
mGattServicesList.setOnChildClickListener(servicesListClickListner);
|
||||||
|
|
||||||
getSupportActionBar().setTitle(mDeviceName);
|
getSupportActionBar().setTitle(mDevice.getName());
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
|
||||||
|
mExporter = new Exporter(this);
|
||||||
|
|
||||||
final Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
|
final Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
|
||||||
bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
|
bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
|
||||||
}
|
}
|
||||||
@@ -298,12 +210,26 @@ public class DeviceControlActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(final Menu menu) {
|
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||||
getMenuInflater().inflate(R.menu.gatt_services, menu);
|
getMenuInflater().inflate(R.menu.gatt_services, menu);
|
||||||
if (mConnected) {
|
|
||||||
menu.findItem(R.id.menu_connect).setVisible(false);
|
switch (mCurrentState) {
|
||||||
menu.findItem(R.id.menu_disconnect).setVisible(true);
|
|
||||||
} else {
|
case DISCONNECTED:
|
||||||
menu.findItem(R.id.menu_connect).setVisible(true);
|
menu.findItem(R.id.menu_connect).setVisible(true);
|
||||||
menu.findItem(R.id.menu_disconnect).setVisible(false);
|
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) {
|
if (mExportString == null) {
|
||||||
@@ -326,7 +252,7 @@ public class DeviceControlActivity extends AppCompatActivity {
|
|||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.menu_connect:
|
case R.id.menu_connect:
|
||||||
mBluetoothLeService.connect(mDeviceAddress);
|
mBluetoothLeService.connect(mDevice.getAddress());
|
||||||
return true;
|
return true;
|
||||||
case R.id.menu_disconnect:
|
case R.id.menu_disconnect:
|
||||||
mBluetoothLeService.disconnect();
|
mBluetoothLeService.disconnect();
|
||||||
@@ -336,7 +262,10 @@ public class DeviceControlActivity extends AppCompatActivity {
|
|||||||
return true;
|
return true;
|
||||||
case R.id.menu_share:
|
case R.id.menu_share:
|
||||||
final Intent intent = new Intent(android.content.Intent.ACTION_SEND);
|
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.setType("text/plain");
|
||||||
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject);
|
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject);
|
||||||
@@ -362,31 +291,41 @@ public class DeviceControlActivity extends AppCompatActivity {
|
|||||||
super.onResume();
|
super.onResume();
|
||||||
registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
|
registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
|
||||||
if (mBluetoothLeService != null) {
|
if (mBluetoothLeService != null) {
|
||||||
final boolean result = mBluetoothLeService.connect(mDeviceAddress);
|
final boolean result = mBluetoothLeService.connect(mDevice.getAddress());
|
||||||
Log.d(TAG, "Connect request result=" + result);
|
Log.d(TAG, "Connect request result=" + result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateConnectionState(final int resourceId) {
|
private void updateConnectionState(final State state) {
|
||||||
|
mCurrentState = state;
|
||||||
|
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
final int colourId;
|
final int colourId;
|
||||||
|
final int resId;
|
||||||
|
|
||||||
switch (resourceId) {
|
switch (state) {
|
||||||
case R.string.connected:
|
case CONNECTED:
|
||||||
colourId = android.R.color.holo_green_dark;
|
colourId = android.R.color.holo_green_dark;
|
||||||
|
resId = R.string.connected;
|
||||||
break;
|
break;
|
||||||
case R.string.disconnected:
|
case DISCONNECTED:
|
||||||
colourId = android.R.color.holo_red_dark;
|
colourId = android.R.color.holo_red_dark;
|
||||||
|
resId = R.string.disconnected;
|
||||||
|
break;
|
||||||
|
case CONNECTING:
|
||||||
|
colourId = android.R.color.black;
|
||||||
|
resId = R.string.connecting;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
colourId = android.R.color.black;
|
colourId = android.R.color.black;
|
||||||
|
resId = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
mConnectionState.setText(resourceId);
|
mConnectionState.setText(resId);
|
||||||
mConnectionState.setTextColor(getResources().getColor(colourId));
|
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_DISCONNECTED);
|
||||||
intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);
|
intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);
|
||||||
intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE);
|
intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE);
|
||||||
|
intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTING);
|
||||||
return intentFilter;
|
return intentFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,4 +347,16 @@ public class DeviceControlActivity extends AppCompatActivity {
|
|||||||
return string;
|
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 = "\"";
|
private static final String QUOTE = "\"";
|
||||||
|
|
||||||
public static String addStuff(final Integer text) {
|
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.BluetoothAdapter;
|
||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
import android.content.DialogInterface;
|
import android.os.Build;
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.text.SpannableString;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.text.util.Linkify;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.TextView;
|
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.Bind;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
|
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.R;
|
||||||
import uk.co.alt236.btlescan.adapters.LeDeviceListAdapter;
|
|
||||||
import uk.co.alt236.btlescan.containers.BluetoothLeDeviceStore;
|
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.BluetoothLeScanner;
|
||||||
import uk.co.alt236.btlescan.util.BluetoothUtils;
|
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)
|
@Bind(R.id.tvBluetoothLe)
|
||||||
protected TextView mTvBluetoothLeStatus;
|
protected TextView mTvBluetoothLeStatus;
|
||||||
@Bind(R.id.tvBluetoothStatus)
|
@Bind(R.id.tvBluetoothStatus)
|
||||||
@@ -35,14 +45,15 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
|
|||||||
@Bind(R.id.tvItemCount)
|
@Bind(R.id.tvItemCount)
|
||||||
protected TextView mTvItemCount;
|
protected TextView mTvItemCount;
|
||||||
@Bind(android.R.id.list)
|
@Bind(android.R.id.list)
|
||||||
protected ListView mList;
|
protected RecyclerView mList;
|
||||||
@Bind(android.R.id.empty)
|
@Bind(android.R.id.empty)
|
||||||
protected View mEmpty;
|
protected View mEmpty;
|
||||||
|
|
||||||
|
private RecyclerViewBinderCore mCore;
|
||||||
private BluetoothUtils mBluetoothUtils;
|
private BluetoothUtils mBluetoothUtils;
|
||||||
private BluetoothLeScanner mScanner;
|
private BluetoothLeScanner mScanner;
|
||||||
private LeDeviceListAdapter mLeDeviceListAdapter;
|
|
||||||
private BluetoothLeDeviceStore mDeviceStore;
|
private BluetoothLeDeviceStore mDeviceStore;
|
||||||
|
private DeviceRecyclerAdapter mRecyclerAdapter;
|
||||||
|
|
||||||
private final BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
|
private final BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
|
||||||
@Override
|
@Override
|
||||||
@@ -50,51 +61,33 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
|
|||||||
|
|
||||||
final BluetoothLeDevice deviceLe = new BluetoothLeDevice(device, rssi, scanRecord, System.currentTimeMillis());
|
final BluetoothLeDevice deviceLe = new BluetoothLeDevice(device, rssi, scanRecord, System.currentTimeMillis());
|
||||||
mDeviceStore.addDevice(deviceLe);
|
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() {
|
runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
mLeDeviceListAdapter.swapCursor(c);
|
mRecyclerAdapter.setData(itemList);
|
||||||
updateItemCount(mLeDeviceListAdapter.getCount());
|
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
|
@Override
|
||||||
protected void onCreate(final Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
ButterKnife.bind(this);
|
ButterKnife.bind(this);
|
||||||
mList.setEmptyView(mEmpty);
|
mCore = RecyclerViewCoreFactory.create(this, new Navigation(this));
|
||||||
mList.setOnItemClickListener(this);
|
mList.setLayoutManager(new LinearLayoutManager(this));
|
||||||
mDeviceStore = new BluetoothLeDeviceStore();
|
mDeviceStore = new BluetoothLeDeviceStore();
|
||||||
mBluetoothUtils = new BluetoothUtils(this);
|
mBluetoothUtils = new BluetoothUtils(this);
|
||||||
mScanner = new BluetoothLeScanner(mLeScanCallback, mBluetoothUtils);
|
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);
|
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);
|
menu.findItem(R.id.menu_share).setVisible(true);
|
||||||
} else {
|
} else {
|
||||||
menu.findItem(R.id.menu_share).setVisible(false);
|
menu.findItem(R.id.menu_share).setVisible(false);
|
||||||
@@ -123,32 +116,21 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
|
|||||||
return true;
|
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
|
@Override
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.menu_scan:
|
case R.id.menu_scan:
|
||||||
startScan();
|
startScanPrepare();
|
||||||
break;
|
break;
|
||||||
case R.id.menu_stop:
|
case R.id.menu_stop:
|
||||||
mScanner.scanLeDevice(-1, false);
|
mScanner.scanLeDevice(-1, false);
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
break;
|
break;
|
||||||
case R.id.menu_about:
|
case R.id.menu_about:
|
||||||
displayAboutDialog();
|
DialogFactory.createAboutDialog(this).show();
|
||||||
break;
|
break;
|
||||||
case R.id.menu_share:
|
case R.id.menu_share:
|
||||||
mDeviceStore.shareDataAsEmail(this);
|
new Sharer().shareDataAsEmail(this, mDeviceStore);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -162,16 +144,14 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
|
|||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
final boolean mIsBluetoothOn = mBluetoothUtils.isBluetoothOn();
|
|
||||||
final boolean mIsBluetoothLePresent = mBluetoothUtils.isBluetoothLeSupported();
|
|
||||||
|
|
||||||
if (mIsBluetoothOn) {
|
if (mBluetoothUtils.isBluetoothOn()) {
|
||||||
mTvBluetoothStatus.setText(R.string.on);
|
mTvBluetoothStatus.setText(R.string.on);
|
||||||
} else {
|
} else {
|
||||||
mTvBluetoothStatus.setText(R.string.off);
|
mTvBluetoothStatus.setText(R.string.off);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mIsBluetoothLePresent) {
|
if (mBluetoothUtils.isBluetoothLeSupported()) {
|
||||||
mTvBluetoothLeStatus.setText(R.string.supported);
|
mTvBluetoothLeStatus.setText(R.string.supported);
|
||||||
} else {
|
} else {
|
||||||
mTvBluetoothLeStatus.setText(R.string.not_supported);
|
mTvBluetoothLeStatus.setText(R.string.not_supported);
|
||||||
@@ -180,17 +160,44 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
|
|||||||
invalidateOptionsMenu();
|
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() {
|
private void startScan() {
|
||||||
final boolean mIsBluetoothOn = mBluetoothUtils.isBluetoothOn();
|
final boolean isBluetoothOn = mBluetoothUtils.isBluetoothOn();
|
||||||
final boolean mIsBluetoothLePresent = mBluetoothUtils.isBluetoothLeSupported();
|
final boolean isBluetoothLePresent = mBluetoothUtils.isBluetoothLeSupported();
|
||||||
mDeviceStore.clear();
|
mDeviceStore.clear();
|
||||||
updateItemCount(0);
|
updateItemCount(0);
|
||||||
|
|
||||||
mLeDeviceListAdapter = new LeDeviceListAdapter(this, mDeviceStore.getDeviceCursor());
|
mRecyclerAdapter = new DeviceRecyclerAdapter(mCore);
|
||||||
mList.setAdapter(mLeDeviceListAdapter);
|
mList.setAdapter(mRecyclerAdapter);
|
||||||
|
|
||||||
mBluetoothUtils.askUserToEnableBluetoothIfNeeded();
|
mBluetoothUtils.askUserToEnableBluetoothIfNeeded();
|
||||||
if (mIsBluetoothOn && mIsBluetoothLePresent) {
|
if (isBluetoothOn && isBluetoothLePresent) {
|
||||||
mScanner.scanLeDevice(-1, true);
|
mScanner.scanLeDevice(-1, true);
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
}
|
}
|
||||||
@@ -203,4 +210,10 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
|
|||||||
String.valueOf(count)));
|
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 |
|
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 |
|
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 |
|
After Width: | Height: | Size: 885 B |
|
Before Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
@@ -6,9 +6,9 @@
|
|||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
android:paddingTop="@dimen/activity_vertical_margin">
|
android:paddingTop="@dimen/activity_vertical_margin">
|
||||||
|
|
||||||
<ListView
|
|
||||||
android:id="@android:id/list"
|
<android.support.v7.widget.RecyclerView
|
||||||
android:layout_width="fill_parent"
|
android:id="@+id/recycler"
|
||||||
android:layout_height="wrap_content">
|
android:layout_width="match_parent"
|
||||||
</ListView>
|
android:layout_height="wrap_content"/>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
android:layout_below="@id/deviceInformation"
|
android:layout_below="@id/deviceInformation"
|
||||||
android:background="@android:color/holo_blue_dark"/>
|
android:background="@color/colorSeparator" />
|
||||||
|
|
||||||
<GridLayout
|
<GridLayout
|
||||||
android:id="@+id/gattInformation"
|
android:id="@+id/gattInformation"
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
android:layout_above="@id/gattInformation"
|
android:layout_above="@id/gattInformation"
|
||||||
android:background="@android:color/holo_blue_dark"/>
|
android:background="@color/colorSeparator" />
|
||||||
|
|
||||||
<ExpandableListView
|
<ExpandableListView
|
||||||
android:id="@+id/gatt_services_list"
|
android:id="@+id/gatt_services_list"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
android:paddingTop="@dimen/activity_vertical_margin"
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
tools:context=".MainActivity">
|
tools:context=".ui.main.MainActivity">
|
||||||
|
|
||||||
<GridLayout
|
<GridLayout
|
||||||
android:id="@+id/gridLayout1"
|
android:id="@+id/gridLayout1"
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
android:id="@+id/upperSepparator"
|
android:id="@+id/upperSepparator"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
android:background="@android:color/holo_blue_dark"/>
|
android:background="@color/colorSeparator" />
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
android:id="@+id/lowerSepparator"
|
android:id="@+id/lowerSepparator"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
android:background="@android:color/holo_blue_dark"/>
|
android:background="@color/colorSeparator" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvItemCount"
|
android:id="@+id/tvItemCount"
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<ListView
|
<android.support.v7.widget.RecyclerView
|
||||||
android:id="@android:id/list"
|
android:id="@android:id/list"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"/>
|
android:layout_height="match_parent"/>
|
||||||
|
|||||||
@@ -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
|
Copyright (C) 2013 The Android Open Source Project
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -17,78 +16,24 @@
|
|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
android:gravity="top"
|
android:gravity="top"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/device_icon"
|
android:id="@+id/device_icon"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="36dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="36dp"
|
||||||
android:paddingRight="5dp"
|
android:paddingRight="5dp"
|
||||||
android:paddingTop="5dp"
|
android:paddingTop="5dp"
|
||||||
android:src="@drawable/ic_bluetooth"/>
|
android:src="@drawable/ic_device_ibeacon"/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<include layout="@layout/viewpart_list_item_device_common"/>
|
||||||
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>
|
|
||||||
|
|
||||||
<GridLayout
|
<GridLayout
|
||||||
android:id="@+id/ibeacon_section"
|
android:id="@+id/ibeacon_section"
|
||||||
@@ -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:layout_height="wrap_content"
|
||||||
android:gravity="top"
|
android:gravity="top"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingBottom="5dp">
|
android:paddingBottom="@dimen/space_between_sections">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/title"
|
android:id="@+id/title"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="top"
|
android:gravity="top"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingBottom="5dp">
|
android:paddingBottom="@dimen/space_between_sections">
|
||||||
|
|
||||||
<GridLayout
|
<GridLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -4,20 +4,15 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="top"
|
android:gravity="top"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingBottom="5dp">
|
android:paddingBottom="6dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/title"
|
android:id="@+id/text"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textAllCaps="true"
|
android:textAllCaps="true"
|
||||||
android:textColor="@android:color/holo_blue_dark"
|
android:textColor="@color/colorSeparator"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
android:textStyle="bold"/>
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:background="@android:color/holo_blue_dark"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||