Merge pull request #21 from alt236/app-material-redisign-and-optimisations

App material redisign and optimisations
This commit is contained in:
Alexandros Schillings
2017-01-19 17:24:06 +00:00
committed by GitHub
111 changed files with 2601 additions and 1068 deletions
Generated
-1
View File
@@ -1 +0,0 @@
bluetooth-le-library
+1 -1
View File
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<option name="DEFAULT_COMPILER" value="Javac" />
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
@@ -12,6 +11,7 @@
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
+1 -1
View File
@@ -5,7 +5,6 @@
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="1.8" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
@@ -13,6 +12,7 @@
<option value="$PROJECT_DIR$/sample_app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
-16
View File
@@ -1,16 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<option name="myLocal" value="true" />
<inspection_tool class="GroovyVariableCanBeFinal" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false">
<option name="loggerClassName" value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger" />
<option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
</inspection_tool>
<inspection_tool class="MissingDeprecatedAnnotation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MissingOverrideAnnotation" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreObjectMethods" value="true" />
<option name="ignoreAnonymousClassMethods" value="false" />
</inspection_tool>
</profile>
</component>
-7
View File
@@ -1,7 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Project Default" />
<option name="USE_PROJECT_PROFILE" value="true" />
<version value="1.0" />
</settings>
</component>
+59 -17
View File
@@ -3,6 +3,64 @@
<component name="EntryPointsManager">
<entry_points version="2.0" />
</component>
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectInspectionProfilesVisibleTreeState">
<entry key="Project Default">
<profile-state>
<expanded-state>
<State>
<id />
</State>
<State>
<id>Android</id>
</State>
<State>
<id>Android &gt; Lint &gt; Correctness</id>
</State>
<State>
<id>Android &gt; Lint &gt; Performance</id>
</State>
<State>
<id>CorrectnessLintAndroid</id>
</State>
<State>
<id>General</id>
</State>
<State>
<id>LintAndroid</id>
</State>
</expanded-state>
<selected-state>
<State>
<id>Android</id>
</State>
</selected-state>
</profile-state>
</entry>
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
@@ -13,26 +71,10 @@
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
<component name="masterDetails">
<states>
<state key="ProjectJDKs.UI">
<settings>
<last-edited>Android API 18 Platform</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
</states>
</component>
</project>
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>
-27
View File
@@ -1,27 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run Android Tests" type="AndroidTestRunConfigurationType" factoryName="Android Tests">
<module name="library" />
<option name="TESTING_TYPE" value="0" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="" />
<option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
<option name="USE_LAST_SELECTED_DEVICE" value="true" />
<option name="PREFERRED_AVD" value="" />
<option name="USE_COMMAND_LINE" value="true" />
<option name="COMMAND_LINE" value="" />
<option name="WIPE_USER_DATA" value="false" />
<option name="DISABLE_BOOT_ANIMATION" value="false" />
<option name="NETWORK_SPEED" value="full" />
<option name="NETWORK_LATENCY" value="none" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="true" />
<option name="FILTER_LOGCAT_AUTOMATICALLY" value="true" />
<option name="SELECTED_MATRIX_CONFIGURATION_ID" value="-1" />
<option name="SELECTED_CLOUD_PROJECT_ID" value="Please select a project..." />
<option name="IS_VALID_CLOUD_SELECTION" value="false" />
<option name="INVALID_CLOUD_SELECTION_ERROR" value="Matrix configuration not specified" />
<method />
</configuration>
</component>
-23
View File
@@ -1,23 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run JUnit Tests" type="JUnit" factoryName="JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="library" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" value="" />
<option name="PACKAGE_NAME" value="uk.co.alt236.bluetoothlelib" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="moduleWithDependencies" />
</option>
<envs />
<patterns />
<method />
</configuration>
</component>
-26
View File
@@ -1,26 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run Sample App" type="AndroidRunConfigurationType" factoryName="Android Application">
<module name="sample_app" />
<option name="ACTIVITY_CLASS" value="" />
<option name="MODE" value="default_activity" />
<option name="DEPLOY" value="true" />
<option name="ARTIFACT_NAME" value="" />
<option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
<option name="USE_LAST_SELECTED_DEVICE" value="true" />
<option name="PREFERRED_AVD" value="" />
<option name="USE_COMMAND_LINE" value="true" />
<option name="COMMAND_LINE" value="" />
<option name="WIPE_USER_DATA" value="false" />
<option name="DISABLE_BOOT_ANIMATION" value="false" />
<option name="NETWORK_SPEED" value="full" />
<option name="NETWORK_LATENCY" value="none" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="true" />
<option name="FILTER_LOGCAT_AUTOMATICALLY" value="true" />
<option name="SELECTED_MATRIX_CONFIGURATION_ID" value="-1" />
<option name="SELECTED_CLOUD_PROJECT_ID" value="Please select a project..." />
<option name="IS_VALID_CLOUD_SELECTION" value="false" />
<option name="INVALID_CLOUD_SELECTION_ERROR" value="Matrix configuration not specified" />
<method />
</configuration>
</component>
Generated
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="Bluetooth-LE-Library---Android" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="java-gradle" name="Java-Gradle">
<configuration>
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
<option name="BUILDABLE" value="false" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
</content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+2 -2
View File
@@ -8,12 +8,12 @@
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
</content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+4 -4
View File
@@ -1,16 +1,16 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
ext.compileSdkVersion = 22
ext.buildToolsVersion = "22.0.1"
ext.compileSdkVersion = 24
ext.buildToolsVersion = "24.0.1"
ext.minSdkVersion = 18
ext.targetSdkVersion = 22
ext.targetSdkVersion = 24
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
classpath 'com.android.tools.build:gradle:2.2.3'
}
}
+7
View File
@@ -0,0 +1,7 @@
test:
override:
- (echo "Running JUnit tests!")
- ./gradlew test -PdisablePreDex
post:
- mkdir -p $CIRCLE_TEST_REPORTS/junit/
- find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \;
+2 -2
View File
@@ -1,6 +1,6 @@
#Wed Apr 10 15:27:10 PDT 2013
#Wed Aug 24 14:15:35 BST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
+32 -18
View File
@@ -12,19 +12,19 @@
<option name="SELECTED_TEST_ARTIFACT" value="_unit_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugUnitTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugUnitTestSources" />
<afterSyncTasks>
<task>generateDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
<option name="LIBRARY_PROJECT" value="true" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="false">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
<exclude-output />
@@ -33,8 +33,16 @@
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
@@ -42,6 +50,7 @@
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
@@ -49,6 +58,7 @@
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
@@ -56,6 +66,15 @@
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
@@ -63,32 +82,27 @@
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" />
<orderEntry type="jdk" jdkName="Android API 24 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" scope="TEST" name="objenesis-1.0" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="mockable-android-22" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="hamcrest-core-1.3" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="junit-4.12" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="mockito-core-1.9.5" level="project" />
+15 -2
View File
@@ -13,14 +13,27 @@ repositories {
maven {
url "https://repo.commonsware.com.s3.amazonaws.com"
}
maven {
url "https://s3.amazonaws.com/repo.commonsware.com"
}
maven {
url "https://dl.bintray.com/alt236/maven"
}
}
dependencies {
compile 'com.jakewharton:butterknife:7.0.1'
compile 'com.android.support:appcompat-v7:22.2.0'
compile 'com.commonsware.cwac:merge:1.1.0'
compile 'com.android.support:appcompat-v7:24.2.0'
compile 'com.android.support:recyclerview-v7:24.2.0'
compile 'com.anthonycr.grant:permissions:1.0'
compile 'uk.co.alt236:easycursor-android:1.0.0'
compile fileTree(include: ['*.jar'], dir: 'libs')
compile project(':library')
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-all:1.9.5'
}
android {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.
+68 -26
View File
@@ -12,9 +12,9 @@
<option name="SELECTED_TEST_ARTIFACT" value="_unit_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugUnitTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugUnitTestSources" />
<afterSyncTasks>
<task>generateDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
@@ -23,7 +23,7 @@
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="false">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
<exclude-output />
@@ -32,8 +32,16 @@
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
@@ -41,6 +49,7 @@
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
@@ -48,6 +57,7 @@
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
@@ -55,6 +65,15 @@
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
@@ -62,41 +81,64 @@
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/builds" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/22.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/22.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.commonsware.cwac/merge/1.1.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.commonsware.cwac/sacklist/1.0.2/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/animated-vector-drawable/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-compat/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-core-ui/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-core-utils/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-fragment/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-media-compat/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-vector-drawable/24.2.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.anthonycr.grant/permissions/1.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-runtime-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-verifier" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-support" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/reload-dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/restart-dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" />
<orderEntry type="jdk" jdkName="Android API 24 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" scope="TEST" name="mockable-android-22" level="project" />
<orderEntry type="library" exported="" name="butterknife-7.0.1" level="project" />
<orderEntry type="library" exported="" name="merge-1.1.0" level="project" />
<orderEntry type="library" exported="" name="sacklist-1.0.2" level="project" />
<orderEntry type="library" exported="" name="EasyCursor-0.1.1" level="project" />
<orderEntry type="library" exported="" name="support-annotations-22.2.0" level="project" />
<orderEntry type="library" exported="" name="support-v4-22.2.0" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-22.2.0" level="project" />
<orderEntry type="library" exported="" name="support-media-compat-24.2.0" level="project" />
<orderEntry type="library" exported="" name="recyclerview-v7-24.2.0" level="project" />
<orderEntry type="library" exported="" name="support-compat-24.2.0" level="project" />
<orderEntry type="library" exported="" name="support-v4-24.2.0" level="project" />
<orderEntry type="library" exported="" name="support-core-ui-24.2.0" level="project" />
<orderEntry type="library" exported="" name="support-core-utils-24.2.0" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-24.2.0" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="mockito-all-1.9.5" level="project" />
<orderEntry type="library" exported="" name="support-fragment-24.2.0" level="project" />
<orderEntry type="library" exported="" name="support-annotations-24.2.0" level="project" />
<orderEntry type="library" exported="" name="support-vector-drawable-24.2.0" level="project" />
<orderEntry type="library" exported="" name="animated-vector-drawable-24.2.0" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="hamcrest-core-1.3" level="project" />
<orderEntry type="library" exported="" name="permissions-1.0" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="junit-4.12" level="project" />
<orderEntry type="library" exported="" name="easycursor-android-1.0.0" level="project" />
<orderEntry type="module" module-name="library" exported="" />
<orderEntry type="library" exported="" scope="TEST" name="objenesis-1.0" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="mockito-core-1.9.5" level="project" />
</component>
</module>
+14 -16
View File
@@ -1,38 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="uk.co.alt236.btlescan"
xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="uk.co.alt236.btlescan">
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="false"/>
android:required="false" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name="uk.co.alt236.btlescan.activities.MainActivity"
android:name=".ui.main.MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="uk.co.alt236.btlescan.activities.DeviceDetailsActivity"
android:label="@string/app_name">
</activity>
<activity android:name="uk.co.alt236.btlescan.activities.DeviceControlActivity"/>
<activity android:name=".ui.details.DeviceDetailsActivity" />
<activity android:name=".ui.control.DeviceControlActivity" />
<service
android:name="uk.co.alt236.btlescan.services.BluetoothLeService"
android:enabled="true"/>
android:enabled="true" />
</application>
</manifest>
@@ -1,240 +0,0 @@
package uk.co.alt236.btlescan.activities;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import com.commonsware.cwac.merge.MergeAdapter;
import java.util.Collection;
import java.util.Locale;
import butterknife.Bind;
import butterknife.ButterKnife;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.bluetoothlelib.device.BluetoothService;
import uk.co.alt236.bluetoothlelib.device.adrecord.AdRecord;
import uk.co.alt236.bluetoothlelib.device.beacon.BeaconType;
import uk.co.alt236.bluetoothlelib.device.beacon.BeaconUtils;
import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconManufacturerData;
import uk.co.alt236.bluetoothlelib.resolvers.CompanyIdentifierResolver;
import uk.co.alt236.bluetoothlelib.util.AdRecordUtils;
import uk.co.alt236.bluetoothlelib.util.ByteUtils;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.util.TimeFormatter;
public class DeviceDetailsActivity extends AppCompatActivity {
public static final String EXTRA_DEVICE = "extra_device";
@Bind(android.R.id.list)
protected ListView mList;
@Nullable
@Bind(android.R.id.empty)
protected View mEmpty;
private BluetoothLeDevice mDevice;
private void appendAdRecordView(final MergeAdapter adapter, final String title, final AdRecord record) {
final LinearLayout lt = (LinearLayout) getLayoutInflater().inflate(R.layout.list_item_view_adrecord, null);
final TextView tvString = (TextView) lt.findViewById(R.id.data_as_string);
final TextView tvArray = (TextView) lt.findViewById(R.id.data_as_array);
final TextView tvTitle = (TextView) lt.findViewById(R.id.title);
tvTitle.setText(title);
tvString.setText("'" + AdRecordUtils.getRecordDataAsString(record) + "'");
tvArray.setText("'" + ByteUtils.byteArrayToHexString(record.getData()) + "'");
adapter.addView(lt);
}
private void appendDeviceInfo(final MergeAdapter adapter, final BluetoothLeDevice device) {
final LinearLayout lt = (LinearLayout) getLayoutInflater().inflate(R.layout.list_item_view_device_info, null);
final TextView tvName = (TextView) lt.findViewById(R.id.deviceName);
final TextView tvAddress = (TextView) lt.findViewById(R.id.deviceAddress);
final TextView tvClass = (TextView) lt.findViewById(R.id.deviceClass);
final TextView tvMajorClass = (TextView) lt.findViewById(R.id.deviceMajorClass);
final TextView tvServices = (TextView) lt.findViewById(R.id.deviceServiceList);
final TextView tvBondingState = (TextView) lt.findViewById(R.id.deviceBondingState);
tvName.setText(device.getName());
tvAddress.setText(device.getAddress());
tvClass.setText(device.getBluetoothDeviceClassName());
tvMajorClass.setText(device.getBluetoothDeviceMajorClassName());
tvBondingState.setText(device.getBluetoothDeviceBondState());
final String supportedServices;
if(device.getBluetoothDeviceKnownSupportedServices().isEmpty()){
supportedServices = getString(R.string.no_known_services);
} else {
final StringBuilder sb = new StringBuilder();
for(final BluetoothService service : device.getBluetoothDeviceKnownSupportedServices()){
if(sb.length() > 0){
sb.append(", ");
}
sb.append(service);
}
supportedServices = sb.toString();
}
tvServices.setText(supportedServices);
adapter.addView(lt);
}
private void appendHeader(final MergeAdapter adapter, final String title) {
final LinearLayout lt = (LinearLayout) getLayoutInflater().inflate(R.layout.list_item_view_header, null);
final TextView tvTitle = (TextView) lt.findViewById(R.id.title);
tvTitle.setText(title);
adapter.addView(lt);
}
private void appendIBeaconInfo(final MergeAdapter adapter, final IBeaconManufacturerData iBeaconData) {
final LinearLayout lt = (LinearLayout) getLayoutInflater().inflate(R.layout.list_item_view_ibeacon_details, null);
final TextView tvCompanyId = (TextView) lt.findViewById(R.id.companyId);
final TextView tvAdvert = (TextView) lt.findViewById(R.id.advertisement);
final TextView tvUUID = (TextView) lt.findViewById(R.id.uuid);
final TextView tvMajor = (TextView) lt.findViewById(R.id.major);
final TextView tvMinor = (TextView) lt.findViewById(R.id.minor);
final TextView tvTxPower = (TextView) lt.findViewById(R.id.txpower);
tvCompanyId.setText(
CompanyIdentifierResolver.getCompanyName(iBeaconData.getCompanyIdentifier(), getString(R.string.unknown))
+ " (" + hexEncode(iBeaconData.getCompanyIdentifier()) + ")");
tvAdvert.setText(iBeaconData.getIBeaconAdvertisement() + " (" + hexEncode(iBeaconData.getIBeaconAdvertisement()) + ")");
tvUUID.setText(iBeaconData.getUUID());
tvMajor.setText(iBeaconData.getMajor() + " (" + hexEncode(iBeaconData.getMajor()) + ")");
tvMinor.setText(iBeaconData.getMinor() + " (" + hexEncode(iBeaconData.getMinor()) + ")");
tvTxPower.setText(iBeaconData.getCalibratedTxPower() + " (" + hexEncode(iBeaconData.getCalibratedTxPower()) + ")");
adapter.addView(lt);
}
private void appendRssiInfo(final MergeAdapter adapter, final BluetoothLeDevice device) {
final LinearLayout lt = (LinearLayout) getLayoutInflater().inflate(R.layout.list_item_view_rssi_info, null);
final TextView tvFirstTimestamp = (TextView) lt.findViewById(R.id.firstTimestamp);
final TextView tvFirstRssi = (TextView) lt.findViewById(R.id.firstRssi);
final TextView tvLastTimestamp = (TextView) lt.findViewById(R.id.lastTimestamp);
final TextView tvLastRssi = (TextView) lt.findViewById(R.id.lastRssi);
final TextView tvRunningAverageRssi = (TextView) lt.findViewById(R.id.runningAverageRssi);
tvFirstTimestamp.setText(formatTime(device.getFirstTimestamp()));
tvFirstRssi.setText(formatRssi(device.getFirstRssi()));
tvLastTimestamp.setText(formatTime(device.getTimestamp()));
tvLastRssi.setText(formatRssi(device.getRssi()));
tvRunningAverageRssi.setText(formatRssi(device.getRunningAverageRssi()));
adapter.addView(lt);
}
private void appendSimpleText(final MergeAdapter adapter, final byte[] data) {
appendSimpleText(adapter, ByteUtils.byteArrayToHexString(data));
}
private void appendSimpleText(final MergeAdapter adapter, final String data) {
final LinearLayout lt = (LinearLayout) getLayoutInflater().inflate(R.layout.list_item_view_textview, null);
final TextView tvData = (TextView) lt.findViewById(R.id.data);
tvData.setText(data);
adapter.addView(lt);
}
private String formatRssi(final double rssi) {
return getString(R.string.formatter_db, String.valueOf(rssi));
}
private String formatRssi(final int rssi) {
return getString(R.string.formatter_db, String.valueOf(rssi));
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_details);
ButterKnife.bind(this);
mList.setEmptyView(mEmpty);
mDevice = getIntent().getParcelableExtra(EXTRA_DEVICE);
pupulateDetails(mDevice);
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.details, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_connect:
final Intent intent = new Intent(this, DeviceControlActivity.class);
intent.putExtra(DeviceControlActivity.EXTRA_DEVICE, mDevice);
startActivity(intent);
break;
}
return true;
}
private void pupulateDetails(final BluetoothLeDevice device) {
final MergeAdapter adapter = new MergeAdapter();
if (device == null) {
appendHeader(adapter, getString(R.string.header_device_info));
appendSimpleText(adapter, getString(R.string.invalid_device_data));
} else {
appendHeader(adapter, getString(R.string.header_device_info));
appendDeviceInfo(adapter, device);
appendHeader(adapter, getString(R.string.header_rssi_info));
appendRssiInfo(adapter, device);
appendHeader(adapter, getString(R.string.header_scan_record));
appendSimpleText(adapter, device.getScanRecord());
final Collection<AdRecord> adRecords = device.getAdRecordStore().getRecordsAsCollection();
if (adRecords.size() > 0) {
appendHeader(adapter, getString(R.string.header_raw_ad_records));
for (final AdRecord record : adRecords) {
appendAdRecordView(
adapter,
"#" + record.getType() + " " + record.getHumanReadableType(),
record);
}
}
final boolean isIBeacon = BeaconUtils.getBeaconType(device) == BeaconType.IBEACON;
if (isIBeacon) {
final IBeaconManufacturerData iBeaconData = new IBeaconManufacturerData(device);
appendHeader(adapter, getString(R.string.header_ibeacon_data));
appendIBeaconInfo(adapter, iBeaconData);
}
}
mList.setAdapter(adapter);
}
private static String formatTime(final long time) {
return TimeFormatter.getIsoDateTime(time);
}
private static String hexEncode(final int integer) {
return "0x" + Integer.toHexString(integer).toUpperCase(Locale.US);
}
}
@@ -1,126 +0,0 @@
package uk.co.alt236.btlescan.adapters;
import android.app.Activity;
import android.support.v4.widget.SimpleCursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.bluetoothlelib.device.beacon.BeaconType;
import uk.co.alt236.bluetoothlelib.device.beacon.BeaconUtils;
import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconDevice;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.util.Constants;
import uk.co.alt236.easycursor.objectcursor.EasyObjectCursor;
// Adapter for holding devices found through scanning.
public class LeDeviceListAdapter extends SimpleCursorAdapter {
private final LayoutInflater mInflator;
private final Activity mActivity;
public LeDeviceListAdapter(final Activity activity, final EasyObjectCursor<BluetoothLeDevice> cursor) {
super(activity, R.layout.list_item_device, cursor, new String[0], new int[0], 0);
mInflator = activity.getLayoutInflater();
mActivity = activity;
}
@SuppressWarnings("unchecked")
@Override
public EasyObjectCursor<BluetoothLeDevice> getCursor() {
return ((EasyObjectCursor<BluetoothLeDevice>) super.getCursor());
}
@Override
public BluetoothLeDevice getItem(final int i) {
return getCursor().getItem(i);
}
@Override
public long getItemId(final int i) {
return i;
}
@Override
public View getView(final int i, View view, final ViewGroup viewGroup) {
final ViewHolder viewHolder;
// General ListView optimization code.
if (view == null) {
view = mInflator.inflate(R.layout.list_item_device, null);
viewHolder = new ViewHolder();
viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address);
viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name);
viewHolder.deviceRssi = (TextView) view.findViewById(R.id.device_rssi);
viewHolder.deviceIcon = (ImageView) view.findViewById(R.id.device_icon);
viewHolder.deviceLastUpdated = (TextView) view.findViewById(R.id.device_last_update);
viewHolder.ibeaconMajor = (TextView) view.findViewById(R.id.ibeacon_major);
viewHolder.ibeaconMinor = (TextView) view.findViewById(R.id.ibeacon_minor);
viewHolder.ibeaconDistance = (TextView) view.findViewById(R.id.ibeacon_distance);
viewHolder.ibeaconUUID = (TextView) view.findViewById(R.id.ibeacon_uuid);
viewHolder.ibeaconTxPower = (TextView) view.findViewById(R.id.ibeacon_tx_power);
viewHolder.ibeaconSection = view.findViewById(R.id.ibeacon_section);
viewHolder.ibeaconDistanceDescriptor = (TextView) view.findViewById(R.id.ibeacon_distance_descriptor);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
final BluetoothLeDevice device = getCursor().getItem(i);
final String deviceName = device.getName();
final double rssi = device.getRssi();
if (deviceName != null && deviceName.length() > 0) {
viewHolder.deviceName.setText(deviceName);
} else {
viewHolder.deviceName.setText(R.string.unknown_device);
}
if (BeaconUtils.getBeaconType(device) == BeaconType.IBEACON) {
final IBeaconDevice iBeacon = new IBeaconDevice(device);
final String accuracy = Constants.DOUBLE_TWO_DIGIT_ACCURACY.format(iBeacon.getAccuracy());
viewHolder.deviceIcon.setImageResource(R.drawable.ic_device_ibeacon);
viewHolder.ibeaconSection.setVisibility(View.VISIBLE);
viewHolder.ibeaconMajor.setText(String.valueOf(iBeacon.getMajor()));
viewHolder.ibeaconMinor.setText(String.valueOf(iBeacon.getMinor()));
viewHolder.ibeaconTxPower.setText(String.valueOf(iBeacon.getCalibratedTxPower()));
viewHolder.ibeaconUUID.setText(iBeacon.getUUID());
viewHolder.ibeaconDistance.setText(
mActivity.getString(R.string.formatter_meters, accuracy));
viewHolder.ibeaconDistanceDescriptor.setText(iBeacon.getDistanceDescriptor().toString());
} else {
viewHolder.deviceIcon.setImageResource(R.drawable.ic_bluetooth);
viewHolder.ibeaconSection.setVisibility(View.GONE);
}
final String rssiString =
mActivity.getString(R.string.formatter_db, String.valueOf(rssi));
final String runningAverageRssiString =
mActivity.getString(R.string.formatter_db, String.valueOf(device.getRunningAverageRssi()));
viewHolder.deviceLastUpdated.setText(
android.text.format.DateFormat.format(
Constants.TIME_FORMAT, new java.util.Date(device.getTimestamp())));
viewHolder.deviceAddress.setText(device.getAddress());
viewHolder.deviceRssi.setText(rssiString + " / " + runningAverageRssiString);
return view;
}
static class ViewHolder {
TextView deviceName;
TextView deviceAddress;
TextView deviceRssi;
TextView ibeaconUUID;
TextView ibeaconMajor;
TextView ibeaconMinor;
TextView ibeaconTxPower;
TextView ibeaconDistance;
TextView ibeaconDistanceDescriptor;
TextView deviceLastUpdated;
View ibeaconSection;
ImageView deviceIcon;
}
}
@@ -1,39 +1,26 @@
package uk.co.alt236.btlescan.containers;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.support.annotation.NonNull;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.bluetoothlelib.device.beacon.BeaconType;
import uk.co.alt236.bluetoothlelib.device.beacon.BeaconUtils;
import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconDevice;
import uk.co.alt236.bluetoothlelib.util.ByteUtils;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.util.CsvWriterHelper;
import uk.co.alt236.btlescan.util.TimeFormatter;
import uk.co.alt236.easycursor.objectcursor.EasyObjectCursor;
public class BluetoothLeDeviceStore {
private static final BluetoothLeDeviceComparator DEFAULT_COMPARATOR = new BluetoothLeDeviceComparator();
private final Map<String, BluetoothLeDevice> mDeviceMap;
public BluetoothLeDeviceStore() {
mDeviceMap = new HashMap<>();
}
public void addDevice(final BluetoothLeDevice device) {
public void addDevice(@NonNull final BluetoothLeDevice device) {
if (mDeviceMap.containsKey(device.getAddress())) {
mDeviceMap.get(device.getAddress()).updateRssiReading(device.getTimestamp(), device.getRssi());
} else {
@@ -45,137 +32,42 @@ public class BluetoothLeDeviceStore {
mDeviceMap.clear();
}
public int getSize() {
return mDeviceMap.size();
}
@NonNull
public EasyObjectCursor<BluetoothLeDevice> getDeviceCursor() {
return getDeviceCursor(DEFAULT_COMPARATOR);
}
@NonNull
public EasyObjectCursor<BluetoothLeDevice> getDeviceCursor(@NonNull Comparator<BluetoothLeDevice> comparator) {
return new EasyObjectCursor<>(
BluetoothLeDevice.class,
getDeviceList(),
getDeviceList(comparator),
"address");
}
@NonNull
public List<BluetoothLeDevice> getDeviceList() {
return getDeviceList(DEFAULT_COMPARATOR);
}
@NonNull
public List<BluetoothLeDevice> getDeviceList(@NonNull Comparator<BluetoothLeDevice> comparator) {
final List<BluetoothLeDevice> methodResult = new ArrayList<>(mDeviceMap.values());
Collections.sort(methodResult, new Comparator<BluetoothLeDevice>() {
@Override
public int compare(final BluetoothLeDevice arg0, final BluetoothLeDevice arg1) {
return arg0.getAddress().compareToIgnoreCase(arg1.getAddress());
}
});
Collections.sort(methodResult, comparator);
return methodResult;
}
private String getListAsCsv() {
final List<BluetoothLeDevice> list = getDeviceList();
final StringBuilder sb = new StringBuilder();
sb.append(CsvWriterHelper.addStuff("mac"));
sb.append(CsvWriterHelper.addStuff("name"));
sb.append(CsvWriterHelper.addStuff("firstTimestamp"));
sb.append(CsvWriterHelper.addStuff("firstRssi"));
sb.append(CsvWriterHelper.addStuff("currentTimestamp"));
sb.append(CsvWriterHelper.addStuff("currentRssi"));
sb.append(CsvWriterHelper.addStuff("adRecord"));
sb.append(CsvWriterHelper.addStuff("iBeacon"));
sb.append(CsvWriterHelper.addStuff("uuid"));
sb.append(CsvWriterHelper.addStuff("major"));
sb.append(CsvWriterHelper.addStuff("minor"));
sb.append(CsvWriterHelper.addStuff("txPower"));
sb.append(CsvWriterHelper.addStuff("distance"));
sb.append(CsvWriterHelper.addStuff("accuracy"));
sb.append('\n');
private static class BluetoothLeDeviceComparator implements Comparator<BluetoothLeDevice> {
for (final BluetoothLeDevice device : list) {
sb.append(CsvWriterHelper.addStuff(device.getAddress()));
sb.append(CsvWriterHelper.addStuff(device.getName()));
sb.append(CsvWriterHelper.addStuff(TimeFormatter.getIsoDateTime(device.getFirstTimestamp())));
sb.append(CsvWriterHelper.addStuff(device.getFirstRssi()));
sb.append(CsvWriterHelper.addStuff(TimeFormatter.getIsoDateTime(device.getTimestamp())));
sb.append(CsvWriterHelper.addStuff(device.getRssi()));
sb.append(CsvWriterHelper.addStuff(ByteUtils.byteArrayToHexString(device.getScanRecord())));
final boolean isIBeacon = BeaconUtils.getBeaconType(device) == BeaconType.IBEACON;
final String uuid;
final String minor;
final String major;
final String txPower;
final String distance;
final String accuracy;
if (isIBeacon) {
final IBeaconDevice beacon = new IBeaconDevice(device);
uuid = String.valueOf(beacon.getUUID());
minor = String.valueOf(beacon.getMinor());
major = String.valueOf(beacon.getMajor());
txPower = String.valueOf(beacon.getCalibratedTxPower());
distance = beacon.getDistanceDescriptor().toString().toLowerCase(Locale.US);
accuracy = String.valueOf(beacon.getAccuracy());
} else {
uuid = "";
minor = "";
major = "";
txPower = "";
distance = "";
accuracy = "";
}
sb.append(CsvWriterHelper.addStuff(isIBeacon));
sb.append(CsvWriterHelper.addStuff(uuid));
sb.append(CsvWriterHelper.addStuff(minor));
sb.append(CsvWriterHelper.addStuff(major));
sb.append(CsvWriterHelper.addStuff(txPower));
sb.append(CsvWriterHelper.addStuff(distance));
sb.append(CsvWriterHelper.addStuff(accuracy));
sb.append('\n');
@Override
public int compare(final BluetoothLeDevice arg0, final BluetoothLeDevice arg1) {
return arg0.getAddress().compareTo(arg1.getAddress());
}
return sb.toString();
}
public void shareDataAsEmail(final Context context) {
final long timeInMillis = System.currentTimeMillis();
final String to = null;
final String subject = context.getString(
R.string.exporter_email_device_list_subject,
TimeFormatter.getIsoDateTime(timeInMillis));
final String message = context.getString(R.string.exporter_email_device_list_body);
final Intent i = new Intent(Intent.ACTION_SEND);
i.setType("plain/text");
try {
final File outputDir = context.getCacheDir();
final File outputFile = File.createTempFile("bluetooth_le_" + timeInMillis, ".csv", outputDir);
outputFile.setReadable(true, false);
generateFile(outputFile, getListAsCsv());
i.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(outputFile));
i.putExtra(Intent.EXTRA_EMAIL, new String[]{to});
i.putExtra(Intent.EXTRA_SUBJECT, subject);
i.putExtra(Intent.EXTRA_TEXT, message);
context.startActivity(Intent.createChooser(i, context.getString(R.string.exporter_email_device_list_picker_text)));
} catch (final IOException e) {
e.printStackTrace();
}
}
private static FileWriter generateFile(final File file, final String contents) {
FileWriter writer = null;
try {
writer = new FileWriter(file);
writer.append(contents);
writer.flush();
} catch (final IOException e) {
e.printStackTrace();
} finally {
try {
writer.close();
} catch (final IOException e) {
e.printStackTrace();
}
}
return writer;
}
}
@@ -38,22 +38,21 @@ import java.util.List;
* given Bluetooth LE device.
*/
public class BluetoothLeService extends Service {
public final static String ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED";
public final static String ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
public final static String ACTION_GATT_SERVICES_DISCOVERED = "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
public final static String ACTION_DATA_AVAILABLE = "com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
public final static String EXTRA_DATA_RAW = "com.example.bluetooth.le.EXTRA_DATA_RAW";
public final static String EXTRA_UUID_CHAR = "com.example.bluetooth.le.EXTRA_UUID_CHAR";
public final static String ACTION_GATT_CONNECTED = BluetoothLeService.class.getName() + ".ACTION_GATT_CONNECTED";
public final static String ACTION_GATT_CONNECTING = BluetoothLeService.class.getName() + ".ACTION_GATT_CONNECTING";
public final static String ACTION_GATT_DISCONNECTED = BluetoothLeService.class.getName() + ".ACTION_GATT_DISCONNECTED";
public final static String ACTION_GATT_SERVICES_DISCOVERED = BluetoothLeService.class.getName() + ".ACTION_GATT_SERVICES_DISCOVERED";
public final static String ACTION_DATA_AVAILABLE = BluetoothLeService.class.getName() + ".ACTION_DATA_AVAILABLE";
public final static String EXTRA_DATA_RAW = BluetoothLeService.class.getName() + ".EXTRA_DATA_RAW";
public final static String EXTRA_UUID_CHAR = BluetoothLeService.class.getName() + ".EXTRA_UUID_CHAR";
private final static String TAG = BluetoothLeService.class.getSimpleName();
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
private static final int STATE_CONNECTED = 2;
private final IBinder mBinder = new LocalBinder();
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private String mBluetoothDeviceAddress;
private BluetoothGatt mBluetoothGatt;
private int mConnectionState = STATE_DISCONNECTED;
private State mConnectionState = State.DISCONNECTED;
// Implements callback methods for GATT events that the app cares about. For example,
// connection change and services discovered.
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@@ -63,28 +62,35 @@ public class BluetoothLeService extends Service {
}
@Override
public void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
public void onCharacteristicRead(final BluetoothGatt gatt,
final BluetoothGattCharacteristic characteristic,
final int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
@Override
public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
final String intentAction;
public void onConnectionStateChange(final BluetoothGatt gatt,
final int status,
final int newState) {
Log.d(TAG, "onConnectionStateChange: status=" + status + ", newState=" + newState);
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
mConnectionState = STATE_CONNECTED;
broadcastUpdate(intentAction);
setConnectionState(State.CONNECTED, true);
Log.i(TAG, "Connected to GATT server.");
// Attempts to discover services after successful connection.
Log.i(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices());
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_GATT_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
// Make sure we tidy up. On certain devices reusing a Gatt after a disconnection
// can cause problems.
disconnect();
setConnectionState(State.DISCONNECTED, true);
Log.i(TAG, "Disconnected from GATT server.");
broadcastUpdate(intentAction);
}
}
@@ -138,37 +144,73 @@ public class BluetoothLeService extends Service {
* callback.
*/
public boolean connect(final String address) {
final boolean retVal;
if (mBluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
retVal = false;
// Previously connected device. Try to reconnect.
if (mBluetoothDeviceAddress != null
// Previously connected device. Try to reconnect.
} else if (mBluetoothDeviceAddress != null
&& address.equals(mBluetoothDeviceAddress)
&& mBluetoothGatt != null) {
Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
if (mBluetoothGatt.connect()) {
mConnectionState = STATE_CONNECTING;
return true;
Log.d(TAG, "Connection attempt OK.");
setConnectionState(State.CONNECTING, true);
retVal = true;
} else {
return false;
Log.w(TAG, "Connection attempt failed.");
setConnectionState(State.DISCONNECTED, true);
retVal = false;
}
} else {
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
retVal = false;
} else {
// We want to directly connect to the device, so we are setting the autoConnect
// parameter to false.
Log.d(TAG, "Trying to create a new connection.");
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
mBluetoothDeviceAddress = address;
setConnectionState(State.CONNECTING, true);
retVal = true;
}
}
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
return retVal;
}
private synchronized void setConnectionState(final State newState, final boolean broadCast) {
Log.i(TAG, "Setting internal state to " + newState);
mConnectionState = newState;
final String broadcastAction;
switch (newState) {
case CONNECTED:
broadcastAction = ACTION_GATT_CONNECTED;
break;
case CONNECTING:
broadcastAction = ACTION_GATT_CONNECTING;
break;
case DISCONNECTED:
broadcastAction = ACTION_GATT_DISCONNECTED;
break;
default:
throw new IllegalArgumentException("Unknown state: " + newState);
}
// We want to directly connect to the device, so we are setting the autoConnect
// parameter to false.
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
Log.d(TAG, "Trying to create a new connection.");
mBluetoothDeviceAddress = address;
mConnectionState = STATE_CONNECTING;
return true;
if (broadCast) {
Log.i(TAG, "Broadcasting " + broadcastAction);
broadcastUpdate(broadcastAction);
}
}
/**
@@ -183,6 +225,9 @@ public class BluetoothLeService extends Service {
return;
}
mBluetoothGatt.disconnect();
// Reusing a Gatt after disconnecting can cause problems
mBluetoothGatt = null;
}
/**
@@ -270,4 +315,10 @@ public class BluetoothLeService extends Service {
return BluetoothLeService.this;
}
}
private enum State {
DISCONNECTED,
CONNECTING,
CONNECTED
}
}
@@ -0,0 +1,50 @@
package uk.co.alt236.btlescan.ui.common;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.support.v4.app.ActivityCompat;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.control.DeviceControlActivity;
import uk.co.alt236.btlescan.ui.details.DeviceDetailsActivity;
public class Navigation {
private final Activity mActivity;
public Navigation(final Activity activity) {
mActivity = activity;
}
public void openDetailsActivity(final BluetoothLeDevice device) {
final Intent intent = DeviceDetailsActivity.createIntent(mActivity, device);
startActivity(intent);
}
public void startControlActivity(final BluetoothLeDevice device) {
final Intent intent = DeviceControlActivity.createIntent(mActivity, device);
startActivity(intent);
}
public void shareFileViaEmail(final Uri fileUri, final String[] recipient, final String subject, final String message) {
final Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("plain/text");
intent.putExtra(Intent.EXTRA_STREAM, fileUri);
intent.putExtra(Intent.EXTRA_EMAIL, recipient);
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
intent.putExtra(Intent.EXTRA_TEXT, message);
startActivity(Intent.createChooser(intent,
mActivity.getString(R.string.exporter_email_device_list_picker_text)));
}
private void startActivity(final Intent intent) {
ActivityCompat.startActivity(mActivity, intent, null);
}
}
@@ -0,0 +1,69 @@
package uk.co.alt236.btlescan.ui.common.recyclerview;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public abstract class BaseRecyclerViewAdapter extends RecyclerView.Adapter<BaseViewHolder<? extends RecyclerViewItem>> {
private final List<RecyclerViewItem> mItemList;
private final RecyclerViewBinderCore mCore;
public BaseRecyclerViewAdapter(final RecyclerViewBinderCore core,
final List<RecyclerViewItem> items) {
mItemList = new ArrayList<>();
mCore = core;
mItemList.addAll(items);
}
public BaseRecyclerViewAdapter(final RecyclerViewBinderCore core) {
this(core, new ArrayList<RecyclerViewItem>());
}
@Override
public BaseViewHolder<? extends RecyclerViewItem> onCreateViewHolder(final ViewGroup parent,
final int viewType) {
return mCore.create(parent, viewType);
}
@Override
public void onBindViewHolder(final BaseViewHolder<? extends RecyclerViewItem> holder,
final int position) {
final int viewType = getItemViewType(position);
final BaseViewBinder<? extends RecyclerViewItem> binder = mCore.getBinder(viewType);
bind(binder, holder, getItem(position));
}
@Override
public int getItemCount() {
return mItemList.size();
}
@Override
public int getItemViewType(int position) {
return mCore.getViewType(getItem(position));
}
public RecyclerViewItem getItem(final int position) {
return mItemList.get(position);
}
public void setData(Collection<? extends RecyclerViewItem> data) {
mItemList.clear();
mItemList.addAll(data);
notifyDataSetChanged();
}
private static <T extends RecyclerViewItem> void bind(final BaseViewBinder<T> binder,
final BaseViewHolder<?> holder,
final RecyclerViewItem item) {
//noinspection unchecked
binder.bind((BaseViewHolder<T>) holder, (T) item);
}
}
@@ -0,0 +1,20 @@
package uk.co.alt236.btlescan.ui.common.recyclerview;
import android.content.Context;
public abstract class BaseViewBinder<T extends RecyclerViewItem> {
private final Context mContext;
public BaseViewBinder(final Context context) {
mContext = context;
}
public abstract void bind(BaseViewHolder<T> holder, T item);
public abstract boolean canBind(RecyclerViewItem item);
protected Context getContext() {
return mContext;
}
}
@@ -0,0 +1,18 @@
package uk.co.alt236.btlescan.ui.common.recyclerview;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public abstract class BaseViewHolder<T extends RecyclerViewItem> extends RecyclerView.ViewHolder {
private final View mItemView;
public BaseViewHolder(final View itemView) {
super(itemView);
mItemView = itemView;
}
public View getView() {
return mItemView;
}
}
@@ -0,0 +1,99 @@
package uk.co.alt236.btlescan.ui.common.recyclerview;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
public class RecyclerViewBinderCore {
public static final int INVALID_VIEWTYPE = -1;
private static final String TAG = RecyclerViewBinderCore.class.getSimpleName();
private final List<Class<? extends BaseViewHolder<? extends RecyclerViewItem>>> mViewHolderClasses;
private final List<BaseViewBinder<? extends RecyclerViewItem>> mViewBinders;
private final List<Integer> mLayoutIds;
public RecyclerViewBinderCore() {
mViewBinders = new ArrayList<>();
mViewHolderClasses = new ArrayList<>();
mLayoutIds = new ArrayList<>();
}
public void clear() {
mViewBinders.clear();
mViewHolderClasses.clear();
mLayoutIds.clear();
}
public <T extends RecyclerViewItem> void add(
final BaseViewBinder<T> binder,
final Class<? extends BaseViewHolder<T>> viewHolder,
final int layoutId) {
mViewBinders.add(binder);
mViewHolderClasses.add(viewHolder);
mLayoutIds.add(layoutId);
}
public BaseViewHolder<? extends RecyclerViewItem> create(ViewGroup parent, final int viewType) {
if (viewType == INVALID_VIEWTYPE) {
throw new IllegalArgumentException("Invalid viewType: " + viewType);
}
final Class<?> clazz = mViewHolderClasses.get(viewType);
final int layoutId = mLayoutIds.get(viewType);
final View itemView = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
return (BaseViewHolder<? extends RecyclerViewItem>) instantiate(clazz, itemView);
}
public <T extends RecyclerViewItem> int getViewType(final T item) {
int result = INVALID_VIEWTYPE;
int count = 0;
for (final BaseViewBinder<? extends RecyclerViewItem> binder : mViewBinders) {
if (binder.canBind(item)) {
result = count;
break;
}
count++;
}
if (result == INVALID_VIEWTYPE) {
Log.w(TAG, "Could not get viewType for " + item);
}
return result;
}
public BaseViewBinder<? extends RecyclerViewItem> getBinder(int viewType) {
if (viewType == INVALID_VIEWTYPE) {
throw new IllegalArgumentException("Invalid viewType: " + viewType);
}
return mViewBinders.get(viewType);
}
@SuppressWarnings("TryWithIdenticalCatches")
private static Object instantiate(
final Class<?> clazz, View parentView) {
try {
final Constructor<?> constructor
= clazz.getDeclaredConstructors()[0];
return constructor.newInstance(parentView);
} catch (InstantiationException e) {
throw new IllegalStateException(e);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
} catch (InvocationTargetException e) {
throw new IllegalStateException(e);
}
}
}
@@ -0,0 +1,4 @@
package uk.co.alt236.btlescan.ui.common.recyclerview;
public interface RecyclerViewItem {
}
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package uk.co.alt236.btlescan.activities;
package uk.co.alt236.btlescan.ui.control;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
@@ -26,6 +26,7 @@ import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
@@ -35,10 +36,7 @@ import android.widget.ExpandableListView;
import android.widget.SimpleExpandableListAdapter;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import butterknife.Bind;
import butterknife.ButterKnife;
@@ -55,10 +53,8 @@ import uk.co.alt236.btlescan.services.BluetoothLeService;
* Bluetooth LE API.
*/
public class DeviceControlActivity extends AppCompatActivity {
public static final String EXTRA_DEVICE = "extra_device";
private static final String EXTRA_DEVICE = DeviceControlActivity.class.getName() + ".EXTRA_DEVICE";
private final static String TAG = DeviceControlActivity.class.getSimpleName();
private static final String LIST_NAME = "NAME";
private static final String LIST_UUID = "UUID";
@Bind(R.id.gatt_services_list)
protected ExpandableListView mGattServicesList;
@Bind(R.id.connection_state)
@@ -71,9 +67,10 @@ public class DeviceControlActivity extends AppCompatActivity {
protected TextView mDataAsString;
@Bind(R.id.data_as_array)
protected TextView mDataAsArray;
private Exporter mExporter;
private BluetoothGattCharacteristic mNotifyCharacteristic;
private BluetoothLeService mBluetoothLeService;
private List<List<BluetoothGattCharacteristic>> mGattCharacteristics = new ArrayList<>();
// If a given GATT characteristic is selected, check for supported features. This sample
// demonstrates 'Read' and 'Notify' features. See
// http://d.android.com/reference/android/bluetooth/BluetoothGatt.html for the complete
@@ -81,28 +78,30 @@ public class DeviceControlActivity extends AppCompatActivity {
private final ExpandableListView.OnChildClickListener servicesListClickListner = new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(final ExpandableListView parent, final View v, final int groupPosition, final int childPosition, final long id) {
if (mGattCharacteristics != null) {
final BluetoothGattCharacteristic characteristic = mGattCharacteristics.get(groupPosition).get(childPosition);
final int charaProp = characteristic.getProperties();
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
// If there is an active notification on a characteristic, clear
// it first so it doesn't update the data field on the user interface.
if (mNotifyCharacteristic != null) {
mBluetoothLeService.setCharacteristicNotification(mNotifyCharacteristic, false);
mNotifyCharacteristic = null;
}
mBluetoothLeService.readCharacteristic(characteristic);
final GattDataAdapterFactory.GattDataAdapter adapter =
(GattDataAdapterFactory.GattDataAdapter) parent.getExpandableListAdapter();
final BluetoothGattCharacteristic characteristic =
adapter.getBluetoothGattCharacteristic(groupPosition, childPosition);
final int charaProp = characteristic.getProperties();
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
// If there is an active notification on a characteristic, clear
// it first so it doesn't update the data field on the user interface.
if (mNotifyCharacteristic != null) {
mBluetoothLeService.setCharacteristicNotification(mNotifyCharacteristic, false);
mNotifyCharacteristic = null;
}
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
mNotifyCharacteristic = characteristic;
mBluetoothLeService.setCharacteristicNotification(characteristic, true);
}
return true;
mBluetoothLeService.readCharacteristic(characteristic);
}
return false;
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
mNotifyCharacteristic = characteristic;
mBluetoothLeService.setCharacteristicNotification(characteristic, true);
}
return true;
}
};
private String mDeviceAddress;
// Code to manage Service lifecycle.
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
@@ -113,7 +112,7 @@ public class DeviceControlActivity extends AppCompatActivity {
finish();
}
// Automatically connects to the device upon successful start-up initialization.
mBluetoothLeService.connect(mDeviceAddress);
mBluetoothLeService.connect(mDevice.getAddress());
}
@Override
@@ -121,8 +120,9 @@ public class DeviceControlActivity extends AppCompatActivity {
mBluetoothLeService = null;
}
};
private String mDeviceName;
private boolean mConnected = false;
private BluetoothLeDevice mDevice;
private State mCurrentState = State.DISCONNECTED;
private String mExportString;
// Handles various events fired by the Service.
// ACTION_GATT_CONNECTED: connected to a GATT server.
@@ -135,14 +135,16 @@ public class DeviceControlActivity extends AppCompatActivity {
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
mConnected = true;
updateConnectionState(R.string.connected);
updateConnectionState(State.CONNECTED);
invalidateOptionsMenu();
} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
mConnected = false;
updateConnectionState(R.string.disconnected);
invalidateOptionsMenu();
clearUI();
updateConnectionState(State.DISCONNECTED);
invalidateOptionsMenu();
} else if (BluetoothLeService.ACTION_GATT_CONNECTING.equals(action)) {
clearUI();
updateConnectionState(State.CONNECTING);
invalidateOptionsMenu();
} else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
// Show all the supported services and characteristics on the user interface.
displayGattServices(mBluetoothLeService.getSupportedGattServices());
@@ -160,6 +162,7 @@ public class DeviceControlActivity extends AppCompatActivity {
};
private void clearUI() {
mExportString = null;
mGattServicesList.setAdapter((SimpleExpandableListAdapter) null);
mGattUUID.setText(R.string.no_data);
mGattUUIDDesc.setText(R.string.no_data);
@@ -172,125 +175,34 @@ public class DeviceControlActivity extends AppCompatActivity {
// on the UI.
private void displayGattServices(final List<BluetoothGattService> gattServices) {
if (gattServices == null) return;
generateExportString(gattServices);
mExportString = mExporter.generateExportString(
mDevice.getName(),
mDevice.getAddress(),
gattServices);
String uuid = null;
final String unknownServiceString = getResources().getString(R.string.unknown_service);
final String unknownCharaString = getResources().getString(R.string.unknown_characteristic);
final List<Map<String, String>> gattServiceData = new ArrayList<>();
final List<List<Map<String, String>>> gattCharacteristicData = new ArrayList<>();
mGattCharacteristics = new ArrayList<>();
// Loops through available GATT Services.
for (final BluetoothGattService gattService : gattServices) {
final Map<String, String> currentServiceData = new HashMap<>();
uuid = gattService.getUuid().toString();
currentServiceData.put(LIST_NAME, GattAttributeResolver.getAttributeName(uuid, unknownServiceString));
currentServiceData.put(LIST_UUID, uuid);
gattServiceData.add(currentServiceData);
final List<Map<String, String>> gattCharacteristicGroupData = new ArrayList<>();
final List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
final List<BluetoothGattCharacteristic> charas = new ArrayList<>();
// Loops through available Characteristics.
for (final BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
charas.add(gattCharacteristic);
final Map<String, String> currentCharaData = new HashMap<>();
uuid = gattCharacteristic.getUuid().toString();
currentCharaData.put(LIST_NAME, GattAttributeResolver.getAttributeName(uuid, unknownCharaString));
currentCharaData.put(LIST_UUID, uuid);
gattCharacteristicGroupData.add(currentCharaData);
}
mGattCharacteristics.add(charas);
gattCharacteristicData.add(gattCharacteristicGroupData);
}
final SimpleExpandableListAdapter gattServiceAdapter = new SimpleExpandableListAdapter(
this,
gattServiceData,
android.R.layout.simple_expandable_list_item_2,
new String[]{LIST_NAME, LIST_UUID},
new int[]{android.R.id.text1, android.R.id.text2},
gattCharacteristicData,
android.R.layout.simple_expandable_list_item_2,
new String[]{LIST_NAME, LIST_UUID},
new int[]{android.R.id.text1, android.R.id.text2}
);
mGattServicesList.setAdapter(gattServiceAdapter);
final GattDataAdapterFactory.GattDataAdapter adapter = GattDataAdapterFactory.createAdapter(this, gattServices);
mGattServicesList.setAdapter(adapter);
invalidateOptionsMenu();
}
private void generateExportString(final List<BluetoothGattService> gattServices) {
final String unknownServiceString = getResources().getString(R.string.unknown_service);
final String unknownCharaString = getResources().getString(R.string.unknown_characteristic);
final StringBuilder exportBuilder = new StringBuilder();
exportBuilder.append("Device Name: ");
exportBuilder.append(mDeviceName);
exportBuilder.append('\n');
exportBuilder.append("Device Address: ");
exportBuilder.append(mDeviceAddress);
exportBuilder.append('\n');
exportBuilder.append('\n');
exportBuilder.append("Services:");
exportBuilder.append("--------------------------");
exportBuilder.append('\n');
String uuid = null;
for (final BluetoothGattService gattService : gattServices) {
uuid = gattService.getUuid().toString();
exportBuilder.append(GattAttributeResolver.getAttributeName(uuid, unknownServiceString));
exportBuilder.append(" (");
exportBuilder.append(uuid);
exportBuilder.append(')');
exportBuilder.append('\n');
final List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
for (final BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
uuid = gattCharacteristic.getUuid().toString();
exportBuilder.append('\t');
exportBuilder.append(GattAttributeResolver.getAttributeName(uuid, unknownCharaString));
exportBuilder.append(" (");
exportBuilder.append(uuid);
exportBuilder.append(')');
exportBuilder.append('\n');
}
exportBuilder.append('\n');
exportBuilder.append('\n');
}
exportBuilder.append("--------------------------");
exportBuilder.append('\n');
mExportString = exportBuilder.toString();
}
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gatt_services);
final Intent intent = getIntent();
final BluetoothLeDevice device = intent.getParcelableExtra(EXTRA_DEVICE);
mDeviceName = device.getName();
mDeviceAddress = device.getAddress();
mDevice = intent.getParcelableExtra(EXTRA_DEVICE);
ButterKnife.bind(this);
// Sets up UI references.
((TextView) findViewById(R.id.device_address)).setText(mDeviceAddress);
((TextView) findViewById(R.id.device_address)).setText(mDevice.getAddress());
mGattServicesList.setOnChildClickListener(servicesListClickListner);
getSupportActionBar().setTitle(mDeviceName);
getSupportActionBar().setTitle(mDevice.getName());
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mExporter = new Exporter(this);
final Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
}
@@ -298,12 +210,26 @@ public class DeviceControlActivity extends AppCompatActivity {
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.gatt_services, menu);
if (mConnected) {
menu.findItem(R.id.menu_connect).setVisible(false);
menu.findItem(R.id.menu_disconnect).setVisible(true);
} else {
menu.findItem(R.id.menu_connect).setVisible(true);
menu.findItem(R.id.menu_disconnect).setVisible(false);
switch (mCurrentState) {
case DISCONNECTED:
menu.findItem(R.id.menu_connect).setVisible(true);
menu.findItem(R.id.menu_disconnect).setVisible(false);
menu.findItem(R.id.menu_refresh).setActionView(null);
break;
case CONNECTING:
menu.findItem(R.id.menu_connect).setVisible(false);
menu.findItem(R.id.menu_disconnect).setVisible(false);
menu.findItem(R.id.menu_refresh).setActionView(R.layout.actionbar_progress_indeterminate);
break;
case CONNECTED:
menu.findItem(R.id.menu_connect).setVisible(false);
menu.findItem(R.id.menu_disconnect).setVisible(true);
menu.findItem(R.id.menu_refresh).setActionView(null);
break;
default:
throw new IllegalStateException("Don't know how to handle: " + mCurrentState);
}
if (mExportString == null) {
@@ -326,7 +252,7 @@ public class DeviceControlActivity extends AppCompatActivity {
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_connect:
mBluetoothLeService.connect(mDeviceAddress);
mBluetoothLeService.connect(mDevice.getAddress());
return true;
case R.id.menu_disconnect:
mBluetoothLeService.disconnect();
@@ -336,7 +262,10 @@ public class DeviceControlActivity extends AppCompatActivity {
return true;
case R.id.menu_share:
final Intent intent = new Intent(android.content.Intent.ACTION_SEND);
final String subject = getString(R.string.exporter_email_device_services_subject, mDeviceName, mDeviceAddress);
final String subject = getString(
R.string.exporter_email_device_services_subject,
mDevice.getName(),
mDevice.getAddress());
intent.setType("text/plain");
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject);
@@ -362,31 +291,41 @@ public class DeviceControlActivity extends AppCompatActivity {
super.onResume();
registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
if (mBluetoothLeService != null) {
final boolean result = mBluetoothLeService.connect(mDeviceAddress);
final boolean result = mBluetoothLeService.connect(mDevice.getAddress());
Log.d(TAG, "Connect request result=" + result);
}
}
private void updateConnectionState(final int resourceId) {
private void updateConnectionState(final State state) {
mCurrentState = state;
runOnUiThread(new Runnable() {
@Override
public void run() {
final int colourId;
final int resId;
switch (resourceId) {
case R.string.connected:
switch (state) {
case CONNECTED:
colourId = android.R.color.holo_green_dark;
resId = R.string.connected;
break;
case R.string.disconnected:
case DISCONNECTED:
colourId = android.R.color.holo_red_dark;
resId = R.string.disconnected;
break;
case CONNECTING:
colourId = android.R.color.black;
resId = R.string.connecting;
break;
default:
colourId = android.R.color.black;
resId = 0;
break;
}
mConnectionState.setText(resourceId);
mConnectionState.setTextColor(getResources().getColor(colourId));
mConnectionState.setText(resId);
mConnectionState.setTextColor(ContextCompat.getColor(DeviceControlActivity.this, colourId));
}
});
}
@@ -397,6 +336,7 @@ public class DeviceControlActivity extends AppCompatActivity {
intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);
intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE);
intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTING);
return intentFilter;
}
@@ -407,4 +347,16 @@ public class DeviceControlActivity extends AppCompatActivity {
return string;
}
}
public static Intent createIntent(final Context context, final BluetoothLeDevice device) {
final Intent intent = new Intent(context, DeviceControlActivity.class);
intent.putExtra(DeviceControlActivity.EXTRA_DEVICE, device);
return intent;
}
private enum State {
DISCONNECTED,
CONNECTING,
CONNECTED
}
}
@@ -0,0 +1,70 @@
package uk.co.alt236.btlescan.ui.control;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import java.util.List;
import uk.co.alt236.bluetoothlelib.resolvers.GattAttributeResolver;
import uk.co.alt236.btlescan.R;
/*package*/ class Exporter {
private final Context mContext;
public Exporter(final Context context) {
mContext = context.getApplicationContext();
}
public String generateExportString(final String deviceName,
final String deviceAddress,
final List<BluetoothGattService> gattServices) {
final String unknownServiceString = mContext.getString(R.string.unknown_service);
final String unknownCharaString = mContext.getString(R.string.unknown_characteristic);
final StringBuilder exportBuilder = new StringBuilder();
exportBuilder.append("Device Name: ");
exportBuilder.append(deviceName);
exportBuilder.append('\n');
exportBuilder.append("Device Address: ");
exportBuilder.append(deviceAddress);
exportBuilder.append('\n');
exportBuilder.append('\n');
exportBuilder.append("Services:");
exportBuilder.append("--------------------------");
exportBuilder.append('\n');
String uuid = null;
for (final BluetoothGattService gattService : gattServices) {
uuid = gattService.getUuid().toString();
exportBuilder.append(GattAttributeResolver.getAttributeName(uuid, unknownServiceString));
exportBuilder.append(" (");
exportBuilder.append(uuid);
exportBuilder.append(')');
exportBuilder.append('\n');
final List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
for (final BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
uuid = gattCharacteristic.getUuid().toString();
exportBuilder.append('\t');
exportBuilder.append(GattAttributeResolver.getAttributeName(uuid, unknownCharaString));
exportBuilder.append(" (");
exportBuilder.append(uuid);
exportBuilder.append(')');
exportBuilder.append('\n');
}
exportBuilder.append('\n');
exportBuilder.append('\n');
}
exportBuilder.append("--------------------------");
exportBuilder.append('\n');
return exportBuilder.toString();
}
}
@@ -0,0 +1,94 @@
package uk.co.alt236.btlescan.ui.control;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import android.widget.SimpleExpandableListAdapter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import uk.co.alt236.bluetoothlelib.resolvers.GattAttributeResolver;
import uk.co.alt236.btlescan.R;
/*package*/ class GattDataAdapterFactory {
private static final String LIST_NAME = "NAME";
private static final String LIST_UUID = "UUID";
public static GattDataAdapter createAdapter(final Context context,
final List<BluetoothGattService> gattServices) {
final String unknownServiceString = context.getString(R.string.unknown_service);
final String unknownCharaString = context.getString(R.string.unknown_characteristic);
final List<Map<String, String>> gattServiceData = new ArrayList<>();
final List<List<Map<String, String>>> gattCharacteristicData = new ArrayList<>();
final List<List<BluetoothGattCharacteristic>> fullGattCharacteristics = new ArrayList<>();
// Loops through available GATT Services.
String uuid;
for (final BluetoothGattService gattService : gattServices) {
final Map<String, String> currentServiceData = new HashMap<>();
uuid = gattService.getUuid().toString();
currentServiceData.put(LIST_NAME, GattAttributeResolver.getAttributeName(uuid, unknownServiceString));
currentServiceData.put(LIST_UUID, uuid);
gattServiceData.add(currentServiceData);
final List<Map<String, String>> gattCharacteristicGroupData = new ArrayList<>();
final List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
final List<BluetoothGattCharacteristic> charas = new ArrayList<>();
// Loops through available Characteristics.
for (final BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
charas.add(gattCharacteristic);
final Map<String, String> currentCharaData = new HashMap<>();
uuid = gattCharacteristic.getUuid().toString();
currentCharaData.put(LIST_NAME, GattAttributeResolver.getAttributeName(uuid, unknownCharaString));
currentCharaData.put(LIST_UUID, uuid);
gattCharacteristicGroupData.add(currentCharaData);
}
fullGattCharacteristics.add(charas);
gattCharacteristicData.add(gattCharacteristicGroupData);
}
return new GattDataAdapter(
context,
fullGattCharacteristics,
gattServiceData,
android.R.layout.simple_expandable_list_item_2,
new String[]{LIST_NAME, LIST_UUID},
new int[]{android.R.id.text1, android.R.id.text2},
gattCharacteristicData,
android.R.layout.simple_expandable_list_item_2,
new String[]{LIST_NAME, LIST_UUID},
new int[]{android.R.id.text1, android.R.id.text2}
);
}
public static class GattDataAdapter extends SimpleExpandableListAdapter {
private final List<List<BluetoothGattCharacteristic>> mGattCharacteristics;
public GattDataAdapter(Context context,
List<List<BluetoothGattCharacteristic>> gattCharacteristics,
List<Map<String, String>> groupData,
int groupLayout, String[] groupFrom,
int[] groupTo,
List<List<Map<String, String>>> childData,
int childLayout,
String[] childFrom,
int[] childTo) {
super(context, groupData, groupLayout, groupFrom, groupTo, childData, childLayout, childFrom, childTo);
mGattCharacteristics = gattCharacteristics;
}
public BluetoothGattCharacteristic getBluetoothGattCharacteristic(final int groupPosition, final int childPosition) {
return mGattCharacteristics.get(groupPosition).get(childPosition);
}
}
}
@@ -0,0 +1,25 @@
package uk.co.alt236.btlescan.ui.details;
import java.util.List;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseRecyclerViewAdapter;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewBinderCore;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
/*package*/ class DetailsRecyclerAdapter extends BaseRecyclerViewAdapter {
public DetailsRecyclerAdapter(RecyclerViewBinderCore core, List<RecyclerViewItem> items) {
super(core, items);
}
// private static List<RecyclerViewItem> validate(RecyclerViewBinderCore core, List<RecyclerViewItem> items) {
// final List<RecyclerViewItem> retVal = new ArrayList<>();
//
// for (final RecyclerViewItem item : items) {
// if (core.getViewType(item) != RecyclerViewBinderCore.INVALID_VIEWTYPE) {
// retVal.add(item);
// }
// }
//
// return retVal;
// }
}
@@ -0,0 +1,125 @@
package uk.co.alt236.btlescan.ui.details;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuItem;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.bluetoothlelib.device.adrecord.AdRecord;
import uk.co.alt236.bluetoothlelib.device.beacon.BeaconType;
import uk.co.alt236.bluetoothlelib.device.beacon.BeaconUtils;
import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconManufacturerData;
import uk.co.alt236.bluetoothlelib.util.ByteUtils;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.Navigation;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewBinderCore;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.AdRecordItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.DeviceInfoItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.HeaderItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.IBeaconItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.RssiItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.TextItem;
public class DeviceDetailsActivity extends AppCompatActivity {
private static final String EXTRA_DEVICE = DeviceDetailsActivity.class.getName() + ".EXTRA_DEVICE";
private static final int LAYOUT_ID = R.layout.activity_details;
@Bind(R.id.recycler)
protected RecyclerView mRecycler;
private BluetoothLeDevice mDevice;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(LAYOUT_ID);
ButterKnife.bind(this);
mRecycler.setLayoutManager(new LinearLayoutManager(this));
mDevice = getIntent().getParcelableExtra(EXTRA_DEVICE);
getSupportActionBar().setTitle(mDevice.getName());
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
pupulateDetails(mDevice);
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.details, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_connect:
new Navigation(this).startControlActivity(mDevice);
return true;
case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
private void pupulateDetails(final BluetoothLeDevice device) {
final DetailsRecyclerAdapter detailsRecyclerAdapter;
final List<RecyclerViewItem> list = new ArrayList<>();
if (device == null) {
list.add(new HeaderItem(getString(R.string.header_device_info)));
list.add(new TextItem(getString(R.string.invalid_device_data)));
} else {
list.add(new HeaderItem(getString(R.string.header_device_info)));
list.add(new DeviceInfoItem(device));
list.add(new HeaderItem(getString(R.string.header_rssi_info)));
list.add(new RssiItem(device));
list.add(new HeaderItem(getString(R.string.header_scan_record)));
list.add(new TextItem(ByteUtils.byteArrayToHexString(device.getScanRecord())));
final Collection<AdRecord> adRecords = device.getAdRecordStore().getRecordsAsCollection();
if (adRecords.size() > 0) {
list.add(new HeaderItem(getString(R.string.header_raw_ad_records)));
for (final AdRecord record : adRecords) {
final String title = "#" + record.getType() + " " + record.getHumanReadableType();
list.add(new AdRecordItem(title, record));
}
}
final boolean isIBeacon = BeaconUtils.getBeaconType(device) == BeaconType.IBEACON;
if (isIBeacon) {
final IBeaconManufacturerData iBeaconData = new IBeaconManufacturerData(device);
list.add(new HeaderItem(getString(R.string.header_ibeacon_data)));
list.add(new IBeaconItem(iBeaconData));
}
}
final RecyclerViewBinderCore core = RecyclerViewCoreFactory.create(this);
mRecycler.setAdapter(new DetailsRecyclerAdapter(core, list));
}
public static Intent createIntent(Context context, BluetoothLeDevice device) {
final Intent intent = new Intent(context, DeviceDetailsActivity.class);
intent.putExtra(DeviceDetailsActivity.EXTRA_DEVICE, device);
return intent;
}
}
@@ -0,0 +1,35 @@
package uk.co.alt236.btlescan.ui.details;
import android.content.Context;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewBinderCore;
import uk.co.alt236.btlescan.ui.details.recyclerview.binder.AdRecordBinder;
import uk.co.alt236.btlescan.ui.details.recyclerview.binder.DeviceInfoBinder;
import uk.co.alt236.btlescan.ui.details.recyclerview.binder.HeaderBinder;
import uk.co.alt236.btlescan.ui.details.recyclerview.binder.IBeaconBinder;
import uk.co.alt236.btlescan.ui.details.recyclerview.binder.RssiBinder;
import uk.co.alt236.btlescan.ui.details.recyclerview.binder.TextBinder;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.AdRecordHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.DeviceInfoHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.HeaderHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.IBeaconHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.RssiInfoHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.TextHolder;
/*protected*/ final class RecyclerViewCoreFactory {
public static RecyclerViewBinderCore create(final Context context) {
final RecyclerViewBinderCore core = new RecyclerViewBinderCore();
core.add(new TextBinder(context), TextHolder.class, R.layout.list_item_view_textview);
core.add(new HeaderBinder(context), HeaderHolder.class, R.layout.list_item_view_header);
core.add(new AdRecordBinder(context), AdRecordHolder.class, R.layout.list_item_view_adrecord);
core.add(new RssiBinder(context), RssiInfoHolder.class, R.layout.list_item_view_rssi_info);
core.add(new DeviceInfoBinder(context), DeviceInfoHolder.class, R.layout.list_item_view_device_info);
core.add(new IBeaconBinder(context), IBeaconHolder.class, R.layout.list_item_view_ibeacon_details);
return core;
}
}
@@ -0,0 +1,38 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.binder;
import android.content.Context;
import uk.co.alt236.bluetoothlelib.util.ByteUtils;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewBinder;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.AdRecordHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.AdRecordItem;
public class AdRecordBinder extends BaseViewBinder<AdRecordItem> {
public AdRecordBinder(Context context) {
super(context);
}
@Override
public void bind(BaseViewHolder<AdRecordItem> holder, AdRecordItem item) {
final AdRecordHolder actualHolder = (AdRecordHolder) holder;
actualHolder.getTitleTextView().setText(item.getTitle());
actualHolder.getStringTextView().setText(
getContext().getString(R.string.formatter_single_quoted_string,
item.getDataAsString()));
actualHolder.getArrayTextView().setText(
getContext().getString(R.string.formatter_single_quoted_string,
ByteUtils.byteArrayToHexString(item.getData())));
}
@Override
public boolean canBind(RecyclerViewItem item) {
return item instanceof AdRecordItem;
}
}
@@ -0,0 +1,57 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.binder;
import android.content.Context;
import uk.co.alt236.bluetoothlelib.device.BluetoothService;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewBinder;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.DeviceInfoHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.DeviceInfoItem;
public class DeviceInfoBinder extends BaseViewBinder<DeviceInfoItem> {
public DeviceInfoBinder(Context context) {
super(context);
}
@Override
public void bind(BaseViewHolder<DeviceInfoItem> holder, DeviceInfoItem item) {
final DeviceInfoHolder actualHolder = (DeviceInfoHolder) holder;
actualHolder.getName().setText(item.getName());
actualHolder.getAddress().setText(item.getAddress());
actualHolder.getDeviceClass().setText(item.getBluetoothDeviceClassName());
actualHolder.getMajorClass().setText(item.getBluetoothDeviceMajorClassName());
actualHolder.getBondingState().setText(item.getBluetoothDeviceBondState());
actualHolder.getServices().setText(createSupportedDevicesString(item));
}
private String createSupportedDevicesString(DeviceInfoItem item) {
final String retVal;
if (item.getBluetoothDeviceKnownSupportedServices().isEmpty()) {
retVal = getContext().getString(R.string.no_known_services);
} else {
final StringBuilder sb = new StringBuilder();
for (final BluetoothService service : item.getBluetoothDeviceKnownSupportedServices()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(service);
}
retVal = sb.toString();
}
return retVal;
}
@Override
public boolean canBind(RecyclerViewItem item) {
return item instanceof DeviceInfoItem;
}
}
@@ -0,0 +1,27 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.binder;
import android.content.Context;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewBinder;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.HeaderHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.HeaderItem;
public class HeaderBinder extends BaseViewBinder<HeaderItem> {
public HeaderBinder(Context context) {
super(context);
}
@Override
public void bind(BaseViewHolder<HeaderItem> holder, HeaderItem item) {
final HeaderHolder actualHolder = (HeaderHolder) holder;
actualHolder.getTextView().setText(item.getText());
}
@Override
public boolean canBind(RecyclerViewItem item) {
return item instanceof HeaderItem;
}
}
@@ -0,0 +1,66 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.binder;
import android.content.Context;
import java.util.Locale;
import uk.co.alt236.bluetoothlelib.resolvers.CompanyIdentifierResolver;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewBinder;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.IBeaconHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.IBeaconItem;
import uk.co.alt236.btlescan.util.TimeFormatter;
public class IBeaconBinder extends BaseViewBinder<IBeaconItem> {
private static final String STRING_FORMAT = "%s (%s)";
public IBeaconBinder(Context context) {
super(context);
}
private static String formatTime(final long time) {
return TimeFormatter.getIsoDateTime(time);
}
private static String getWithHexEncode(final String first, final int value) {
return createLine(first, hexEncode(value));
}
private static String getWithHexEncode(final int value) {
return createLine(String.valueOf(value), hexEncode(value));
}
private static String createLine(final String first, final String second) {
return String.format(Locale.US, STRING_FORMAT, first, second);
}
private static String hexEncode(final int integer) {
return "0x" + Integer.toHexString(integer).toUpperCase(Locale.US);
}
@Override
public void bind(BaseViewHolder<IBeaconItem> holder, IBeaconItem item) {
final IBeaconHolder actualHolder = (IBeaconHolder) holder;
final String companyName = CompanyIdentifierResolver.getCompanyName(
item.getCompanyIdentifier(),
getContext().getString(R.string.unknown));
actualHolder.getCompanyId().setText(
getWithHexEncode(companyName, item.getCompanyIdentifier()));
actualHolder.getAdvert().setText(getWithHexEncode(item.getIBeaconAdvertisement()));
actualHolder.getUuid().setText(item.getUuid());
actualHolder.getMajor().setText(getWithHexEncode(item.getMajor()));
actualHolder.getMinor().setText(getWithHexEncode(item.getMinor()));
actualHolder.getTxPower().setText(getWithHexEncode(item.getCalibratedTxPower()));
}
@Override
public boolean canBind(RecyclerViewItem item) {
return item instanceof IBeaconItem;
}
}
@@ -0,0 +1,46 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.binder;
import android.content.Context;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewBinder;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.RssiInfoHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.RssiItem;
import uk.co.alt236.btlescan.util.TimeFormatter;
public class RssiBinder extends BaseViewBinder<RssiItem> {
public RssiBinder(Context context) {
super(context);
}
private static String formatTime(final long time) {
return TimeFormatter.getIsoDateTime(time);
}
@Override
public void bind(BaseViewHolder<RssiItem> holder, RssiItem item) {
final RssiInfoHolder actualHolder = (RssiInfoHolder) holder;
actualHolder.getFirstTimestamp().setText(formatTime(item.getFirstTimestamp()));
actualHolder.getFirstRssi().setText(formatRssi(item.getFirstRssi()));
actualHolder.getLastTimestamp().setText(formatTime(item.getTimestamp()));
actualHolder.getLastRssi().setText(formatRssi(item.getRssi()));
actualHolder.getRunningAverageRssi().setText(formatRssi(item.getRunningAverageRssi()));
}
@Override
public boolean canBind(RecyclerViewItem item) {
return item instanceof RssiItem;
}
private String formatRssi(final double rssi) {
return getContext().getString(R.string.formatter_db, String.valueOf(rssi));
}
private String formatRssi(final int rssi) {
return getContext().getString(R.string.formatter_db, String.valueOf(rssi));
}
}
@@ -0,0 +1,27 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.binder;
import android.content.Context;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewBinder;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.details.recyclerview.holder.TextHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.TextItem;
public class TextBinder extends BaseViewBinder<TextItem> {
public TextBinder(Context context) {
super(context);
}
@Override
public void bind(BaseViewHolder<TextItem> holder, TextItem item) {
final TextHolder actualHolder = (TextHolder) holder;
actualHolder.getTextView().setText(item.getText());
}
@Override
public boolean canBind(RecyclerViewItem item) {
return item instanceof TextItem;
}
}
@@ -0,0 +1,35 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.holder;
import android.view.View;
import android.widget.TextView;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.AdRecordItem;
public class AdRecordHolder extends BaseViewHolder<AdRecordItem> {
private final TextView mStringTextView;
private final TextView mArrayTextView;
private final TextView mTitleTextView;
public AdRecordHolder(View itemView) {
super(itemView);
mStringTextView = (TextView) itemView.findViewById(R.id.data_as_string);
mArrayTextView = (TextView) itemView.findViewById(R.id.data_as_array);
mTitleTextView = (TextView) itemView.findViewById(R.id.title);
}
public TextView getStringTextView() {
return mStringTextView;
}
public TextView getArrayTextView() {
return mArrayTextView;
}
public TextView getTitleTextView() {
return mTitleTextView;
}
}
@@ -0,0 +1,53 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.holder;
import android.view.View;
import android.widget.TextView;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.DeviceInfoItem;
public class DeviceInfoHolder extends BaseViewHolder<DeviceInfoItem> {
private final TextView mName;
private final TextView mAddress;
private final TextView mClass;
private final TextView mMajorClass;
private final TextView mServices;
private final TextView mBondingState;
public DeviceInfoHolder(View itemView) {
super(itemView);
mName = (TextView) itemView.findViewById(R.id.deviceName);
mAddress = (TextView) itemView.findViewById(R.id.deviceAddress);
mClass = (TextView) itemView.findViewById(R.id.deviceClass);
mMajorClass = (TextView) itemView.findViewById(R.id.deviceMajorClass);
mServices = (TextView) itemView.findViewById(R.id.deviceServiceList);
mBondingState = (TextView) itemView.findViewById(R.id.deviceBondingState);
}
public TextView getName() {
return mName;
}
public TextView getAddress() {
return mAddress;
}
public TextView getDeviceClass() {
return mClass;
}
public TextView getMajorClass() {
return mMajorClass;
}
public TextView getServices() {
return mServices;
}
public TextView getBondingState() {
return mBondingState;
}
}
@@ -0,0 +1,23 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.holder;
import android.view.View;
import android.widget.TextView;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.HeaderItem;
public class HeaderHolder extends BaseViewHolder<HeaderItem> {
private final TextView mText;
public HeaderHolder(View itemView) {
super(itemView);
mText = (TextView) itemView.findViewById(R.id.text);
}
public TextView getTextView() {
return mText;
}
}
@@ -0,0 +1,53 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.holder;
import android.view.View;
import android.widget.TextView;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.IBeaconItem;
public class IBeaconHolder extends BaseViewHolder<IBeaconItem> {
private final TextView mCompanyId;
private final TextView mAdvert;
private final TextView mUuid;
private final TextView mMajor;
private final TextView mMinor;
private final TextView mTxPower;
public IBeaconHolder(View itemView) {
super(itemView);
mCompanyId = (TextView) itemView.findViewById(R.id.companyId);
mAdvert = (TextView) itemView.findViewById(R.id.advertisement);
mUuid = (TextView) itemView.findViewById(R.id.uuid);
mMajor = (TextView) itemView.findViewById(R.id.major);
mMinor = (TextView) itemView.findViewById(R.id.minor);
mTxPower = (TextView) itemView.findViewById(R.id.txpower);
}
public TextView getCompanyId() {
return mCompanyId;
}
public TextView getAdvert() {
return mAdvert;
}
public TextView getUuid() {
return mUuid;
}
public TextView getMajor() {
return mMajor;
}
public TextView getMinor() {
return mMinor;
}
public TextView getTxPower() {
return mTxPower;
}
}
@@ -0,0 +1,47 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.holder;
import android.view.View;
import android.widget.TextView;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.RssiItem;
public class RssiInfoHolder extends BaseViewHolder<RssiItem> {
private final TextView mTvFirstTimestamp;
private final TextView mTvFirstRssi;
private final TextView mTvLastTimestamp;
private final TextView mTvLastRssi;
private final TextView mTvRunningAverageRssi;
public RssiInfoHolder(View itemView) {
super(itemView);
mTvFirstTimestamp = (TextView) itemView.findViewById(R.id.firstTimestamp);
mTvFirstRssi = (TextView) itemView.findViewById(R.id.firstRssi);
mTvLastTimestamp = (TextView) itemView.findViewById(R.id.lastTimestamp);
mTvLastRssi = (TextView) itemView.findViewById(R.id.lastRssi);
mTvRunningAverageRssi = (TextView) itemView.findViewById(R.id.runningAverageRssi);
}
public TextView getFirstTimestamp() {
return mTvFirstTimestamp;
}
public TextView getFirstRssi() {
return mTvFirstRssi;
}
public TextView getLastTimestamp() {
return mTvLastTimestamp;
}
public TextView getLastRssi() {
return mTvLastRssi;
}
public TextView getRunningAverageRssi() {
return mTvRunningAverageRssi;
}
}
@@ -0,0 +1,23 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.holder;
import android.view.View;
import android.widget.TextView;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.details.recyclerview.model.TextItem;
public class TextHolder extends BaseViewHolder<TextItem> {
private final TextView mText;
public TextHolder(View itemView) {
super(itemView);
mText = (TextView) itemView.findViewById(R.id.text);
}
public TextView getTextView() {
return mText;
}
}
@@ -0,0 +1,31 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.model;
import uk.co.alt236.bluetoothlelib.device.adrecord.AdRecord;
import uk.co.alt236.bluetoothlelib.util.AdRecordUtils;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
public class AdRecordItem implements RecyclerViewItem {
private final String mTitle;
private final byte[] mData;
private final String mDataAsString;
public AdRecordItem(final String title,
final AdRecord record) {
mTitle = title;
mData = record.getData();
mDataAsString = AdRecordUtils.getRecordDataAsString(record);
}
public String getTitle() {
return mTitle;
}
public byte[] getData() {
return mData;
}
public String getDataAsString() {
return mDataAsString;
}
}
@@ -0,0 +1,40 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.model;
import java.util.Set;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.bluetoothlelib.device.BluetoothService;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
public class DeviceInfoItem implements RecyclerViewItem {
private final BluetoothLeDevice mDevice;
public DeviceInfoItem(BluetoothLeDevice device) {
mDevice = device;
}
public Set<BluetoothService> getBluetoothDeviceKnownSupportedServices() {
return mDevice.getBluetoothDeviceKnownSupportedServices();
}
public String getBluetoothDeviceBondState() {
return mDevice.getBluetoothDeviceBondState();
}
public String getBluetoothDeviceMajorClassName() {
return mDevice.getBluetoothDeviceMajorClassName();
}
public String getBluetoothDeviceClassName() {
return mDevice.getBluetoothDeviceClassName();
}
public String getAddress() {
return mDevice.getAddress();
}
public String getName() {
return mDevice.getName();
}
}
@@ -0,0 +1,15 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.model;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
public class HeaderItem implements RecyclerViewItem {
private final CharSequence mText;
public HeaderItem(CharSequence text) {
mText = text;
}
public CharSequence getText() {
return mText;
}
}
@@ -0,0 +1,47 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.model;
import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconManufacturerData;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
public class IBeaconItem implements RecyclerViewItem {
private final int mMajor;
private final int mMinor;
private final String mUuid;
private final int mCompanyIdentifier;
private final int mIBeaconAdvertisement;
private final int mCalibratedTxPower;
public IBeaconItem(final IBeaconManufacturerData iBeaconData) {
mMajor = iBeaconData.getMajor();
mMinor = iBeaconData.getMinor();
mUuid = iBeaconData.getUUID();
mCompanyIdentifier = iBeaconData.getCompanyIdentifier();
mIBeaconAdvertisement = iBeaconData.getIBeaconAdvertisement();
mCalibratedTxPower = iBeaconData.getCalibratedTxPower();
}
public int getCompanyIdentifier() {
return mCompanyIdentifier;
}
public int getMajor() {
return mMajor;
}
public int getMinor() {
return mMinor;
}
public String getUuid() {
return mUuid;
}
public int getIBeaconAdvertisement() {
return mIBeaconAdvertisement;
}
public int getCalibratedTxPower() {
return mCalibratedTxPower;
}
}
@@ -0,0 +1,33 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.model;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
public class RssiItem implements RecyclerViewItem {
private final BluetoothLeDevice mDevice;
public RssiItem(BluetoothLeDevice device) {
mDevice = device;
}
public int getRssi() {
return mDevice.getRssi();
}
public double getRunningAverageRssi() {
return mDevice.getRunningAverageRssi();
}
public int getFirstRssi() {
return mDevice.getFirstRssi();
}
public long getFirstTimestamp() {
return mDevice.getFirstTimestamp();
}
public long getTimestamp() {
return mDevice.getTimestamp();
}
}
@@ -0,0 +1,15 @@
package uk.co.alt236.btlescan.ui.details.recyclerview.model;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
public class TextItem implements RecyclerViewItem {
private final CharSequence mText;
public TextItem(CharSequence text) {
mText = text;
}
public CharSequence getText() {
return mText;
}
}
@@ -1,6 +1,6 @@
package uk.co.alt236.btlescan.util;
package uk.co.alt236.btlescan.ui.main;
public class CsvWriterHelper {
/*package*/ class CsvWriterHelper {
private static final String QUOTE = "\"";
public static String addStuff(final Integer text) {
@@ -0,0 +1,10 @@
package uk.co.alt236.btlescan.ui.main;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseRecyclerViewAdapter;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewBinderCore;
/*package*/ class DeviceRecyclerAdapter extends BaseRecyclerViewAdapter {
public DeviceRecyclerAdapter(RecyclerViewBinderCore core) {
super(core);
}
}
@@ -0,0 +1,47 @@
package uk.co.alt236.btlescan.ui.main;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.support.v7.app.AlertDialog;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.text.util.Linkify;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import uk.co.alt236.btlescan.R;
/*package*/ final class DialogFactory {
private DialogFactory() {
// NOOP
}
public static Dialog createAboutDialog(final Context context) {
final View view = LayoutInflater.from(context).inflate(R.layout.dialog_textview, null);
final TextView textView = (TextView) view.findViewById(R.id.text);
final SpannableString text = new SpannableString(context.getString(R.string.about_dialog_text));
textView.setText(text);
textView.setAutoLinkMask(Activity.RESULT_OK);
textView.setMovementMethod(LinkMovementMethod.getInstance());
Linkify.addLinks(text, Linkify.ALL);
final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
public void onClick(final DialogInterface dialog, final int id) {
}
};
return new AlertDialog.Builder(context)
.setTitle(R.string.menu_about)
.setCancelable(false)
.setPositiveButton(android.R.string.ok, listener)
.setView(view)
.create();
}
}
@@ -1,33 +1,42 @@
package uk.co.alt236.btlescan.activities;
package uk.co.alt236.btlescan.ui.main;
import android.app.AlertDialog;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.text.util.Linkify;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.anthonycr.grant.PermissionsManager;
import com.anthonycr.grant.PermissionsResultAction;
import java.util.ArrayList;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.bluetoothlelib.device.beacon.BeaconType;
import uk.co.alt236.bluetoothlelib.device.beacon.BeaconUtils;
import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconDevice;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.adapters.LeDeviceListAdapter;
import uk.co.alt236.btlescan.containers.BluetoothLeDeviceStore;
import uk.co.alt236.btlescan.ui.common.Navigation;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewBinderCore;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.main.recyclerview.model.IBeaconItem;
import uk.co.alt236.btlescan.ui.main.recyclerview.model.LeDeviceItem;
import uk.co.alt236.btlescan.util.BluetoothLeScanner;
import uk.co.alt236.btlescan.util.BluetoothUtils;
import uk.co.alt236.easycursor.objectcursor.EasyObjectCursor;
public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
public class MainActivity extends AppCompatActivity {
@Bind(R.id.tvBluetoothLe)
protected TextView mTvBluetoothLeStatus;
@Bind(R.id.tvBluetoothStatus)
@@ -35,14 +44,15 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
@Bind(R.id.tvItemCount)
protected TextView mTvItemCount;
@Bind(android.R.id.list)
protected ListView mList;
protected RecyclerView mList;
@Bind(android.R.id.empty)
protected View mEmpty;
private RecyclerViewBinderCore mCore;
private BluetoothUtils mBluetoothUtils;
private BluetoothLeScanner mScanner;
private LeDeviceListAdapter mLeDeviceListAdapter;
private BluetoothLeDeviceStore mDeviceStore;
private DeviceRecyclerAdapter mRecyclerAdapter;
private final BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
@@ -50,51 +60,33 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
final BluetoothLeDevice deviceLe = new BluetoothLeDevice(device, rssi, scanRecord, System.currentTimeMillis());
mDeviceStore.addDevice(deviceLe);
final EasyObjectCursor<BluetoothLeDevice> c = mDeviceStore.getDeviceCursor();
final List<RecyclerViewItem> itemList = new ArrayList<>();
for (final BluetoothLeDevice leDevice : mDeviceStore.getDeviceList()) {
if (BeaconUtils.getBeaconType(leDevice) == BeaconType.IBEACON) {
itemList.add(new IBeaconItem(new IBeaconDevice(leDevice)));
} else {
itemList.add(new LeDeviceItem(leDevice));
}
}
runOnUiThread(new Runnable() {
@Override
public void run() {
mLeDeviceListAdapter.swapCursor(c);
updateItemCount(mLeDeviceListAdapter.getCount());
mRecyclerAdapter.setData(itemList);
updateItemCount(mRecyclerAdapter.getItemCount());
}
});
}
};
private void displayAboutDialog() {
// REALLY REALLY LAZY LINKIFIED DIALOG
final int paddingSizeDp = 5;
final float scale = getResources().getDisplayMetrics().density;
final int dpAsPixels = (int) (paddingSizeDp * scale + 0.5f);
final TextView textView = new TextView(this);
final SpannableString text = new SpannableString(getString(R.string.about_dialog_text));
textView.setText(text);
textView.setAutoLinkMask(RESULT_OK);
textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setPadding(dpAsPixels, dpAsPixels, dpAsPixels, dpAsPixels);
Linkify.addLinks(text, Linkify.ALL);
new AlertDialog.Builder(this)
.setTitle(R.string.menu_about)
.setCancelable(false)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(final DialogInterface dialog, final int id) {
}
})
.setView(textView)
.show();
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mList.setEmptyView(mEmpty);
mList.setOnItemClickListener(this);
mCore = RecyclerViewCoreFactory.create(this, new Navigation(this));
mList.setLayoutManager(new LinearLayoutManager(this));
mDeviceStore = new BluetoothLeDeviceStore();
mBluetoothUtils = new BluetoothUtils(this);
mScanner = new BluetoothLeScanner(mLeScanCallback, mBluetoothUtils);
@@ -114,7 +106,7 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
menu.findItem(R.id.menu_refresh).setActionView(R.layout.actionbar_progress_indeterminate);
}
if (mList.getCount() > 0) {
if (mRecyclerAdapter != null && mRecyclerAdapter.getItemCount() > 0) {
menu.findItem(R.id.menu_share).setVisible(true);
} else {
menu.findItem(R.id.menu_share).setVisible(false);
@@ -123,32 +115,36 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
return true;
}
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
final BluetoothLeDevice device = mLeDeviceListAdapter.getItem(position);
if (device == null) return;
final Intent intent = new Intent(this, DeviceDetailsActivity.class);
intent.putExtra(DeviceDetailsActivity.EXTRA_DEVICE, device);
startActivity(intent);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_scan:
startScan();
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();
}
});
break;
case R.id.menu_stop:
mScanner.scanLeDevice(-1, false);
invalidateOptionsMenu();
break;
case R.id.menu_about:
displayAboutDialog();
DialogFactory.createAboutDialog(this).show();
break;
case R.id.menu_share:
mDeviceStore.shareDataAsEmail(this);
new Sharer().shareDataAsEmail(this, mDeviceStore);
}
return true;
}
@@ -162,16 +158,14 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
@Override
public void onResume() {
super.onResume();
final boolean mIsBluetoothOn = mBluetoothUtils.isBluetoothOn();
final boolean mIsBluetoothLePresent = mBluetoothUtils.isBluetoothLeSupported();
if (mIsBluetoothOn) {
if (mBluetoothUtils.isBluetoothOn()) {
mTvBluetoothStatus.setText(R.string.on);
} else {
mTvBluetoothStatus.setText(R.string.off);
}
if (mIsBluetoothLePresent) {
if (mBluetoothUtils.isBluetoothLeSupported()) {
mTvBluetoothLeStatus.setText(R.string.supported);
} else {
mTvBluetoothLeStatus.setText(R.string.not_supported);
@@ -186,8 +180,8 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
mDeviceStore.clear();
updateItemCount(0);
mLeDeviceListAdapter = new LeDeviceListAdapter(this, mDeviceStore.getDeviceCursor());
mList.setAdapter(mLeDeviceListAdapter);
mRecyclerAdapter = new DeviceRecyclerAdapter(mCore);
mList.setAdapter(mRecyclerAdapter);
mBluetoothUtils.askUserToEnableBluetoothIfNeeded();
if (mIsBluetoothOn && mIsBluetoothLePresent) {
@@ -203,4 +197,10 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
String.valueOf(count)));
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
PermissionsManager.getInstance().notifyPermissionsChange(permissions, grantResults);
}
}
@@ -0,0 +1,24 @@
package uk.co.alt236.btlescan.ui.main;
import android.content.Context;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.Navigation;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewBinderCore;
import uk.co.alt236.btlescan.ui.main.recyclerview.binder.IBeaconBinder;
import uk.co.alt236.btlescan.ui.main.recyclerview.binder.LeDeviceBinder;
import uk.co.alt236.btlescan.ui.main.recyclerview.holder.IBeaconHolder;
import uk.co.alt236.btlescan.ui.main.recyclerview.holder.LeDeviceHolder;
/*protected*/ final class RecyclerViewCoreFactory {
public static RecyclerViewBinderCore create(final Context context, final Navigation navigation) {
final RecyclerViewBinderCore core = new RecyclerViewBinderCore();
core.add(new IBeaconBinder(context, navigation), IBeaconHolder.class, R.layout.list_item_device_ibeacon);
core.add(new LeDeviceBinder(context, navigation), LeDeviceHolder.class, R.layout.list_item_device_le);
return core;
}
}
@@ -0,0 +1,161 @@
package uk.co.alt236.btlescan.ui.main;
import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import android.support.v4.content.ContextCompat;
import android.widget.Toast;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.bluetoothlelib.device.beacon.BeaconType;
import uk.co.alt236.bluetoothlelib.device.beacon.BeaconUtils;
import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconDevice;
import uk.co.alt236.bluetoothlelib.util.ByteUtils;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.containers.BluetoothLeDeviceStore;
import uk.co.alt236.btlescan.ui.common.Navigation;
import uk.co.alt236.btlescan.util.TimeFormatter;
/*package*/ class Sharer {
private static final String CSV_FILENAME_PREFIX = "bluetooth_le_%d";
private static final String CSV_FILENAME_SUFFIX = ".csv";
private static File getExternalCacheDir(final Context context) {
final File[] files = ContextCompat.getExternalCacheDirs(context);
final File retVal;
if (files == null || files.length == 0 || files[0] == null) {
retVal = null;
} else {
retVal = files[0];
}
return retVal;
}
private static String getListAsCsv(List<BluetoothLeDevice> deviceList) {
final StringBuilder sb = new StringBuilder();
sb.append(CsvWriterHelper.addStuff("mac"));
sb.append(CsvWriterHelper.addStuff("name"));
sb.append(CsvWriterHelper.addStuff("firstTimestamp"));
sb.append(CsvWriterHelper.addStuff("firstRssi"));
sb.append(CsvWriterHelper.addStuff("currentTimestamp"));
sb.append(CsvWriterHelper.addStuff("currentRssi"));
sb.append(CsvWriterHelper.addStuff("adRecord"));
sb.append(CsvWriterHelper.addStuff("iBeacon"));
sb.append(CsvWriterHelper.addStuff("uuid"));
sb.append(CsvWriterHelper.addStuff("major"));
sb.append(CsvWriterHelper.addStuff("minor"));
sb.append(CsvWriterHelper.addStuff("txPower"));
sb.append(CsvWriterHelper.addStuff("distance"));
sb.append(CsvWriterHelper.addStuff("accuracy"));
sb.append('\n');
for (final BluetoothLeDevice device : deviceList) {
sb.append(CsvWriterHelper.addStuff(device.getAddress()));
sb.append(CsvWriterHelper.addStuff(device.getName()));
sb.append(CsvWriterHelper.addStuff(TimeFormatter.getIsoDateTime(device.getFirstTimestamp())));
sb.append(CsvWriterHelper.addStuff(device.getFirstRssi()));
sb.append(CsvWriterHelper.addStuff(TimeFormatter.getIsoDateTime(device.getTimestamp())));
sb.append(CsvWriterHelper.addStuff(device.getRssi()));
sb.append(CsvWriterHelper.addStuff(ByteUtils.byteArrayToHexString(device.getScanRecord())));
final boolean isIBeacon = BeaconUtils.getBeaconType(device) == BeaconType.IBEACON;
final String uuid;
final String minor;
final String major;
final String txPower;
final String distance;
final String accuracy;
if (isIBeacon) {
final IBeaconDevice beacon = new IBeaconDevice(device);
uuid = String.valueOf(beacon.getUUID());
minor = String.valueOf(beacon.getMinor());
major = String.valueOf(beacon.getMajor());
txPower = String.valueOf(beacon.getCalibratedTxPower());
distance = beacon.getDistanceDescriptor().toString().toLowerCase(Locale.US);
accuracy = String.valueOf(beacon.getAccuracy());
} else {
uuid = "";
minor = "";
major = "";
txPower = "";
distance = "";
accuracy = "";
}
sb.append(CsvWriterHelper.addStuff(isIBeacon));
sb.append(CsvWriterHelper.addStuff(uuid));
sb.append(CsvWriterHelper.addStuff(minor));
sb.append(CsvWriterHelper.addStuff(major));
sb.append(CsvWriterHelper.addStuff(txPower));
sb.append(CsvWriterHelper.addStuff(distance));
sb.append(CsvWriterHelper.addStuff(accuracy));
sb.append('\n');
}
return sb.toString();
}
private static FileWriter saveToFile(final File file, final String contents) {
FileWriter writer = null;
try {
writer = new FileWriter(file);
writer.append(contents);
writer.flush();
} catch (final IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
return writer;
}
public void shareDataAsEmail(final Activity activity,
final BluetoothLeDeviceStore store) {
final long timeInMillis = System.currentTimeMillis();
final String filename = String.format(Locale.US, CSV_FILENAME_PREFIX, timeInMillis);
final String to = null;
final String subject = activity.getString(
R.string.exporter_email_device_list_subject,
TimeFormatter.getIsoDateTime(timeInMillis));
final String message = activity.getString(R.string.exporter_email_device_list_body);
final String contents = getListAsCsv(store.getDeviceList());
final File outputDir = getExternalCacheDir(activity);
if (outputDir == null) {
Toast.makeText(activity, R.string.error_unable_to_access_external_storage, Toast.LENGTH_SHORT).show();
} else {
try {
final File outputFile = File.createTempFile(filename, CSV_FILENAME_SUFFIX, outputDir);
saveToFile(outputFile, contents);
final Uri uri = Uri.fromFile(outputFile);
new Navigation(activity)
.shareFileViaEmail(uri, new String[]{to}, subject, message);
} catch (final IOException e) {
e.printStackTrace();
}
}
}
}
@@ -0,0 +1,36 @@
package uk.co.alt236.btlescan.ui.main.recyclerview.binder;
import android.content.Context;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.main.recyclerview.holder.CommonDeviceHolder;
import uk.co.alt236.btlescan.util.Constants;
/*package*/ class CommonBinding {
public static void bind(final Context context,
final CommonDeviceHolder holder,
final BluetoothLeDevice device) {
final String deviceName = device.getName();
final double rssi = device.getRssi();
if (deviceName != null && deviceName.length() > 0) {
holder.getDeviceName().setText(deviceName);
} else {
holder.getDeviceName().setText(R.string.unknown_device);
}
final String rssiString =
context.getString(R.string.formatter_db, String.valueOf(rssi));
final String runningAverageRssiString =
context.getString(R.string.formatter_db, String.valueOf(device.getRunningAverageRssi()));
holder.getDeviceLastUpdated().setText(
android.text.format.DateFormat.format(
Constants.TIME_FORMAT, new java.util.Date(device.getTimestamp())));
holder.getDeviceAddress().setText(device.getAddress());
holder.getDeviceRssi().setText(rssiString + " / " + runningAverageRssiString);
}
}
@@ -0,0 +1,54 @@
package uk.co.alt236.btlescan.ui.main.recyclerview.binder;
import android.content.Context;
import android.view.View;
import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconDevice;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.Navigation;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewBinder;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.main.recyclerview.holder.IBeaconHolder;
import uk.co.alt236.btlescan.ui.main.recyclerview.model.IBeaconItem;
import uk.co.alt236.btlescan.util.Constants;
public class IBeaconBinder extends BaseViewBinder<IBeaconItem> {
private final Navigation navigation;
public IBeaconBinder(Context context, Navigation navigation) {
super(context);
this.navigation = navigation;
}
@Override
public void bind(BaseViewHolder<IBeaconItem> holder, IBeaconItem item) {
final IBeaconHolder actualHolder = (IBeaconHolder) holder;
final IBeaconDevice device = item.getDevice();
final String accuracy = Constants.DOUBLE_TWO_DIGIT_ACCURACY.format(device.getAccuracy());
actualHolder.getIbeaconMajor().setText(String.valueOf(device.getMajor()));
actualHolder.getIbeaconMinor().setText(String.valueOf(device.getMinor()));
actualHolder.getIbeaconTxPower().setText(String.valueOf(device.getCalibratedTxPower()));
actualHolder.getIbeaconUUID().setText(device.getUUID());
actualHolder.getIbeaconDistance().setText(
getContext().getString(R.string.formatter_meters, accuracy));
actualHolder.getIbeaconDistanceDescriptor().setText(device.getDistanceDescriptor().toString());
CommonBinding.bind(getContext(), actualHolder, device);
actualHolder.getView().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
navigation.openDetailsActivity(device);
}
});
}
@Override
public boolean canBind(RecyclerViewItem item) {
return item instanceof IBeaconItem;
}
}
@@ -0,0 +1,42 @@
package uk.co.alt236.btlescan.ui.main.recyclerview.binder;
import android.content.Context;
import android.view.View;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.btlescan.ui.common.Navigation;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewBinder;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
import uk.co.alt236.btlescan.ui.main.recyclerview.holder.LeDeviceHolder;
import uk.co.alt236.btlescan.ui.main.recyclerview.model.LeDeviceItem;
public class LeDeviceBinder extends BaseViewBinder<LeDeviceItem> {
private final Navigation navigation;
public LeDeviceBinder(Context context, Navigation navigation) {
super(context);
this.navigation = navigation;
}
@Override
public void bind(BaseViewHolder<LeDeviceItem> holder, LeDeviceItem item) {
final LeDeviceHolder actualHolder = (LeDeviceHolder) holder;
final BluetoothLeDevice device = item.getDevice();
CommonBinding.bind(getContext(), actualHolder, device);
actualHolder.getView().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
navigation.openDetailsActivity(device);
}
});
}
@Override
public boolean canBind(RecyclerViewItem item) {
return item instanceof LeDeviceItem;
}
}
@@ -0,0 +1,13 @@
package uk.co.alt236.btlescan.ui.main.recyclerview.holder;
import android.widget.TextView;
public interface CommonDeviceHolder {
TextView getDeviceName();
TextView getDeviceAddress();
TextView getDeviceRssi();
TextView getDeviceLastUpdated();
}
@@ -0,0 +1,83 @@
package uk.co.alt236.btlescan.ui.main.recyclerview.holder;
import android.view.View;
import android.widget.TextView;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.main.recyclerview.model.IBeaconItem;
public class IBeaconHolder extends BaseViewHolder<IBeaconItem> implements CommonDeviceHolder {
private final TextView ibeaconUUID;
private final TextView ibeaconMajor;
private final TextView ibeaconMinor;
private final TextView ibeaconTxPower;
private final TextView ibeaconDistance;
private final TextView ibeaconDistanceDescriptor;
private final TextView deviceName;
private final TextView deviceAddress;
private final TextView deviceRssi;
private final TextView deviceLastUpdated;
public IBeaconHolder(View itemView) {
super(itemView);
this.deviceAddress = (TextView) itemView.findViewById(R.id.device_address);
this.deviceName = (TextView) itemView.findViewById(R.id.device_name);
this.deviceRssi = (TextView) itemView.findViewById(R.id.device_rssi);
this.deviceLastUpdated = (TextView) itemView.findViewById(R.id.device_last_update);
this.ibeaconMajor = (TextView) itemView.findViewById(R.id.ibeacon_major);
this.ibeaconMinor = (TextView) itemView.findViewById(R.id.ibeacon_minor);
this.ibeaconDistance = (TextView) itemView.findViewById(R.id.ibeacon_distance);
this.ibeaconUUID = (TextView) itemView.findViewById(R.id.ibeacon_uuid);
this.ibeaconTxPower = (TextView) itemView.findViewById(R.id.ibeacon_tx_power);
this.ibeaconDistanceDescriptor = (TextView) itemView.findViewById(R.id.ibeacon_distance_descriptor);
}
@Override
public TextView getDeviceName() {
return deviceName;
}
@Override
public TextView getDeviceAddress() {
return deviceAddress;
}
@Override
public TextView getDeviceRssi() {
return deviceRssi;
}
@Override
public TextView getDeviceLastUpdated() {
return deviceLastUpdated;
}
public TextView getIbeaconUUID() {
return ibeaconUUID;
}
public TextView getIbeaconMajor() {
return ibeaconMajor;
}
public TextView getIbeaconMinor() {
return ibeaconMinor;
}
public TextView getIbeaconTxPower() {
return ibeaconTxPower;
}
public TextView getIbeaconDistance() {
return ibeaconDistance;
}
public TextView getIbeaconDistanceDescriptor() {
return ibeaconDistanceDescriptor;
}
}
@@ -0,0 +1,45 @@
package uk.co.alt236.btlescan.ui.main.recyclerview.holder;
import android.view.View;
import android.widget.TextView;
import uk.co.alt236.btlescan.R;
import uk.co.alt236.btlescan.ui.common.recyclerview.BaseViewHolder;
import uk.co.alt236.btlescan.ui.main.recyclerview.model.LeDeviceItem;
public class LeDeviceHolder extends BaseViewHolder<LeDeviceItem> implements CommonDeviceHolder {
private final TextView deviceName;
private final TextView deviceAddress;
private final TextView deviceRssi;
private final TextView deviceLastUpdated;
public LeDeviceHolder(View itemView) {
super(itemView);
this.deviceAddress = (TextView) itemView.findViewById(R.id.device_address);
this.deviceName = (TextView) itemView.findViewById(R.id.device_name);
this.deviceRssi = (TextView) itemView.findViewById(R.id.device_rssi);
this.deviceLastUpdated = (TextView) itemView.findViewById(R.id.device_last_update);
}
@Override
public TextView getDeviceName() {
return deviceName;
}
@Override
public TextView getDeviceAddress() {
return deviceAddress;
}
@Override
public TextView getDeviceRssi() {
return deviceRssi;
}
@Override
public TextView getDeviceLastUpdated() {
return deviceLastUpdated;
}
}
@@ -0,0 +1,18 @@
package uk.co.alt236.btlescan.ui.main.recyclerview.model;
import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconDevice;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
public class IBeaconItem implements RecyclerViewItem {
private final IBeaconDevice device;
public IBeaconItem(final IBeaconDevice device) {
this.device = device;
}
public IBeaconDevice getDevice() {
return device;
}
}
@@ -0,0 +1,17 @@
package uk.co.alt236.btlescan.ui.main.recyclerview.model;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.btlescan.ui.common.recyclerview.RecyclerViewItem;
public class LeDeviceItem implements RecyclerViewItem {
private final BluetoothLeDevice device;
public LeDeviceItem(final BluetoothLeDevice device) {
this.device = device;
}
public BluetoothLeDevice getDevice() {
return device;
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 445 B

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 548 B

After

Width:  |  Height:  |  Size: 899 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 824 B

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 911 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -6,9 +6,9 @@
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</ListView>
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
@@ -57,7 +57,7 @@
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@id/deviceInformation"
android:background="@android:color/holo_blue_dark"/>
android:background="@color/colorSeparator" />
<GridLayout
android:id="@+id/gattInformation"
@@ -113,7 +113,7 @@
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_above="@id/gattInformation"
android:background="@android:color/holo_blue_dark"/>
android:background="@color/colorSeparator" />
<ExpandableListView
android:id="@+id/gatt_services_list"
@@ -7,7 +7,7 @@
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
tools:context=".ui.main.MainActivity">
<GridLayout
android:id="@+id/gridLayout1"
@@ -61,7 +61,7 @@
android:id="@+id/upperSepparator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/holo_blue_dark"/>
android:background="@color/colorSeparator" />
<RelativeLayout
android:layout_width="match_parent"
@@ -79,7 +79,7 @@
android:id="@+id/lowerSepparator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/holo_blue_dark"/>
android:background="@color/colorSeparator" />
<TextView
android:id="@+id/tvItemCount"
@@ -96,7 +96,7 @@
android:layout_alignParentTop="true"
android:orientation="vertical">
<ListView
<android.support.v7.widget.RecyclerView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="?dialogPreferredPadding">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?xml version="1.0" encoding="utf-8"?><!--
Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,78 +16,24 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:gravity="top"
android:orientation="horizontal">
<ImageView
android:id="@+id/device_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="36dp"
android:layout_height="36dp"
android:paddingRight="5dp"
android:paddingTop="5dp"
android:src="@drawable/ic_bluetooth"/>
android:src="@drawable/ic_device_ibeacon"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/device_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="24sp"/>
<GridLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:columnCount="2">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/label_mac"
android:textSize="12sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/device_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:typeface="monospace"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/label_updated"
android:textSize="12sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/device_last_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:textSize="12sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/label_rssi"
android:textSize="12sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/device_rssi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:textSize="12sp"/>
</GridLayout>
<include layout="@layout/viewpart_list_item_device_common"/>
<GridLayout
android:id="@+id/ibeacon_section"
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:gravity="top"
android:orientation="horizontal">
<ImageView
android:id="@+id/device_icon"
android:layout_width="36dp"
android:layout_height="36dp"
android:paddingRight="5dp"
android:paddingTop="5dp"
android:src="@drawable/ic_bluetooth"/>
<include layout="@layout/viewpart_list_item_device_common"/>
</LinearLayout>
@@ -4,7 +4,7 @@
android:layout_height="wrap_content"
android:gravity="top"
android:orientation="vertical"
android:paddingBottom="5dp">
android:paddingBottom="@dimen/space_between_sections">
<TextView
android:id="@+id/title"
@@ -4,7 +4,7 @@
android:layout_height="wrap_content"
android:gravity="top"
android:orientation="vertical"
android:paddingBottom="5dp">
android:paddingBottom="@dimen/space_between_sections">
<GridLayout
android:layout_width="match_parent"
@@ -4,20 +4,15 @@
android:layout_height="wrap_content"
android:gravity="top"
android:orientation="vertical"
android:paddingBottom="5dp">
android:paddingBottom="6dp">
<TextView
android:id="@+id/title"
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAllCaps="true"
android:textColor="@android:color/holo_blue_dark"
android:textColor="@color/colorSeparator"
android:textSize="14sp"
android:textStyle="bold"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/holo_blue_dark"/>
</LinearLayout>
@@ -4,7 +4,7 @@
android:layout_height="wrap_content"
android:gravity="top"
android:orientation="vertical"
android:paddingBottom="5dp">
android:paddingBottom="@dimen/space_between_sections">
<GridLayout
android:layout_width="match_parent"
@@ -4,7 +4,7 @@
android:layout_height="wrap_content"
android:gravity="top"
android:orientation="vertical"
android:paddingBottom="5dp">
android:paddingBottom="@dimen/space_between_sections">
<GridLayout
android:layout_width="match_parent"
@@ -4,10 +4,10 @@
android:layout_height="wrap_content"
android:gravity="top"
android:orientation="vertical"
android:paddingBottom="5dp">
android:paddingBottom="@dimen/space_between_sections">
<TextView
android:id="@+id/data"
android:id="@+id/text"
style="@style/GridLayoutDataTextViewMonospace"/>
</LinearLayout>
@@ -0,0 +1,65 @@
<?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:orientation="vertical">
<TextView
android:id="@+id/device_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="24sp"/>
<GridLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:columnCount="2">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/label_mac"
android:textSize="12sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/device_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:typeface="monospace"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/label_updated"
android:textSize="12sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/device_last_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:textSize="12sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/label_rssi"
android:textSize="12sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/device_rssi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:textSize="12sp"/>
</GridLayout>
</LinearLayout>
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

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