diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f885ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.gradle +/local.properties +/.idea/workspace.xml +.DS_Store +/build diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..987ad50 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +BG Scout \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..217af47 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..e206d70 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..ac329d2 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/.idea/libraries/mongo_java_driver_2_12_3.xml b/.idea/libraries/mongo_java_driver_2_12_3.xml new file mode 100644 index 0000000..bca31c8 --- /dev/null +++ b/.idea/libraries/mongo_java_driver_2_12_3.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/play_services_wearable_5_0_77.xml b/.idea/libraries/play_services_wearable_5_0_77.xml new file mode 100644 index 0000000..cd236ac --- /dev/null +++ b/.idea/libraries/play_services_wearable_5_0_77.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/square_otto_1_3_2.xml b/.idea/libraries/square_otto_1_3_2.xml new file mode 100644 index 0000000..3f91bb9 --- /dev/null +++ b/.idea/libraries/square_otto_1_3_2.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/support_annotations_20_0_0.xml b/.idea/libraries/support_annotations_20_0_0.xml new file mode 100644 index 0000000..ce41c6b --- /dev/null +++ b/.idea/libraries/support_annotations_20_0_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/support_v13_20_0_0.xml b/.idea/libraries/support_v13_20_0_0.xml new file mode 100644 index 0000000..29145e7 --- /dev/null +++ b/.idea/libraries/support_v13_20_0_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/support_v4_19_1_0.xml b/.idea/libraries/support_v4_19_1_0.xml new file mode 100644 index 0000000..9752c4c --- /dev/null +++ b/.idea/libraries/support_v4_19_1_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/support_v4_20_0_0.xml b/.idea/libraries/support_v4_20_0_0.xml new file mode 100644 index 0000000..7574a44 --- /dev/null +++ b/.idea/libraries/support_v4_20_0_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/wearable_1_0_0.xml b/.idea/libraries/wearable_1_0_0.xml new file mode 100644 index 0000000..58144b3 --- /dev/null +++ b/.idea/libraries/wearable_1_0_0.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..b153e48 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..a18bf9a --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml new file mode 100644 index 0000000..922003b --- /dev/null +++ b/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..275077f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/BGScout.iml b/BGScout.iml new file mode 100644 index 0000000..0bb6048 --- /dev/null +++ b/BGScout.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..a75fc81 --- /dev/null +++ b/build.gradle @@ -0,0 +1,19 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:0.12.+' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + } +} diff --git a/glass/.gitignore b/glass/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/glass/.gitignore @@ -0,0 +1 @@ +/build diff --git a/glass/build.gradle b/glass/build.gradle new file mode 100644 index 0000000..5acbde6 --- /dev/null +++ b/glass/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.application' + +repositories { + jcenter() + flatDir { + dirs 'prebuilt-libs' + } +} + +android { + compileSdkVersion 'Google Inc.:Glass Development Kit Preview:19' + buildToolsVersion "20.0.0" + + defaultConfig { + applicationId "com.ktind.cgm.bgscout" + minSdkVersion 19 + targetSdkVersion 20 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + runProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) +} diff --git a/glass/glass.iml b/glass/glass.iml new file mode 100644 index 0000000..508b71d --- /dev/null +++ b/glass/glass.iml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/glass/proguard-rules.pro b/glass/proguard-rules.pro new file mode 100644 index 0000000..8b3ee7d --- /dev/null +++ b/glass/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/klee24/Android/android-sdk-mac_x86/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/glass/src/androidTest/java/com/ktind/cgm/bgscout/ApplicationTest.java b/glass/src/androidTest/java/com/ktind/cgm/bgscout/ApplicationTest.java new file mode 100644 index 0000000..c16e3ed --- /dev/null +++ b/glass/src/androidTest/java/com/ktind/cgm/bgscout/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.ktind.cgm.bgscout; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/glass/src/main/AndroidManifest.xml b/glass/src/main/AndroidManifest.xml new file mode 100644 index 0000000..523378a --- /dev/null +++ b/glass/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/glass/src/main/res/drawable-hdpi/ic_launcher.png b/glass/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..96a442e Binary files /dev/null and b/glass/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/glass/src/main/res/drawable-mdpi/ic_launcher.png b/glass/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..359047d Binary files /dev/null and b/glass/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/glass/src/main/res/drawable-xhdpi/ic_launcher.png b/glass/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000..71c6d76 Binary files /dev/null and b/glass/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/glass/src/main/res/drawable-xxhdpi/ic_launcher.png b/glass/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..4df1894 Binary files /dev/null and b/glass/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/glass/src/main/res/values/strings.xml b/glass/src/main/res/values/strings.xml new file mode 100644 index 0000000..1f3c202 --- /dev/null +++ b/glass/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + BG Scout + diff --git a/glass/src/main/res/values/styles.xml b/glass/src/main/res/values/styles.xml new file mode 100644 index 0000000..65a325d --- /dev/null +++ b/glass/src/main/res/values/styles.xml @@ -0,0 +1,5 @@ + + + + diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..5d08ba7 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,18 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Settings specified in this file will override any Gradle settings +# configured through the IDE. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..8c0fb64 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1e61d1f --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Apr 10 15:27:10 PDT 2013 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..91a7e26 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/mobile/.gitignore b/mobile/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/mobile/.gitignore @@ -0,0 +1 @@ +/build diff --git a/mobile/build.gradle b/mobile/build.gradle new file mode 100644 index 0000000..56d2bab --- /dev/null +++ b/mobile/build.gradle @@ -0,0 +1,28 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 20 + buildToolsVersion '20.0.0' + + defaultConfig { + applicationId "com.ktind.cgm.bgscout" + minSdkVersion 16 + targetSdkVersion 20 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + runProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + wearApp project(':wear') + compile 'com.google.android.gms:play-services-wearable:+' + // You must install or update the Support Repository through the SDK manager to use this dependency. + compile 'com.android.support:support-v4:19.+' +} diff --git a/mobile/libs/mongo-java-driver-2.12.3.jar b/mobile/libs/mongo-java-driver-2.12.3.jar new file mode 100644 index 0000000..98c555a Binary files /dev/null and b/mobile/libs/mongo-java-driver-2.12.3.jar differ diff --git a/mobile/libs/square-otto-1.3.2.jar b/mobile/libs/square-otto-1.3.2.jar new file mode 100644 index 0000000..7a2ed89 Binary files /dev/null and b/mobile/libs/square-otto-1.3.2.jar differ diff --git a/mobile/mobile.iml b/mobile/mobile.iml new file mode 100644 index 0000000..f94d476 --- /dev/null +++ b/mobile/mobile.iml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/proguard-rules.pro b/mobile/proguard-rules.pro new file mode 100644 index 0000000..8b3ee7d --- /dev/null +++ b/mobile/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/klee24/Android/android-sdk-mac_x86/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/mobile/src/androidTest/java/com/ktind/cgm/bgscout/ApplicationTest.java b/mobile/src/androidTest/java/com/ktind/cgm/bgscout/ApplicationTest.java new file mode 100644 index 0000000..c16e3ed --- /dev/null +++ b/mobile/src/androidTest/java/com/ktind/cgm/bgscout/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.ktind.cgm.bgscout; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c368441 --- /dev/null +++ b/mobile/src/main/AndroidManifest.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/AbstractCGMDevice.java b/mobile/src/main/java/com/ktind/cgm/bgscout/AbstractCGMDevice.java new file mode 100644 index 0000000..98bff25 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/AbstractCGMDevice.java @@ -0,0 +1,156 @@ +package com.ktind.cgm.bgscout; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.os.Handler; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Date; + +/** + * Created by klee24 on 8/2/14. + */ +abstract public class AbstractCGMDevice implements CGMDeviceInterface { + private static final String TAG = DeviceDownloadService.class.getSimpleName(); + protected String name; + protected int deviceID; +// private Date date; + protected GlucoseUnit unit =GlucoseUnit.MGDL; + protected ArrayList monitors=new ArrayList(); + protected DeviceDownloadObject lastDownloadObject; + protected Context appContext; + protected CGMTransportAbstract cgmTransport; + protected MonitorProxy monitorProxy=new MonitorProxy(); + protected boolean virtual =false; + protected long nextFire=45000L; + protected Handler mHandler; + protected int pollInterval=302000; + + public boolean isVirtual() { + return virtual; + } + + public AbstractCGMDevice(String n,int deviceID,Context appContext,Handler mH){ + Log.i(TAG, "Creating CGM " + n); + setName(n); + this.setDeviceID(deviceID); + this.setAppContext(appContext); + this.setHandler(mH); + + AbstractMonitor anm=new AndroidNotificationMonitor(getName(),deviceID,appContext); + AbstractMonitor mongo=new MongoUploadMonitor(getName()); + monitors.add(anm); + monitors.add(mongo); + monitorProxy.setMonitors(monitors); + } + + public void setHandler(Handler mH){ + this.mHandler=mH; + } + + public float getUploaderBattery(){ + IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); + Intent batteryStatus = appContext.registerReceiver(null, ifilter); + int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); + int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1); + return level / (float) scale; + } + + abstract int getCGMBattery(); + + public int getDeviceID() { + return deviceID; + } + + public void setDeviceID(int deviceID) { + this.deviceID = deviceID; + } + + public Context getAppContext() { + return appContext; + } + + final public DeviceDownloadObject download(){ + lastDownloadObject=this.doDownload(); +// lastDownloadObject.setLastDownloadDate(new Date()); + return lastDownloadObject; + } + + abstract protected DeviceDownloadObject doDownload(); + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public GlucoseUnit getUnit() { + return unit; + } + + public void stopMonitors(){ + monitorProxy.stopMonitors(); + } + + public void setUnit(GlucoseUnit unit) { + this.unit = unit; + } + + @Override + public void fireMonitors() { + // FIXME - Not sure this is healthy....? + MonitorProxy myProxy=new MonitorProxy(monitorProxy); + myProxy.execute(lastDownloadObject); + } + + + + public boolean isConnected(){ + return cgmTransport.isOpen(); + } + + public void setAppContext(Context appContext) { + this.appContext = appContext; + } + +// public abstract float getUploaderBattery(); + + public abstract void connect() throws DeviceNotConnected; + + public abstract void disconnect(); + + public int getPollInterval() { + return pollInterval; + } + + public void setPollInterval(int pollInterval) { + Log.d(TAG,"Setting poll interval to: "+pollInterval); + this.pollInterval = pollInterval; + } + + public long nextFire(){ + return nextFire(getPollInterval()); + } + + public long nextFire(long millis){ + if (lastDownloadObject!=null){ + long diff=(millis-(new Date().getTime() - lastDownloadObject.getEgvRecords()[lastDownloadObject.getEgvRecords().length-1].getDate().getTime())); + Log.d(TAG,"nextFire calculated to be: "+diff+" for "+getName()+" using a poll interval of "+millis); + if (diff<0) { + Log.d(TAG,"nextFire returning 45 seconds because diff was negative"); + return 45000; + } + return diff; + } else { + Log.d(TAG,"nextFire returning 45 seconds because there wasn't a lastdownloadobject set"); + return 450000; + } + } + +// public abstract G4EGVRecord[] getReadings(); +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/AbstractMonitor.java b/mobile/src/main/java/com/ktind/cgm/bgscout/AbstractMonitor.java new file mode 100644 index 0000000..081b524 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/AbstractMonitor.java @@ -0,0 +1,62 @@ +package com.ktind.cgm.bgscout; + +import android.util.Log; + +/** + * Created by klee24 on 8/2/14. + */ +abstract public class AbstractMonitor implements MonitorInterface { + private static final String TAG = DeviceDownloadService.class.getSimpleName(); + protected String name; + //FIXME find a better way to manage this? + protected boolean allowVirtual=false; + protected String monitorType="generic"; + protected int highThreshold=180; + protected int lowThreshold=60; + + public AbstractMonitor(String n){ + this.name=n; + } + + public String getMonitorType() { + return monitorType; + } + + public void setMonitorType(String monitorType) { + this.monitorType = monitorType; + } + + abstract protected void doProcess(DeviceDownloadObject d); + + public boolean isAllowVirtual() { + return allowVirtual; + } + + public void setAllowVirtual(boolean allowVirtual) { + this.allowVirtual = allowVirtual; + } + + @Override + final public void process(DeviceDownloadObject d) { + Log.d(TAG,"Monitor "+name+" has fired for "+monitorType); + if (isAllowVirtual() || ! d.getDevice().isVirtual()){ + Log.d(TAG, "Processing monitor "+name+" for "+monitorType); + this.doProcess(d); + } else { + Log.d(TAG, "Not processing monitor "+name+" for "+monitorType); + } + } + + @Override + public void stop(){ + Log.i(TAG,"Stopping monitor "+monitorType+" for "+name); + } + + public void setHighThreshold(int highThreshold) { + this.highThreshold = highThreshold; + } + + public void setLowThreshold(int lowThreshold) { + this.lowThreshold = lowThreshold; + } +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/AndroidNotificationMonitor.java b/mobile/src/main/java/com/ktind/cgm/bgscout/AndroidNotificationMonitor.java new file mode 100644 index 0000000..f5a8f58 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/AndroidNotificationMonitor.java @@ -0,0 +1,184 @@ +package com.ktind.cgm.bgscout; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.Log; + +/** + * Created by klee24 on 8/2/14. + */ +public class AndroidNotificationMonitor extends AbstractMonitor { + private static final String TAG = AndroidNotificationMonitor.class.getSimpleName(); + protected int notifID; + protected Context appContext; + protected Notification.Builder notifBuilder; + protected NotificationManager mNotifyMgr; + final protected String monitorType="android notification"; + + public NotificationManager getmNotifyMgr() { + return mNotifyMgr; + } + + public void setmNotifyMgr(NotificationManager mNotifyMgr) { + this.mNotifyMgr = mNotifyMgr; + } + + public int getNotifID() { + return notifID; + } + + public void setNotifID(int notifID) { + this.notifID = notifID; + } + + public Context getAppContext() { + return appContext; + } + + public void setAppContext(Context appContext) { + this.appContext = appContext; + } + + public Notification.Builder getNotifBuilder() { + return notifBuilder; + } + + public void setNotifBuilder(Notification.Builder notifBuilder) { + this.notifBuilder = notifBuilder; + } + + AndroidNotificationMonitor(String name,int notifID,Context appContext){ + super(name); + + this.setNotifID(notifID); +// this.name=name; + this.setAppContext(appContext); + this.setMonitorType("mongo uploader"); + mNotifyMgr = (NotificationManager) appContext.getSystemService(Context.NOTIFICATION_SERVICE); + PendingIntent contentIntent = PendingIntent.getActivity(appContext, 0, new Intent(appContext, MainActivity.class), 0); + Bitmap bm = BitmapFactory.decodeResource(appContext.getResources(), R.drawable.icon); + this.setNotifBuilder(new Notification.Builder(appContext) + .setSmallIcon(R.drawable.icon) + .setContentTitle(name) + .setContentText("Monitor started. No data yet") + .setContentIntent(contentIntent) + .setOngoing(true) +// .addAction(R.drawable.icon24x24, "Snooze", contentIntent) + .setLargeIcon(bm)); + Notification notification = notifBuilder.build(); + mNotifyMgr.notify(notifID, notification); + this.setAllowVirtual(true); + } + + @Override + public void doProcess(DeviceDownloadObject dl) { + EGVRecord[] recs = dl.getEgvRecords(); + EGVRecord lastRec = null; + if (recs != null && recs.length > 0) + lastRec = recs[recs.length - 1]; + String msg=""; + + Notification notification; + int icon = R.drawable.questionmarkicon; + Log.v(TAG,"Status: "+dl.getStatus().toString()); + if (dl.getStatus() != DownloadStatus.SUCCESS && dl.getStatus() != DownloadStatus.SPECIALVALUE) { + msg = dl.getStatus().toString()+"\n"; + notifBuilder.setTicker(msg); + } + if (dl.getStatus() == DownloadStatus.SPECIALVALUE){ + msg = dl.getSpecialValueMessage()+"\n"; + notifBuilder.setTicker(msg); + } + +// if (dl.getStatus()== DownloadStatus.NORECORDS){ +// msg+=dl.getStatus().toString(); +// notifBuilder.setTicker(msg); +// } + + if (lastRec != null && dl.getStatus() != DownloadStatus.SPECIALVALUE) { + icon = getIcon(lastRec.getEgv(), lastRec.getTrend()); + msg+= "BG: " + lastRec.getEgv() + " " + dl.getDevice().getUnit().toString() + " and " + lastRec.getTrend().toString(); +// msg+="\nLast reading: "+lastRec.getDate().toString(); + notifBuilder.setContentText(msg); +// notifBuilder.setDefaults(Notification.DEFAULT_ALL); + } + notifBuilder.setStyle(new Notification.BigTextStyle().bigText(msg)); + notification = notifBuilder + .setSmallIcon(icon) + .build(); + mNotifyMgr.notify(notifID, notification); + } + + + private int getIcon(int bgValue,Trend trend){ + // Handle "NOT COMPUTABLE", "RATE OUT OF RANGE", and anything else that crops up. + int icon=R.drawable.questionmarkicon; + if (bgValue>=highThreshold){ + if (trend==Trend.NONE) { + icon=R.drawable.nonered; + }else if(trend==Trend.DOUBLEUP) { + icon=R.drawable.arrowdoubleupred; + }else if(trend==Trend.SINGLEUP) { + icon=R.drawable.arrowupred; + }else if(trend==Trend.FORTYFIVEUP) { + icon=R.drawable.arrow45upred; + }else if(trend==Trend.FLAT) { + icon=R.drawable.arrowflatred; + }else if(trend==Trend.DOUBLEDOWN) { + icon=R.drawable.arrowdoubledownred; + }else if(trend==Trend.SINGLEDOWN) { + icon=R.drawable.arrowdownred; + }else if(trend==Trend.FORTYFIVEDOWN) { + icon=R.drawable.arrow45downred; + } + }else if (bgValue<=lowThreshold){ + if (trend==Trend.NONE) { + icon=R.drawable.noneyellow; + }else if(trend==Trend.DOUBLEUP) { + icon=R.drawable.arrowdoubleupyellow; + }else if(trend==Trend.SINGLEUP) { + icon=R.drawable.arrowupyellow; + }else if(trend==Trend.FORTYFIVEUP) { + icon=R.drawable.arrow45upyellow; + }else if(trend==Trend.FLAT) { + icon=R.drawable.arrowflatyellow; + }else if(trend==Trend.DOUBLEDOWN) { + icon=R.drawable.arrowdoubledownyellow; + }else if(trend==Trend.SINGLEDOWN) { + icon=R.drawable.arrowdownyellow; + }else if(trend==Trend.FORTYFIVEDOWN) { + icon=R.drawable.arrow45downyellow; + } + }else{ + if (trend==Trend.NONE) { + icon=R.drawable.noneblue; + }else if(trend==Trend.DOUBLEUP) { + icon=R.drawable.arrowdoubleupblue; + }else if(trend==Trend.SINGLEUP) { + icon=R.drawable.arrowupblue; + }else if(trend==Trend.FORTYFIVEUP) { + icon=R.drawable.arrow45upblue; + }else if(trend==Trend.FLAT) { + icon=R.drawable.arrowflatblue; + }else if(trend==Trend.DOUBLEDOWN) { + icon=R.drawable.arrowdoubledownblue; + }else if(trend==Trend.SINGLEDOWN) { + icon=R.drawable.arrowdownblue; + }else if(trend==Trend.FORTYFIVEDOWN) { + icon=R.drawable.arrow45downblue; + } + } + return icon; + } + + @Override + public void stop() { + Log.i(TAG, "Stopping monitor " + monitorType + " for " + name); + mNotifyMgr.cancel(notifID); + } +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/BitTools.java b/mobile/src/main/java/com/ktind/cgm/bgscout/BitTools.java new file mode 100644 index 0000000..823d6b8 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/BitTools.java @@ -0,0 +1,48 @@ +package com.ktind.cgm.bgscout; + +//import android.util.Log; + +/** + * Created by klee24 on 7/20/14. + */ +public class BitTools { + private static final String TAG = BitTools.class.getSimpleName(); + final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); + + // TODO Need to do some more checking but in theory this could be larger than an int. + public static int byteArraytoInt(byte[] b){ +// if (b == null || b.length<4) + if (b == null ) + return -1; + int val=0; + int counter=0; + for (byte v: b){ + val+=(v & 0x000000FF) << counter*8; + counter+=1; + } + return val; + } + + + + public static String bytesToHex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 3]; + for ( int j = 0; j < bytes.length; j++ ) { + int v = bytes[j] & 0xFF; + hexChars[j * 3] = hexArray[v >>> 4]; + hexChars[j * 3 + 1] = hexArray[v & 0x0F]; + hexChars[j * 3 + 2] = " ".toCharArray()[0]; + } + return new String(hexChars); + } + + public static byte[] intToByteArray(int i){ + byte[] byteArray=new byte[4]; + byteArray[0]=(byte) (i & 0xFF); + byteArray[1]=(byte) ((i >> 8) & 0xFF); + byteArray[2]=(byte) ((i >> 16) & 0xFF); + byteArray[3]=(byte) ((i >> 24) & 0xFF); + return byteArray; + } + +} \ No newline at end of file diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/CGMBus.java b/mobile/src/main/java/com/ktind/cgm/bgscout/CGMBus.java new file mode 100644 index 0000000..6aaddba --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/CGMBus.java @@ -0,0 +1,14 @@ +package com.ktind.cgm.bgscout; + +import com.squareup.otto.Bus; + +/** + * Created by klee24 on 7/29/14. + */ +public class CGMBus { + private static final Bus bus=new Bus(); + + public static Bus getInstance(){ + return bus; + } +} \ No newline at end of file diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/CGMDeviceInterface.java b/mobile/src/main/java/com/ktind/cgm/bgscout/CGMDeviceInterface.java new file mode 100644 index 0000000..019ef81 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/CGMDeviceInterface.java @@ -0,0 +1,10 @@ +package com.ktind.cgm.bgscout; + +/** + * Created by klee24 on 8/2/14. + */ +public interface CGMDeviceInterface { + public DeviceDownloadObject download(); + void fireMonitors(); + void stopMonitors(); +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/CGMTransportAbstract.java b/mobile/src/main/java/com/ktind/cgm/bgscout/CGMTransportAbstract.java new file mode 100644 index 0000000..56fe02f --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/CGMTransportAbstract.java @@ -0,0 +1,28 @@ +package com.ktind.cgm.bgscout; + +import android.util.Log; + +import java.io.IOException; + + +/** + * Created by klee24 on 8/2/14. + */ +abstract public class CGMTransportAbstract { + protected boolean isopen=false; + protected boolean chargeDevice=false; + + abstract public boolean open() throws DeviceNotConnected; + abstract public void close(); + abstract public int read(byte[] responseBuffer,int timeoutMillis) throws IOException; + abstract public int write(byte[] packet,int timeoutMillis) throws IOException; + + public void setChargeReceiver(boolean c){ + Log.d("G4Device", "Abstract setting charge receiver to " + c); + chargeDevice=c; + } + + public boolean isOpen(){ + return isopen; + } +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/Constants.java b/mobile/src/main/java/com/ktind/cgm/bgscout/Constants.java new file mode 100644 index 0000000..a8b9224 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/Constants.java @@ -0,0 +1,8 @@ +package com.ktind.cgm.bgscout; + +/** + * Created by klee24 on 8/2/14. + */ +public class Constants { + public final static int READINGINTERVALMS=45000; +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceDownloadObject.java b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceDownloadObject.java new file mode 100644 index 0000000..9331408 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceDownloadObject.java @@ -0,0 +1,69 @@ +package com.ktind.cgm.bgscout; + +import java.util.Date; + +/** + * Created by klee24 on 8/2/14. + */ +public class DeviceDownloadObject { + private AbstractCGMDevice device; + private EGVRecord[] egvRecords; + private DownloadStatus status; + private String specialValueMessage; + private Date lastDownloadDate; + + public Date getLastDownloadDate() { + return lastDownloadDate; + } + + public void setLastDownloadDate(Date lastDownloadDate) { + this.lastDownloadDate = lastDownloadDate; + } + + public String getSpecialValueMessage() { + return specialValueMessage; + } + + public void setSpecialValueMessage(String specialValueMessage) { + this.specialValueMessage = specialValueMessage; + } + + DeviceDownloadObject(AbstractCGMDevice c,EGVRecord[] e, DownloadStatus s){ + super(); + setDevice(c); + setEgvRecords(e); + setStatus(s); + } + + DeviceDownloadObject(){ + egvRecords=new EGVRecord[0]; + } + + public AbstractCGMDevice getDevice() { + return device; + } + + public void setDevice(AbstractCGMDevice device) { + this.device = device; + } + + public EGVRecord[] getEgvRecords() { + return egvRecords; + } + + public void setEgvRecords(EGVRecord[] egvRecords) { + this.egvRecords = egvRecords; + } + + public DownloadStatus getStatus() { + return status; + } + + public void setStatus(DownloadStatus status) { + this.status = status; + } + +// public boolean didFail(){ +// return this.getStatus()!=DownloadStatus.SUCCESS; +// } +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceDownloadService.java b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceDownloadService.java new file mode 100644 index 0000000..ba24da8 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceDownloadService.java @@ -0,0 +1,121 @@ +package com.ktind.cgm.bgscout; + +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.AsyncTask; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.util.Log; + +import com.squareup.otto.Subscribe; + +import java.util.ArrayList; + +public class DeviceDownloadService extends Service { + private static final String TAG = DeviceDownloadService.class.getSimpleName(); + private ArrayList cgms=new ArrayList(); + private DeviceDownloadObject lastDownload; + private Notification.Builder notificationBuilder; + private Handler mHandler=new Handler(); + + @Override + public void onCreate() { + super.onCreate(); + CGMBus.getInstance().register(this); +// cgms.add(new G4CGMDevice("Melissa",1,getBaseContext(),mHandler)); + cgms.add(new RemoteMongoDevice("Melissa",1,getBaseContext(),mHandler)); +// cgms.add(new FakeCGMDevice("Billy",2,this.getBaseContext())); +// cgms.add(new FakeCGMDevice("Sue",3,this.getBaseContext())); + Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.icon); + PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, new Intent(getApplicationContext(), MainActivity.class), 0); + notificationBuilder = new Notification.Builder(getApplicationContext()) + .setContentTitle(getText(R.string.cgm_service_title)) + .setContentIntent(contentIntent) + .setSmallIcon(R.drawable.icon24x24) + .setNumber(cgms.size()) + .setLargeIcon(bm); + } + + //FIXME right now this treats all CGMs as firing at the same time. Need individual timers. + private Runnable pollDevices = new Runnable() { + @Override + public void run() { + for (AbstractCGMDevice cgm:cgms) { + innerDeviceProxy deviceProxy=new innerDeviceProxy(cgm); + deviceProxy.execute(); + } +// mHandler.postDelayed(pollDevices, nextFire); + } + }; + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Notification notification=notificationBuilder.build(); + for (AbstractCGMDevice cgm:cgms) { + innerDeviceProxy deviceProxy=new innerDeviceProxy(cgm); + deviceProxy.execute(); + } +// mHandler.post(pollDevices); + startForeground(1,notification); + return super.onStartCommand(intent, flags, startId); + } + + @Override + public void onDestroy() { +// mHandler.removeCallbacks(pollDevices); + CGMBus.getInstance().unregister(this); + stopForeground(false); + for (AbstractCGMDevice cgm:cgms){ + cgm.stopMonitors(); + } + super.onDestroy(); + } + + @Override + public IBinder onBind(Intent intent) { + // TODO: Return the communication channel to the service. + throw new UnsupportedOperationException("Not yet implemented"); + } + + @Subscribe + public void onDeviceDownloadObject(AbstractCGMDevice cgm) { + Log.d(TAG,"message received! Starting over again!"); + innerDeviceProxy deviceProxy=new innerDeviceProxy(cgm); + deviceProxy.execute(); + } + + public class innerDeviceProxy extends AsyncTask { + AbstractCGMDevice cgmDevice; + Handler myInnerHandler=new Handler(); + + innerDeviceProxy(AbstractCGMDevice cD){ + super(); + this.cgmDevice=cD; + } + + public Runnable nextPoll = new Runnable() { + @Override + public void run() { + CGMBus.getInstance().post(cgmDevice); +// onDeviceDownloadObject(cgmDevice); + } + }; + + @Override protected DeviceDownloadObject doInBackground(Void... params) { + return cgmDevice.download(); + } + + @Override protected void onPostExecute(DeviceDownloadObject result) { + super.onPostExecute(result); + result.getDevice().fireMonitors(); + myInnerHandler.postDelayed(nextPoll, result.getDevice().nextFire()); + } + + } + +} \ No newline at end of file diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceDownloadServiceInterface.java b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceDownloadServiceInterface.java new file mode 100644 index 0000000..43de0c4 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceDownloadServiceInterface.java @@ -0,0 +1,9 @@ +package com.ktind.cgm.bgscout; + +/** + * Created by klee24 on 8/2/14. + */ +public interface DeviceDownloadServiceInterface { + public DeviceDownloadObject downloadDevice(); + public void processDownload(DeviceDownloadObject d); +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceNotConnected.java b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceNotConnected.java new file mode 100644 index 0000000..caa65fa --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceNotConnected.java @@ -0,0 +1,17 @@ +package com.ktind.cgm.bgscout; + +/** + * Created by klee24 on 8/3/14. + */ +public class DeviceNotConnected extends Exception{ + public DeviceNotConnected(){} + + public DeviceNotConnected(String message){ + super(message); + } + + public DeviceNotConnected(String message, Throwable e){ + super(message,e); + } + +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceProxy.java b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceProxy.java new file mode 100644 index 0000000..d76f63e --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/DeviceProxy.java @@ -0,0 +1,45 @@ +package com.ktind.cgm.bgscout; + +import android.os.AsyncTask; +import android.os.Handler; + +/** + * Created by klee24 on 8/3/14. + */ +public class DeviceProxy extends AsyncTask { + AbstractCGMDevice cgmDevice; + Handler mHandler; + + DeviceProxy(AbstractCGMDevice cgm,Handler mH){ + this.cgmDevice=cgm; + this.mHandler=mH; + } + + @Override + protected Void doInBackground(AbstractCGMDevice... devices) { + cgmDevice.download(); +// cgmDevice.doDownload(); + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + super.onPostExecute(aVoid); + cgmDevice.fireMonitors(); + } +// public Runnable pollCallback = new Runnable() { +// @Override +// public void run() { +// long nextFire=45000; +// for (AbstractCGMDevice cgm:cgms){ +// long tmpNextFire=cgm.nextFire(); +// if (nextFire egvHistory=new ArrayList(FakeCGMDeviceConstants.MAXEGV); + private boolean initialRun; + private Date lastReading=new Date(new Date().getTime()-10800000L); + + public FakeCGMDevice(String n, int deviceID, Context appContext,Handler mH) { + // public AbstractCGMDevice(String n,int deviceID,Context appContext){ + super(n,deviceID,appContext,mH); + generateEGVHistory(); + initialRun=true; + virtual =false; + } + + @Override + int getCGMBattery() { + return 0; + } + + @Override + protected DeviceDownloadObject doDownload() { + return generateDownloadObject(); + } + + @Override + public void connect() { + } + + @Override + public void disconnect() { + } + +// @Override +// public G4EGVRecord[] getReadings() { +// return new G4EGVRecord[0]; +// } + + private DeviceDownloadObject generateDownloadObject(){ + Log.d(TAG,"Generating download object"); + if (!initialRun) + this.addEGV(); + DownloadStatus downloadStatus=generateStatus(); + EGVRecord[] egvArray=egvHistory.toArray(new EGVRecord[egvHistory.size()]); + DeviceDownloadObject ddo=new DeviceDownloadObject(this,egvArray,downloadStatus); + lastDownloadObject=ddo; + initialRun=false; +// for (EGVRecord r:egvHistory){ +// r.setNew(false); +// } + return ddo; + } + + @Override + public void fireMonitors() { + super.fireMonitors(); + lastReading=egvHistory.get(egvHistory.size()-1).getDate(); +// for (EGVRecord r:egvHistory){ +// r.setNew(false); +// } + } + + private void addEGV(){ + egvHistory.remove(0); + for (EGVRecord r:egvHistory){ + r.setNew(false); + } + int lastIndex=egvHistory.size()-1; + int lastBG=egvHistory.get(lastIndex).getEgv(); + Trend lastTrend=egvHistory.get(lastIndex).getTrend(); + Date lastDate=egvHistory.get(lastIndex).getDate(); + Log.d(TAG,"Last reading. BG: "+lastBG+" Trend: "+lastTrend.toString()+" Date: "+lastDate); + EGVRecord record=generateNextEGV(lastBG, lastTrend, lastDate); + if (record.getDate().after(lastReading)) + record.setNew(true); + egvHistory.add(record); + } + + // This patient doesn't seem to mind extreme highs or lows so you won't + // see the normal patterns for corrections + private void generateEGVHistory(){ + Log.d(TAG,"Generating new EGV History"); + Random rand=new Random(); + int initialBG=rand.nextInt(FakeCGMDeviceConstants.MAXEGV+1)+ FakeCGMDeviceConstants.MINEGV; + Trend initialTrend=generateInitialTrend(); + long initialMillis=1000*(FakeCGMDeviceConstants.READINGINTERVALSECONDS* FakeCGMDeviceConstants.MAXRECORDS); + Date initialDate=new Date(new Date().getTime()-initialMillis); + egvHistory.add(generateEGV(initialBG, initialTrend, initialDate, true)); +// int missedCounter=0; + for (int i=1;i< FakeCGMDeviceConstants.MAXRECORDS;i++){ + Log.d(TAG,"Counter: "+i+" egvHistory.size(): "+egvHistory.size()); + if (rand.nextFloat()> FakeCGMDeviceConstants.MISSEDREADINGRATE) { + int lastIndex=egvHistory.size()-1; + int lastBG = egvHistory.get(lastIndex).getEgv(); + Trend lastTrend = egvHistory.get(lastIndex).getTrend(); + Date date = new Date(egvHistory.get(lastIndex).getDate().getTime() - (1000 * FakeCGMDeviceConstants.READINGINTERVALSECONDS * (FakeCGMDeviceConstants.MAXRECORDS - i))); + egvHistory.add(generateNextEGV(lastBG, lastTrend, date)); +// missedCounter+=1; + } + } + } + + private DownloadStatus generateStatus(){ + DownloadStatus status=generateStatus(FakeCGMDeviceConstants.FAILRATE); + return status; + } + + private DownloadStatus generateStatus(float failRate){ + Random rand=new Random(); + DownloadStatus status=DownloadStatus.SUCCESS; + float check=rand.nextFloat(); + Log.d(TAG,"Checking for failure: "+check+" Rate is: "+failRate); + if (check0.50f){ + negTrend=true; + } + break; + } + if (changeRate==0) + changeRate=1; + int bgChange=rand.nextInt(changeRate*(FakeCGMDeviceConstants.READINGINTERVALSECONDS/60)); + if (negTrend) + bgChange=bgChange*-1; + int newEGV=lastBG+bgChange; + if (newEGV< FakeCGMDeviceConstants.MINEGV) + newEGV= FakeCGMDeviceConstants.MINEGV; + if (newEGV> FakeCGMDeviceConstants.MAXEGV) + newEGV= FakeCGMDeviceConstants.MAXEGV; + record.setEgv(newEGV); + Log.d(TAG,"Generated EGV. BG: "+record.getEgv()+" Trend: "+record.getTrend().toString()+" Date: "+record.getDate().toString()); + return record; + } +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/FakeCGMDeviceConstants.java b/mobile/src/main/java/com/ktind/cgm/bgscout/FakeCGMDeviceConstants.java new file mode 100644 index 0000000..0868be5 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/FakeCGMDeviceConstants.java @@ -0,0 +1,13 @@ +package com.ktind.cgm.bgscout; + +/** + * Created by klee24 on 8/2/14. + */ +public class FakeCGMDeviceConstants { + public static final int MAXRECORDS=36; + public static final float FAILRATE=0.08f; + public static final int READINGINTERVALSECONDS=300; + public static final int MAXEGV=401; + public static final int MINEGV=39; + public static final float MISSEDREADINGRATE=0.10f; +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4BatteryState.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4BatteryState.java new file mode 100644 index 0000000..3778ade --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4BatteryState.java @@ -0,0 +1,28 @@ +package com.ktind.cgm.bgscout; + +/** + * Created by klee24 on 8/2/14. + */ +public enum G4BatteryState { + Charging((byte) 1,"Charging"), + NotCharging((byte) 2, "Not charging"), + NTCFault((byte) 3,"NTCFault"), + BadBattery((byte) 4, "Bad battery"); + + private String stringValue=null; + private byte byteVal; + + private G4BatteryState(byte b, String s){ + stringValue=s; + byteVal=b; + } + + public byte getValue(){ + return byteVal; + } + + @Override + public String toString(){ + return stringValue; + } +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4CGMDevice.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4CGMDevice.java new file mode 100644 index 0000000..6812640 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4CGMDevice.java @@ -0,0 +1,700 @@ +package com.ktind.cgm.bgscout; + +import android.content.Context; +import android.os.Handler; +import android.util.Log; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + + +/** + * Created by klee24 on 8/2/14. + */ +public class G4CGMDevice extends AbstractCGMDevice { + protected String serialNum; + protected String receiverID; + protected int cgmBattery=-1; + private static final String TAG = G4CGMDevice.class.getSimpleName(); + // TODO add this as a shared preference... + protected Date myLastDownload=new Date(new Date().getTime()-10800000); + + // public AbstractCGMDevice(String n,int deviceID,Context appContext){ + + public String getSerialNum() { + return serialNum; + } + + public void setSerialNum(String serialNum) { + this.serialNum = serialNum; + } + + public G4CGMDevice(String name,int devID,Context appContext,Handler mH){ + super(name,devID,appContext,mH); +// this.appContext=context; + virtual = false; + cgmTransport=new G4USBSerialTransport(appContext); + } + + @Override + int getCGMBattery() { + if (cgmBattery==-1) + cgmBattery=getCGMBattery(); + return cgmBattery; + } + + @Override + public void connect() throws DeviceNotConnected { + cgmTransport.open(); + } + + public void setup() throws IOException { + if (isConnected()) { + unit = getGlucoseUnit(); + serialNum = getRcvrSerial(); + cgmBattery=getBatteryLevel(); +// myBattery=getUploaderBattery(); + + }else{ + Log.e("G4Device","Unable to setup device that I am not connected to"); + } + } + + @Override + protected DeviceDownloadObject doDownload() { + DeviceDownloadObject ddo=new DeviceDownloadObject(); + ddo.setDevice(this); + if (lastDownloadObject==null) + lastDownloadObject=ddo; + try { + cgmTransport.open(); + this.setup(); + EGVRecord[] egvArray = this.getLastEGVRecords(); + getReadingsSince(myLastDownload, egvArray); + int batteryLevel = this.getBatteryLevel(); + float myBattery= getUploaderBattery(); + Log.d(TAG, "Device battery level: " + batteryLevel); + Log.d(TAG, "Phone battery level: " + myBattery); + if (batteryLevel < 40) { + if (myBattery > 0.40) { + Log.d(TAG, "Setting phone to charge device"); + this.setChargeDevice(true); + } else { + Log.d(TAG, "G4 battery low but this device is too low to charge it"); + this.setChargeDevice(false); + } + } else { + Log.d(TAG, "Stopping this device from charging G4"); + this.setChargeDevice(false); + } + try { + Date dispDate=getDisplayTime(); + Long jitter=dispDate.getTime()-new Date().getTime(); + if (Math.abs(jitter) > 30000 ) { + Log.w(TAG,"Device time off by "+jitter+" ms"); + this.syncTimeToDevice(); + } + }catch (IOException e){ + Log.e(TAG,"Unable to syncTime to device"); + } + cgmTransport.close(); + // FIXME reflect actual status + DownloadStatus downloadStatus = DownloadStatus.SUCCESS; + ddo = new DeviceDownloadObject(this, egvArray, downloadStatus); + lastDownloadObject = ddo; + myLastDownload=lastDownloadObject.getEgvRecords()[lastDownloadObject.getEgvRecords().length-1].getDate(); + } catch (DeviceNotConnected e){ + Log.d(TAG, "Not finding device here!"); + EGVRecord[] records=lastDownloadObject.getEgvRecords(); +// records=lastDownloadObject.getEgvRecords(); + ddo.setEgvRecords(records); + lastDownloadObject=ddo; + ddo.setStatus(DownloadStatus.DEVICENOTFOUND); + } catch (IOException e){ + EGVRecord[] records=lastDownloadObject.getEgvRecords(); + ddo.setEgvRecords(records); + lastDownloadObject=ddo; + ddo.setStatus(DownloadStatus.IOERROR); + } + + for (G4EGVSpecialValue specialValue:G4EGVSpecialValue.values()) { + EGVRecord[] records=lastDownloadObject.getEgvRecords(); + if (records!=null && records.length>0) { + EGVRecord rec = records[records.length - 1]; + if (rec.getEgv() == specialValue.getValue()) { + ddo.setStatus(DownloadStatus.SPECIALVALUE); + ddo.setSpecialValueMessage(G4EGVSpecialValue.getEGVSpecialValue(rec.getEgv()).toString()); + break; + } + } + } + + return ddo; +// return super.doDownload(); + } + + @Override + public void disconnect() { + cgmTransport.close(); + } + + public void setChargeDevice(boolean c){ + cgmTransport.setChargeReceiver(c); + } + +// @Override + public G4EGVRecord[] getReadings() { + return this.getReadings(G4Constants.defaultReadings); + } + + public G4EGVRecord[] getReadings(int numReadings) { + return new G4EGVRecord[0]; + } + + // Retrieves the last 4 pages + public G4EGVRecord[] getLastEGVRecords() throws IOException { + G4Partition partition=getDBPageRange(G4RecType.EGVDATA); + G4EGVRecord[] results=getEGVPages(partition.lastPage - 3, 4); + return results; + } + + // TODO Possibly abstract this out and have it return a collection of parsed records? + public G4EGVRecord[] getEGVPages(int firstPage,int lastPage) throws IOException { + + G4DBPage[] pages=getDBPages(G4RecType.EGVDATA,firstPage,lastPage); + int totalNumRecords=0; + for (G4DBPage page: pages){ + totalNumRecords+=page.PageHeader.NumberOfRecords; + } + + Log.d(TAG,"Record type: "+pages[0].PageHeader.RecordType.toString()+"PageCount: "+pages.length+"Total records: "+totalNumRecords); + G4EGVRecord[] egvrecords = new G4EGVRecord[totalNumRecords]; + int i=0; + for (G4DBPage page: pages) { + G4EGVRecord[] tmprecs = new G4EGVRecord[page.PageHeader.NumberOfRecords]; + tmprecs=parsePage(page); + System.arraycopy(tmprecs,0,egvrecords,i,page.PageHeader.NumberOfRecords); + Log.d(TAG,"Start index: "+i+" Record count: "+page.PageHeader.NumberOfRecords+" End: "+(page.PageHeader.NumberOfRecords*i+page.PageHeader.NumberOfRecords)); + i+=page.PageHeader.NumberOfRecords; + } + return egvrecords; + } + + public G4EGVRecord lastReading() throws IOException { + G4EGVRecord[] results=getLastEGVRecords(); + if (results==null | results.length<1) + return null; + return results[results.length-1]; + } + + public EGVRecord[] getReadingsSince(Date d) throws IOException { + // TODO continue to go back in time until we find the earliest record. + G4EGVRecord[] recs=getLastEGVRecords(); + return getReadingsSince(d,recs); + } + + //FIXME: clean this up. There shouldn't be a separate method to set this value should there? Violates SRP + public EGVRecord[] getReadingsSince(Date d,EGVRecord[] recs){ + ArrayList resultsArrayList=new ArrayList(); + Log.d(TAG,"getReadingsSince date=> "+d); + for (EGVRecord record:recs){ + if (record.getDate().after(d)) { + record.setNew(true); + } else { + record.setNew(false); + } + resultsArrayList.add(record); + } + return resultsArrayList.toArray(new EGVRecord[resultsArrayList.size()]); + } + + public boolean ping() throws IOException { + if (!isConnected()){ + Log.e(TAG,"Ping failed - not connected to device"); + return false; + } + writeCmd(G4RcvrCmd.PING); + byte[] result=readResponse(); + if (result==null || result.length!=6){ + Log.e(TAG,"Ping unsuccessful"); + return false; + } + Log.i(TAG,"Ping successful"); + return true; + } + + public G4Partition getDBPageRange(G4RecType recordType) throws IOException { + G4Partition response=new G4Partition(); + writeCmd(G4RcvrCmd.READDATABASEPAGERANGE,recordType.getValue()); + byte[] responseBuff = readResponse(); + if (responseBuff==null || responseBuff.length!=8){ + throw new IOException("Problem reading response"); +// return null; + } + byte[] firstPage = {responseBuff[0],responseBuff[1],responseBuff[2],responseBuff[3]}; + byte[] lastPage = {responseBuff[4],responseBuff[5],responseBuff[6],responseBuff[7]}; + response.Partition=recordType; + response.firstPage= BitTools.byteArraytoInt(firstPage); + response.lastPage= BitTools.byteArraytoInt(lastPage); + Log.d(TAG,"Partition: "+response.Partition.toString()+"First Page: "+response.firstPage+"Last Page: "+response.lastPage); + return response; + } + + // There has to be a better way to do this? + private void writeCmd(G4RcvrCmd cmd, byte value) throws IOException { + byte[] b=new byte[1]; + b[0]=value; + writeCmd(cmd,b); + } + + public GlucoseUnit getGlucoseUnit() throws IOException { + writeCmd(G4RcvrCmd.READGLUCOSEUNIT); + byte[] res=readResponse(); + int result=0; + if (res.length>0) + result=(int) res[0]; + return GlucoseUnit.values()[result]; + } + + public String getTransmitterId() throws IOException { + writeCmd(G4RcvrCmd.READTRANSMITTERID); + String result=null; + try { + result = new String(readResponse(), "UTF-8"); + }catch(UnsupportedEncodingException e){ + Log.e(TAG,"Exception converting byte array to String",e); + } + return result; + } + + public String getBatteryState() throws IOException { + writeCmd(G4RcvrCmd.READBATTERYSTATE); + String result="Unable to determine battery state"; + byte[] response=readResponse(); + + if (response!=null && response.length>=0) + result= G4BatteryState.values()[response[0]-1].toString(); + return result; + } + + public int getBatteryLevel() throws IOException { + writeCmd(G4RcvrCmd.READBATTERYLEVEL); + int result=0; + result = BitTools.byteArraytoInt(readResponse()); + return result; + } + + public Date getDisplayTime() throws IOException { + return new Date(getDisplayTimeLong()); + } + + public void syncTimeToDevice() throws IOException { + Calendar mCalendar = new GregorianCalendar(); + TimeZone mTimeZone = mCalendar.getTimeZone(); + // TODO add protections against the device settings its time pre Jan 1 2009... + long dispTimeOffset=new Date().getTime()/1000-G4Constants.RECEIVERBASEDATE/1000-getSystemTimeLong(); + // TODO Test daylight time change. + // TODO Also test daylight time change and time zones that don't observe daylight time + if (mTimeZone.inDaylightTime(new Date())){ + dispTimeOffset+=3600L; // 1 hour for daylight time if it is observed + } + // TODO switch everything to ByteBuffers - all the things. + byte[] byteArray=ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt((int) dispTimeOffset).array(); +// Log.d(TAG,"Hex sync time(Little): "+ BitTools.bytesToHex(byteArray)); + writeCmd(G4RcvrCmd.WRITEDISPLAYTIMEOFFSET, byteArray); + // TODO add verification that device did receive the command? + byte[] resp=readResponse(); + Log.i(TAG,"Sync'd device time with cell. Set display time offset to: "+dispTimeOffset); + } + + public long getDisplayTimeLong() throws IOException { + Calendar mCalendar = new GregorianCalendar(); + TimeZone mTimeZone = mCalendar.getTimeZone(); + long dispTime=G4Constants.RECEIVERBASEDATE+getDisplayTimeOffsetLong()*1000L+getSystemTimeLong()*1000L; + if (mTimeZone.inDaylightTime(new Date())) + dispTime-=3600000L; + Log.d(TAG,"getDisplayTimeLong: "+dispTime); + return dispTime; + } + + public long getSystemTimeLong() throws IOException { + writeCmd(G4RcvrCmd.READSYSTEMTIME); + long result=0; + result = BitTools.byteArraytoInt(readResponse()); + Log.d(TAG,"getSystemTimeLong=>"+result); + return result; + } + + protected long getDisplayTimeOffsetLong() throws IOException { + writeCmd(G4RcvrCmd.READDISPLAYTIMEOFFSET); + long result=0; + result = BitTools.byteArraytoInt(readResponse()); + Log.d(TAG,"getDisplayTimeOffsetLong=>"+result); + return result; + } + + protected long getSystemTimeOffsetLong() throws IOException { + writeCmd(G4RcvrCmd.READSYSTEMTIMEOFFSET); + long result=0; + result = BitTools.byteArraytoInt(readResponse()); + Log.d(TAG,"getSystemTimeOffsetLong=>"+result); + return result; + } + + //@Override + protected String getDatabasePartitionInfo() throws IOException { + writeCmd(G4RcvrCmd.READDATABASEPARTITIONINFO); + String response = new String(readResponse()); + return response; + } + + //CRC methods + public static int calcCrc16 (byte [] buff) { + int crc = 0; + for (int i = 0; i < buff.length; i++) + { + crc = ((crc >>> 8) | (crc << 8) )& 0xffff; + crc ^= (buff[i] & 0xff); + crc ^= ((crc & 0xff) >> 4); + crc ^= (crc << 12) & 0xffff; + crc ^= ((crc & 0xFF) << 5) & 0xffff; + + } + crc &= 0xffff; + return crc; + } + + public int writeCmd(G4RcvrCmd rcvrCmd, byte [] payload) throws IOException { + Log.d(TAG,"Attempting to write to receiver"); + if(!isConnected()){ + Log.e(TAG,"Write failed - not connected to device"); + return 0; + } + int bytesWritten=0; + if (! cgmTransport.isOpen()) + return bytesWritten; + + // Retrieve how many bytes should be sent by this command + int bytesToWrite=rcvrCmd.getCmdSize(); + Log.d(TAG,"Bytes to write: "+bytesToWrite); + int calcBytesToWrite=6; + if (payload!=null) + calcBytesToWrite = 4 + payload.length + 2; +// Log.d(TAG,"Calculated bytes to write: "+calcBytesToWrite); + if (bytesToWrite != calcBytesToWrite){ + Log.e(TAG,"Insufficient data for command"); + return 0; + } + //TODO throw an exception? + if (bytesToWrite==-1){ + Log.e(TAG,"Command "+rcvrCmd.toString()+" has not been implemented"); + return bytesToWrite; + } + byte[] packet = new byte[bytesToWrite]; + + packet[0]=0x01; // Always 1 + + // next two bytes are the size of the command => SOF+packet size+command+payload+crc + packet[1]=(byte) (bytesToWrite & 0xFF); + packet[2]=(byte) (bytesToWrite >> 8 & 0xFF); + packet[3]=rcvrCmd.getValue(); + + // Copy the payload if it exists + if (payload!=null && bytesToWrite>6 && payload.length>0) + System.arraycopy(payload,0,packet,4,bytesToWrite-6); + byte [] crcPacket=new byte[bytesToWrite-2]; + System.arraycopy(packet,0,crcPacket,0,bytesToWrite-2); + int crc=calcCrc16(crcPacket); + packet[bytesToWrite-2]=(byte)(crc & 255); + packet[bytesToWrite-1]=(byte)(crc >> 8 & 255); + + try { + Log.v(TAG,"Writing("+rcvrCmd.toString()+"): "+ BitTools.bytesToHex(packet)); + bytesWritten=cgmTransport.write(packet,G4Constants.defaultWriteTimeout); + Log.d(TAG,"Bytes written - "+bytesWritten); + } catch (IOException e) { + throw new IOException("Unable to write to Dexcom G4"); +// Log.e(TAG, "Unable to write to Dexcom G4", e); + + } + return bytesWritten; + } + + + public int writeCmd(G4RcvrCmd rcvCmd) throws IOException { + return writeCmd(rcvCmd,null); + } + + public byte[] readResponse(){ + return this.readResponse(G4Constants.defaultReadTimeout); + } + + public byte[] readResponse(int millis) { + Log.d(TAG,"Attempting to read to receiver"); + if(!isConnected()){ + Log.e(TAG,"Read failed - not connected to device"); + return null; + } + + int bytesRead=0; + if (! isConnected()) { + Log.e(TAG,"Device is not connected"); + return null; + } + // Seems we can't read fewer bytes than they send.. + // but we can request more bytes than they'll send. + // Setting max response buffer to 3072 to prevent + // a read failure. + // Max size required should be somewhere around 2122 + // while reading database pages. Let's just round + // that up a bit + byte [] responseBuffer = new byte[3072]; + + try { + bytesRead=cgmTransport.read(responseBuffer,millis); + Log.d(TAG,"Bytes read - "+bytesRead); + } catch (IOException e) { + Log.e(TAG,"Unable to read headers from Dexcom G4"); + return null; + } + if (responseBuffer[0]!=0x01) { + Log.e(TAG, "Unexpected response back while parsing header: "+ BitTools.bytesToHex(responseBuffer)); + return null; + } + int bytesToRead=(int)responseBuffer[2]<<8 ^ (int)responseBuffer[1]; + Log.d(TAG,"Calculated bytes to read at "+bytesRead); + if (bytesToRead!=bytesRead) { + Log.e(TAG, "Calculated bytes to read does not equal the bytes actually read"); + return null; + } + + byte [] header = new byte[4]; + byte [] body = new byte[bytesRead-6]; + byte [] crc=new byte[2]; + + System.arraycopy(responseBuffer,0,header,0,4); + System.arraycopy(responseBuffer,4+(bytesToRead-6),crc,0,2); + System.arraycopy(responseBuffer,4,body,0,bytesRead-6); + +// Log.d(TAG,"Response hex: " + BitTools.bytesToHex(header) + BitTools.bytesToHex(body) + BitTools.bytesToHex(crc)); + Log.v(TAG,"Header hex:"+ BitTools.bytesToHex(header)); + Log.v(TAG,"Body hex:"+ BitTools.bytesToHex(body)); + Log.v(TAG,"CRC hex: "+ BitTools.bytesToHex(crc)); + byte[] crcCheckArray=new byte[bytesToRead-2]; + System.arraycopy(responseBuffer,0,crcCheckArray,0,bytesToRead-2); + int calcCRC=calcCrc16(crcCheckArray); + int crcInt=(crc[0] & 0xFF); + crcInt+=(crc[1] << 8) & 0xFF00; + if (calcCRC!=crcInt) { + Log.d(TAG, "Calculated CRC: " + calcCRC+"Response CRC: " + crcInt); + Log.e(TAG, "CRC check failed!"); + return null; + }else{ + Log.d(TAG,"Successful CRC check"); + } + return body; + } + + private G4DBPageHeader getPageHeader(G4RecType recType,int pageNumber) throws IOException { + Log.d(TAG,"getPageHeader called"); + G4DBPageHeader result=new G4DBPageHeader(); + byte[] requestPayload=new byte[5]; + requestPayload[0]=recType.getValue(); + System.arraycopy(BitTools.intToByteArray(pageNumber),0,requestPayload,1,4); + writeCmd(G4RcvrCmd.READDATABASEPAGEHEADER, requestPayload); + byte[] resultBuffer=readResponse(); + byte[] FirstRecordIndexArray=new byte[4]; + byte[] NumberOfRecordsArray=new byte[4]; +// byte[] RecordTypeArray=new byte[1]; +// byte[] RevisionArray=new byte[1]; + byte[] PageNumberArray=new byte[4]; + byte[] Reserved2Array=new byte[4]; + byte[] Reserved3Array=new byte[4]; + byte[] Reserved4Array=new byte[4]; + byte[] CRCArray=new byte[2]; + + System.arraycopy(resultBuffer,0,FirstRecordIndexArray,0,4); + System.arraycopy(resultBuffer,4,NumberOfRecordsArray,0,4); +// System.arraycopy(resultBuffer,8,RecordTypeArray,0,1); +// System.arraycopy(resultBuffer,9,RevisionArray,0,1); + System.arraycopy(resultBuffer,10,PageNumberArray,0,4); + System.arraycopy(resultBuffer,14,Reserved2Array,0,4); + System.arraycopy(resultBuffer,18,Reserved3Array,0,4); + System.arraycopy(resultBuffer,22,Reserved4Array,0,4); + System.arraycopy(resultBuffer,26,CRCArray,0,2); + result.FirstRecordIndex= BitTools.byteArraytoInt(FirstRecordIndexArray); + result.NumberOfRecords= BitTools.byteArraytoInt(NumberOfRecordsArray); + result.RecordType=G4RecType.values()[(int) resultBuffer[8]]; + result.Revision=resultBuffer[9]; + result.PageNumber= BitTools.byteArraytoInt(PageNumberArray); + result.Reserved2= BitTools.byteArraytoInt(Reserved2Array); + result.Reserved3= BitTools.byteArraytoInt(Reserved3Array); + result.Reserved4= BitTools.byteArraytoInt(Reserved4Array); + int crcInt=(CRCArray[0] & 0xFF); + crcInt+=(CRCArray[1] << 8) & 0xFF00; + result.Crc=crcInt; + Log.v(TAG,"FirstRecordIndex: "+result.FirstRecordIndex+" NumberOfRecords: "+result.NumberOfRecords+" RecordType: "+result.RecordType.toString()+" Revision: "+result.Revision+" PageNumber: "+result.PageNumber+"CRC: "+result.Crc); + return result; + } + + public G4DBPage[] getDBPages(G4RecType recType,int startPage, int numPages) throws IOException { + Log.d(TAG,"Requesting "+numPages+" starting with "+startPage); + byte [] requestPayload=new byte[6]; + requestPayload[0]=recType.getValue(); + requestPayload[1]=(byte) (startPage & 0xFF); + requestPayload[2]=(byte) ((startPage >> 8) & 0xFF); + requestPayload[3]=(byte) ((startPage >> 16) & 0xFF); + requestPayload[4]=(byte) ((startPage >> 24) & 0xFF); + requestPayload[5]=(byte) numPages; + writeCmd(G4RcvrCmd.READDATABASEPAGES, requestPayload); + byte[] response=readResponse(); + Log.d(TAG,"Response Length: "+response.length); + G4DBPage[] pages=new G4DBPage[numPages]; + for (int i=0;ii*13){ + results[i]=new G4EGVRecord(); + byte[] recordBuffer=new byte[13]; + System.arraycopy(page.PageData, i * 13, recordBuffer, 0, 13); + byte[] sysTimeArray=new byte[4]; + byte[] dispTimeArray=new byte[4]; + byte[] egvwflagArray=new byte[2]; + byte[] dirnoiseArray=new byte[1]; + byte[] crcArray=new byte[2]; + System.arraycopy(recordBuffer, 0, sysTimeArray, 0, 4); + System.arraycopy(recordBuffer, 4, dispTimeArray, 0, 4); + System.arraycopy(recordBuffer, 8, egvwflagArray, 0, 2); + System.arraycopy(recordBuffer, 10, dirnoiseArray, 0, 1); + System.arraycopy(recordBuffer, 11, crcArray, 0, 2); + results[i].setSystemTime(BitTools.byteArraytoInt(sysTimeArray)); + long dtime=(long) BitTools.byteArraytoInt(dispTimeArray)*1000; + Calendar mCalendar = new GregorianCalendar(); + TimeZone mTimeZone = mCalendar.getTimeZone(); +// long mGMTOffset = mTimeZone.getRawOffset(); +// long displayTimeLong=rcvrBaseDatems+dtime+mGMTOffset; + long displayTimeLong=G4Constants.RECEIVERBASEDATE+dtime; + if (mTimeZone.inDaylightTime(new Date())){ + displayTimeLong-=3600000L; + } + Date displayTimeDate=new Date(displayTimeLong); + + results[i].setDate(displayTimeDate); + int bgValue= BitTools.byteArraytoInt(egvwflagArray) & 0x3FF; + // This means we've found the end + if (bgValue==1023) { + Log.d(TAG,"Last reading found in this page"); + break; + } + results[i].setEgv(bgValue); + results[i].setSpecialValue(null); +// results[i].s(deviceUnits); + for (G4EGVSpecialValue e: G4EGVSpecialValue.values()) { + if (e.getValue()==bgValue) { + results[i].setSpecialValue(G4EGVSpecialValue.getEGVSpecialValue(bgValue)); + Log.w(TAG,"Special value set: "+results[i].getSpecialValue().toString()); + break; + } + } + int trendNoise= BitTools.byteArraytoInt(dirnoiseArray); + results[i].setTrend(Trend.values()[trendNoise & 0xF]); + results[i].setNoiseMode(G4NoiseMode.getNoiseMode((byte)(trendNoise & 0xF)>>4)); + Log.v(TAG,"Reading time("+i+"): "+results[i].getDate().toString()+" EGV: "+results[i].getEgv()+" Trend: "+results[i].getTrend().toString()+" Noise: "+results[i].getNoiseMode().toString()); + } else { + Log.w(TAG,"Record ("+i+") appears to be truncated in page number "+page.PageHeader.PageNumber); + } + } + Log.d(TAG,"Number of Records: "+results.length); + return results; + } + + public String getRcvrSerial() throws IOException { + serialNum = getParam(G4RecType.MANUFACTURINGDATA, "SerialNumber"); + return serialNum; + } + + private String getParam(G4RecType recType,String param) throws IOException { + G4Partition part=getDBPageRange(recType); + String result=""; +// Charset charset= + G4DBPage[] pages=getDBPages(recType,part.firstPage,1); + String data=new String(); + for (G4DBPage page:pages){ + int i; + // Ugly code to capture the null terminated string + for (i = 0; i < page.PageData.length && page.PageData[i] != 0x00; i++) { } + // Strip the header and reduce the size of the string by the header length + data+=new String(page.PageData,8,i-8); + } + try { + InputStream is = new ByteArrayInputStream(data.getBytes("UTF-8")); + DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance(); + DocumentBuilder builder=factory.newDocumentBuilder(); + Document dom=builder.parse(is); + String elemName; + if (recType==G4RecType.PCSOFTWAREPARAMETER) { + elemName = "PCParameterRecord"; + }else if (recType==G4RecType.MANUFACTURINGDATA){ + elemName="ManufacturingParameters"; + } else { + return ""; + } + // TODO Need to add checking for null pointers... + Element elem = (Element) dom.getElementsByTagName(elemName).item(0); + result=elem.getAttribute(param); + }catch (Exception e) { + // TODO specific error handling + Log.e(TAG,"Problem parsing XML from "+recType.toString()+" partition in getParam",e); + } + Log.d(TAG,recType.toString()+" data:"+data+"Result: "+result); + return result; + } + + public String getRcvrID() throws IOException { + // Only get this value once per instance of a G4Device + if (receiverID=="") { + receiverID = getParam(G4RecType.PCSOFTWAREPARAMETER, "ReceiverId"); + } else { + Log.d(TAG, "Returning cached results for ReceiverId"); + } + String result=receiverID; + return result; + } + +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4Constants.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4Constants.java new file mode 100644 index 0000000..c1fd607 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4Constants.java @@ -0,0 +1,14 @@ +package com.ktind.cgm.bgscout; + +import com.ktind.cgm.bgscout.Constants; + +/** + * Created by klee24 on 8/2/14. + */ +public class G4Constants { + final static int READING_INTERVAL= 60*5; // 5 minutes in seconds (60 seconds * 5 minutes) + final static int defaultReadings=10; + final static int defaultReadTimeout=200; + final static int defaultWriteTimeout=200; + final static long RECEIVERBASEDATE=1230789600000L; +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4DBPage.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4DBPage.java new file mode 100644 index 0000000..7c7ab0e --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4DBPage.java @@ -0,0 +1,9 @@ +package com.ktind.cgm.bgscout; + +/** + * Created by klee24 on 8/2/14. + */ +public class G4DBPage { + public G4DBPageHeader PageHeader=new G4DBPageHeader(); + public byte[] PageData=new byte[500]; +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4DBPageHeader.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4DBPageHeader.java new file mode 100644 index 0000000..dd3443d --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4DBPageHeader.java @@ -0,0 +1,16 @@ +package com.ktind.cgm.bgscout; + +/** + * Created by klee24 on 8/2/14. + */ +public class G4DBPageHeader { + public int FirstRecordIndex; + public int NumberOfRecords; + public G4RecType RecordType; + public byte Revision; + public int PageNumber; + public int Reserved2; + public int Reserved3; + public int Reserved4; + public int Crc; +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4EGVRecord.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4EGVRecord.java new file mode 100644 index 0000000..3e23f9c --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4EGVRecord.java @@ -0,0 +1,38 @@ +package com.ktind.cgm.bgscout; + +import com.ktind.cgm.bgscout.EGVRecord; +import com.ktind.cgm.bgscout.G4EGVSpecialValue; +import com.ktind.cgm.bgscout.G4NoiseMode; + +/** + * Created by klee24 on 7/13/14. + */ +public class G4EGVRecord extends EGVRecord {; + private long systemTime=0L; + private G4EGVSpecialValue specialValue; + private G4NoiseMode noiseMode; + + public void setSystemTime(long systemTime) { + this.systemTime = systemTime; + } + + public long getSystemTime() { + return systemTime; + } + + public void setSpecialValue(G4EGVSpecialValue specialValue) { + this.specialValue = specialValue; + } + + public G4EGVSpecialValue getSpecialValue() { + return specialValue; + } + + public void setNoiseMode(G4NoiseMode noiseMode) { + this.noiseMode = noiseMode; + } + + public G4NoiseMode getNoiseMode() { + return noiseMode; + } +} \ No newline at end of file diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4EGVSpecialValue.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4EGVSpecialValue.java new file mode 100644 index 0000000..51f8233 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4EGVSpecialValue.java @@ -0,0 +1,41 @@ +package com.ktind.cgm.bgscout; + +/** + * Created by klee24 on 7/20/14. + */ +public enum G4EGVSpecialValue { + + NONE("None",0), + SENSORNOTACTIVE("Sensor not active",1), + MINIMALLYEGVAB("Minimally EGV Aberration",2), + NOANTENNA("No Antenna",3), + SENSOROUTOFCAL("Sensor needs Calibration",5), + COUNTSAB("Counts Aberration",6), + ABSOLUTEAB("Absolute Aberration",9), + POWERAB("Power Aberration",10), + RFBADSTATUS("RF bad status",12); + + + private String name; + private int val; + private G4EGVSpecialValue(String s, int i){ + name=s; + val=i; + } + + public int getValue(){ + return val; + } + + public String toString(){ + return name; + } + + public static G4EGVSpecialValue getEGVSpecialValue(int val){ + for (G4EGVSpecialValue e: values()){ + if (e.getValue()==val) + return e; + } + return null; + } +} \ No newline at end of file diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4NoiseMode.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4NoiseMode.java new file mode 100644 index 0000000..6ac734e --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4NoiseMode.java @@ -0,0 +1,32 @@ +package com.ktind.cgm.bgscout; + +/** + * Created by klee24 on 7/20/14. + */ +public enum G4NoiseMode { + None(0), + Clean(1), + Light(2), + Medium(3), + Heavy(4), + NotComputed(5), + Max(6); + + private int index; + private G4NoiseMode(int i){ + index=i; + } + + public int getValue(){ + return index; + } + + public static G4NoiseMode getNoiseMode(int val){ + for (G4NoiseMode e: values()){ + if (e.getValue()==val) + return e; + } + return null; + } + +} \ No newline at end of file diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4Partition.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4Partition.java new file mode 100644 index 0000000..c2d0aa0 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4Partition.java @@ -0,0 +1,10 @@ +package com.ktind.cgm.bgscout; + +/** + * Created by klee24 on 7/20/14. + */ +public class G4Partition { + public G4RecType Partition; + public int firstPage; + public int lastPage; +} \ No newline at end of file diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4RcvrCmd.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4RcvrCmd.java new file mode 100644 index 0000000..51a2315 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4RcvrCmd.java @@ -0,0 +1,88 @@ +package com.ktind.cgm.bgscout; + +/** + * Created by klee24 on 8/2/14. + */ +public enum G4RcvrCmd { + NUL("Nul",(byte)0x00), + ACK("Ack",(byte)0x01), + NAK("Nak",(byte)0x02), + INVALIDCOMMAND("InvalidCommand",(byte)0x03), + INVALIDPARAM("InvalidParam",(byte)0x04), + INCOMPLETEPACKETRECEIVED("IncompletePacketReceived",(byte)0x05), + RECEIVERERROR("ReceiverError",(byte)0x06), + INVALIDMODE("InvalidMode",(byte)0x07), + PING("Ping",(byte)0x0A,6), + READFIRMWAREHEADER("ReadFirmwareHeader",(byte)0x0B,6), + READDATABASEPARTITIONINFO("ReadDatabasePartitionInfo",(byte)0x0F,6), + READDATABASEPAGERANGE("ReadDatabasePageRange",(byte)0x10,7), + READDATABASEPAGES("ReadDatabasePages",(byte)0x11,12), + READDATABASEPAGEHEADER("ReadDatabasePageHeader",(byte)0x12,11), + READTRANSMITTERID("ReadTransmitterID",(byte)0x19,6), + WRITETRANSMITTERID("WriteTransmitterID",(byte)0x20), + READLANGUAGE("ReadLanguage",(byte)0x1B,6), + WRITELANGUAGE("WriteLanguage",(byte)0x1C,8), + READDISPLAYTIMEOFFSET("ReadDisplayTimeOffset",(byte)0x1D,6), + WRITEDISPLAYTIMEOFFSET("WriteDisplayTimeOffset",(byte)0x1E,10), + READRTC("ReadRTC",(byte)0x1F,6), + RESETRECEIVER("ResetReceiver",(byte)0x20,6), + READBATTERYLEVEL("ReadBatteryLevel",(byte)0x21,6), + READSYSTEMTIME("ReadSystemTime",(byte)0x22,6), + READSYSTEMTIMEOFFSET("ReadSystemTimeOffset",(byte)0x23), + WRITESYSTEMTIME("WriteSystemTime",(byte)0x24,6), + READGLUCOSEUNIT("ReadGlucoseUnit",(byte)0x25,6), + WRITEGLUCOSEUNIT("WriteGlucoseUnit",(byte)0x26,7), + READBLINDEDMODE("ReadBlindedMode",(byte)0x27,6), + WRITEBLINDEDMODE("WriteBlindedMode",(byte)0x28,7), + READCLOCKMODE("ReadClockMode",(byte)0x29,6), + WRITECLOCKMODE("WriteClockMode",(byte)0x2A,7), + READDEVICEMODE("ReadDeviceMode",(byte)0x2B,6), + ERASEDATABASE("EraseDatabase",(byte)0x2D), + SHUTDOWNRECEIVER("ShutdownReceiver",(byte)0x2E,6), + WRITEPCPARAMETERS("WritePCParameters",(byte)0x2F), + READBATTERYSTATE("ReadBatteryState",(byte)0x30,6), + READHARDWAREBOARDID("ReadHardwareBoardId",(byte)0x31,6), + ENTERFIRMWAREUPGRADEMODE("EnterFirmwareUpgradeMode",(byte)0x32), + READFLASHPAGE("ReadFlashPage",(byte)0x33,10), + WRITEFLASHPAGE("WriteFlashPage",(byte)0x34), + ENTERSAMBAACCESSMODE("EnterSambaAccessMode",(byte)0x35), + READFIRMWARESETTINGS("ReadFirmwareSettings",(byte)0x36), + READENABLESETUPWIZARDFLAG("ReadEnableSetupWizardFlag",(byte)0x37), + WRITEENABLESETUPWIZARDFLAG("WriteEnableSetUpWizardFlag",(byte)0x38), + READSETUPWIZARDSTATE("ReadSetUpWizardState",(byte)0x39), + WRITESETUPWIZARDSTATE("WriteSetupWizardState",(byte)0x3A), + MAXCOMMAND("MaxCommand",(byte)0x3B), + MAXPOSSIBLECOMMAND("MaxPossibleCommand",(byte)0xFF); + + + private String stringValue; + private byte byteVal; + // This is the expected size of the command to be written. + // Todo: determine how to variable sized commands. Perhaps if the value is 0? + private int cmdSize; + + public byte getValue(){ + return byteVal; + } + + private G4RcvrCmd(String toString,byte value, int size){ + stringValue=toString; + byteVal=value; + cmdSize=size; + } + + private G4RcvrCmd(String toString, byte value){ + stringValue=toString; + byteVal=value; + cmdSize=-1; + } + + public int getCmdSize() { + return cmdSize; + } + + @Override + public String toString(){ + return stringValue; + } +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4RecType.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4RecType.java new file mode 100644 index 0000000..678f0c7 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4RecType.java @@ -0,0 +1,37 @@ +package com.ktind.cgm.bgscout; + +/** + * Created by klee24 on 7/13/14. + */ +public enum G4RecType { + MANUFACTURINGDATA("ManufacturingData",(byte)0x00), + FIRMWAREPARAMETERDATA("FirmwareParameterData",(byte)0x01), + PCSOFTWAREPARAMETER("PCSoftwareParameter",(byte)0x02), + SENSORDATA("SensorData",(byte)0x03), + EGVDATA("EGVData",(byte)0x04), + CALSET("CalSet",(byte)0x05), + ABERRATION("Aberration",(byte)0x06), + INSERTIONTIME("InsertionTime",(byte)0x07), + RECEIVERLOGDATA("ReceiverLogData",(byte)0x08), + RECEIVERERRORDATA("ReceiverErrorData",(byte)0x09), + METERDATA("MeterData",(byte)0x0a), + USEREVENTDATA("UserEventData",(byte)0x0b), + USERSETTINGDATA("UserSettingData",(byte)0x0c), + MAXVALUE("MaxValue",(byte)0x0d); + + private String stringValue; + private byte byteVal; + private G4RecType(String toString,byte value){ + stringValue=toString; + byteVal=value; + } + + public byte getValue(){ + return byteVal; + } + + @Override + public String toString(){ + return stringValue; + } +} \ No newline at end of file diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/G4USBSerialTransport.java b/mobile/src/main/java/com/ktind/cgm/bgscout/G4USBSerialTransport.java new file mode 100644 index 0000000..0267d1e --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/G4USBSerialTransport.java @@ -0,0 +1,101 @@ +package com.ktind.cgm.bgscout; + +import android.content.Context; +import android.hardware.usb.UsbManager; +import android.util.Log; + +//import com.ktind.cgm.bgscout.CGMTransportAbstract; + +import com.ktind.cgm.bgscout.USB.USBPower; +import com.ktind.cgm.bgscout.USB.UsbSerialDriver; +import com.ktind.cgm.bgscout.USB.UsbSerialProber; + +import java.io.IOException; + +/** + * Created by klee24 on 7/21/14. + */ +public class G4USBSerialTransport extends CGMTransportAbstract { + private static final String TAG = G4USBSerialTransport.class.getSimpleName(); + // private UsbManager mUsbManager; + private UsbSerialDriver mSerialDevice; + Context appContext; + int totalBytesRead=0; + int totalBytesWritten=0; +// boolean chargeReceiver=false; + + + public G4USBSerialTransport(Context c){ + appContext=c; + } + + @Override + public boolean open() throws DeviceNotConnected { + USBPower.PowerOn(); + if (! isOpen()){ + Log.d(TAG, "Attempting to connect"); + UsbManager mUsbManager; + mUsbManager=(UsbManager) appContext.getSystemService(Context.USB_SERVICE); + mSerialDevice = UsbSerialProber.acquire(mUsbManager); + if (mSerialDevice != null) { + try { + mSerialDevice.open(); + Log.d(TAG, "Successfully connected"); + isopen = true; + } catch (IOException e) { +// Log.e(TAG, "Unable to establish a serial connection to Dexcom G4"); + isopen = false; + throw new DeviceNotConnected("Unable to establish a serial connection to Dexcom G4"); + } + }else{ +// Log.e(TAG,"Unable to acquire USB Manager"); + throw new DeviceNotConnected("Unable to acquire USB manager"); + } + }else{ + Log.d(TAG, "Already connected"); + } + + return false; + } + +// public void setChargeReceiver(boolean charge){ +// chargeReceiver=charge; +// } + + @Override + public void close() { + if (isOpen() && mSerialDevice!=null) { + if (!chargeDevice) { + Log.d(TAG,"chargeReceiver:"+chargeDevice); + Log.d(TAG,"Disabling USB power"); + USBPower.PowerOff(); + } + Log.d(TAG, "Attempting to disconnect"); + try { + mSerialDevice.close(); + Log.d(TAG, "Successfully disconnected"); + isopen = false; + } catch (IOException e) { + Log.e(TAG, "Unable to close serial connection to Dexcom G4"); + } + } else { + Log.d(TAG,"Already disconnected"); + } + } + + @Override + public int read(byte[] responseBuffer,int timeoutMillis) throws IOException { + int bytesRead; + bytesRead=mSerialDevice.read(responseBuffer,timeoutMillis); + totalBytesRead+=bytesRead; + return bytesRead; + } + + @Override + public int write(byte [] packet, int writeTimeout) throws IOException { + int bytesWritten; + bytesWritten=mSerialDevice.write(packet, writeTimeout); + totalBytesWritten+=bytesWritten; + return bytesWritten; + } +} \ No newline at end of file diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/GlucoseUnit.java b/mobile/src/main/java/com/ktind/cgm/bgscout/GlucoseUnit.java new file mode 100644 index 0000000..1e084ee --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/GlucoseUnit.java @@ -0,0 +1,27 @@ +package com.ktind.cgm.bgscout; + +/** + * Created by klee24 on 8/2/14. + */ +public enum GlucoseUnit { + NONE("None",(byte) 0), + MGDL("mg/dL",(byte) 1), + MMOL("mmol/L",(byte) 2); + + + private String unit; + private byte value; + private GlucoseUnit(String mUnit, byte mVal){ + unit=mUnit; + value=mVal; + } + + public byte getValue() { + return value; + } + + @Override + public String toString() { + return unit; + } +} \ No newline at end of file diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/MainActivity.java b/mobile/src/main/java/com/ktind/cgm/bgscout/MainActivity.java new file mode 100644 index 0000000..f9f8dc9 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/MainActivity.java @@ -0,0 +1,73 @@ +package com.ktind.cgm.bgscout; + +import android.app.Activity; +import android.app.ActivityManager; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; + +import com.ktind.cgm.bgscout.R; + +public class MainActivity extends Activity { + private static final String TAG = DeviceDownloadService.class.getSimpleName(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + final Button button= (Button) findViewById(R.id.button); + if (isServiceRunning()) { + button.setText("Start"); + } else { + button.setText("Stop"); + } + + } + + public void toggleService(View view){ + Intent mIntent=new Intent(MainActivity.this,DeviceDownloadService.class); + final Button button= (Button) findViewById(R.id.button); + if (isServiceRunning()) { + Log.d(TAG, "Stopping service"); + stopService(mIntent); + button.setText("Start"); + } else { + Log.d(TAG, "Starting service"); + startService(mIntent); + button.setText("Stop"); + } + } + + private boolean isServiceRunning() { + ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE); + for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { + if ("com.ktind.cgm.bgscout.DeviceDownloadService".equals(service.service.getClassName())) { + return true; + } + } + return false; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + if (id == R.id.action_settings) { + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/MongoUploadMonitor.java b/mobile/src/main/java/com/ktind/cgm/bgscout/MongoUploadMonitor.java new file mode 100644 index 0000000..6728e17 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/MongoUploadMonitor.java @@ -0,0 +1,75 @@ +package com.ktind.cgm.bgscout; + +import android.util.Log; + +import com.mongodb.*; + +import java.net.UnknownHostException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Created by klee24 on 7/27/14. + */ +public class MongoUploadMonitor extends AbstractMonitor { + private static final String TAG = MongoUploadMonitor.class.getSimpleName(); + + MongoUploadMonitor(String name) { + super(name); + this.setAllowVirtual(false); + this.setMonitorType("mongo uploader"); + } + + @Override + protected void doProcess(DeviceDownloadObject d) { + //FIXME add these as user configurable options + String mongoURI = ""; + String collectionName=""; + DBCollection deviceData; + + MongoClientURI uri = new MongoClientURI(mongoURI); + DB db; + + try{ + MongoClient mongoClient = new MongoClient(uri); + db = mongoClient.getDB(uri.getDatabase()); + deviceData = db.getCollection(collectionName); + EGVRecord[] r=d.getEgvRecords(); + int uploadCount=0; + for (EGVRecord sr:r) { + BasicDBObject data = new BasicDBObject(); + if (sr.isNew()) { + //Fixme: need to be a separate document + data.put("name", d.getDevice().getName()); + data.put("cgmbattery", d.getDevice().getCGMBattery()); + data.put("uploaderBattery", d.getDevice().getUploaderBattery()); + data.put("units", d.getDevice().getUnit().getValue()); + + data.put("trend", sr.getTrend().getVal()); + // NightScout comptability + data.put("device", "dexcom"); + data.put("date", sr.getDate().getTime()); + data.put("dateString", new SimpleDateFormat("MM/dd/yyy hh:mm:ss aa").format(sr.getDate())); + data.put("sgv", sr.getEgv()); + data.put("direction", sr.getTrend().getNsString()); + + deviceData.update(data, data, true, false, WriteConcern.UNACKNOWLEDGED); + uploadCount+=1; + Log.v(TAG, "Added Record - EGV: " + sr.getEgv() + " Trend: " + sr.getTrend().getNsString() + " Date: " + new SimpleDateFormat("MM/dd/yyy hh:mm:ss aa").format(sr.getDate())); + } + } + Log.i(TAG,"Records processed: "+r.length+" Records Uploaded: "+uploadCount); +// BasicDBObject data= new BasicDBObject(); +// data.put("deviceCheckinDate",new Date().getTime()); + if (mongoClient != null) + mongoClient.close(); + } catch (UnknownHostException e) { + Log.e(TAG,"Unable to upload to mongoDB",e); + } + } + + @Override + public void stop() { + Log.i(TAG, "Stopping monitor " + monitorType + " for " + name); + } +} \ No newline at end of file diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/MonitorInterface.java b/mobile/src/main/java/com/ktind/cgm/bgscout/MonitorInterface.java new file mode 100644 index 0000000..067dfd0 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/MonitorInterface.java @@ -0,0 +1,11 @@ +package com.ktind.cgm.bgscout; + +/** + * Created by klee24 on 8/2/14. + */ +public interface MonitorInterface { + public void process(DeviceDownloadObject d); + public void stop(); + public void setHighThreshold(int highThreshold); + public void setLowThreshold(int lowThreshold); +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/MonitorProxy.java b/mobile/src/main/java/com/ktind/cgm/bgscout/MonitorProxy.java new file mode 100644 index 0000000..d590aa4 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/MonitorProxy.java @@ -0,0 +1,44 @@ +package com.ktind.cgm.bgscout; + +import android.os.AsyncTask; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Created by klee24 on 8/3/14. + */ +public class MonitorProxy extends AsyncTask { + + private ArrayList monitors; + + MonitorProxy(){ + + } + + MonitorProxy(MonitorProxy mProxy){ + this.monitors=mProxy.monitors; + } + + public void setMonitors(AbstractMonitor[] mons){ + ArrayList listMons=new ArrayList(Arrays.asList(mons)); + this.monitors=listMons; + } + public void setMonitors(ArrayList mons) { + this.monitors = mons; + } + + public void stopMonitors() { + for (AbstractMonitor mon:monitors){ + mon.stop(); + } + } + + @Override + protected Void doInBackground(DeviceDownloadObject... dl) { + for (AbstractMonitor mon: monitors){ + mon.process(dl[0]); + } + return null; + } +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/RemoteMongoDevice.java b/mobile/src/main/java/com/ktind/cgm/bgscout/RemoteMongoDevice.java new file mode 100644 index 0000000..727a833 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/RemoteMongoDevice.java @@ -0,0 +1,106 @@ +package com.ktind.cgm.bgscout; + +import android.content.Context; +import android.os.Handler; +import android.util.Log; + +import com.mongodb.*; + +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Created by klee24 on 8/3/14. + */ +public class RemoteMongoDevice extends AbstractCGMDevice { + private static final String TAG = RemoteMongoDevice.class.getSimpleName(); + private String mongoURI = ""; + private String collectionName=""; + private DBCollection deviceData; + MongoClientURI uri = new MongoClientURI(mongoURI); + DB db; + MongoClient mongoClient = null; + long lastQueryDate; + EGVRecord[] lastRecord=new EGVRecord[1]; + + public RemoteMongoDevice(String n,int deviceID,Context appContext,Handler mH){ + super(n,deviceID,appContext,mH); + // Quasi race condition - CGM takes a second or 2 to read and upload while the "virtual CGM" takes less time. + // Give it some time to settle. If not it'll try again in 45 seconds. + this.setPollInterval(304000); + // stop infinite loops! + virtual = true; + } + + @Override + public int getCGMBattery() { + return 100; + } + + @Override + public void connect() throws DeviceNotConnected { + } + + @Override + protected DeviceDownloadObject doDownload() { + DeviceDownloadObject ddo=new DeviceDownloadObject(); + ddo.setDevice(this); + ddo.setStatus(DownloadStatus.APPLICATIONERROR); + ArrayList egvRecords=new ArrayList(); + ddo.setEgvRecords(new EGVRecord[0]); + try { + mongoClient = new MongoClient(uri); + db = mongoClient.getDB(uri.getDatabase()); + deviceData = db.getCollection(collectionName); + //FIXME Limit this unless we want to kill the heap over time... + BasicDBObject query=new BasicDBObject("date", new BasicDBObject("$gt",lastQueryDate)); + DBCursor cursor=deviceData.find(query); + try { + List dbObjects=cursor.toArray(); + Log.d(TAG,"Size of response from mongo query: "+dbObjects.size()); + for (DBObject dbObject:dbObjects){ + long recQueryDate=Long.valueOf(dbObject.get("date").toString()); + Date recDate=new Date(recQueryDate); + int bgValue=Integer.valueOf(dbObject.get("sgv").toString()); + Trend trend=Trend.values()[Integer.valueOf(dbObject.get("trend").toString())]; + EGVRecord record; + if (recQueryDate>lastQueryDate){ + lastQueryDate=recQueryDate; + record=new EGVRecord(bgValue,recDate,trend,true); + lastRecord[0]=record; + }else{ + record=new EGVRecord(bgValue,recDate,trend,false); + } + egvRecords.add(record); + ddo.setEgvRecords(egvRecords.toArray(new EGVRecord[egvRecords.size()])); + } + ddo.setStatus(DownloadStatus.SUCCESS); + //FIXME there has to be a more efficient way + if (lastRecord!=null && ddo.getEgvRecords().length==0){ + ddo.setStatus(DownloadStatus.NORECORDS); + ddo.setEgvRecords(lastRecord); + } + + } finally { + cursor.close(); + } + Log.d(TAG, "Performing download of data from mongo for " + getName()); + mongoClient.close(); + }catch(UnknownHostException e){ + Log.e(TAG,"Unable to connect to MongoDB URI",e); + ddo.setStatus(DownloadStatus.DEVICENOTFOUND); + ddo.setEgvRecords(new EGVRecord[0]); + } + // FIXME potential cause for a race condition + // Should be resolved by device proxy + lastDownloadObject=ddo; + return ddo; + } + + @Override + public void disconnect() { + + } +} \ No newline at end of file diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/SettingsActivity.java b/mobile/src/main/java/com/ktind/cgm/bgscout/SettingsActivity.java new file mode 100644 index 0000000..2fc01c2 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/SettingsActivity.java @@ -0,0 +1,292 @@ +package com.ktind.cgm.bgscout; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Configuration; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceCategory; +import android.preference.PreferenceFragment; +import android.preference.PreferenceManager; +import android.preference.RingtonePreference; +import android.text.TextUtils; +import android.view.MenuItem; +import android.support.v4.app.NavUtils; +import com.ktind.cgm.bgscout.R; + +import java.util.List; + +/** + * A {@link PreferenceActivity} that presents a set of application settings. On + * handset devices, settings are presented as a single list. On tablets, + * settings are split by category, with category headers shown to the left of + * the list of settings. + *

+ * See + * Android Design: Settings for design guidelines and the Settings + * API Guide for more information on developing a Settings UI. + */ +public class SettingsActivity extends PreferenceActivity { + /** + * Determines whether to always show the simplified settings UI, where + * settings are presented in a single list. When false, settings are shown + * as a master/detail two-pane view on tablets. When true, a single pane is + * shown on tablets. + */ + private static final boolean ALWAYS_SIMPLE_PREFS = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setupActionBar(); + } + + /** + * Set up the {@link android.app.ActionBar}, if the API is available. + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + private void setupActionBar() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + // Show the Up button in the action bar. + getActionBar().setDisplayHomeAsUpEnabled(true); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + // This ID represents the Home or Up button. In the case of this + // activity, the Up button is shown. Use NavUtils to allow users + // to navigate up one level in the application structure. For + // more details, see the Navigation pattern on Android Design: + // + // http://developer.android.com/design/patterns/navigation.html#up-vs-back + // + // TODO: If Settings has multiple levels, Up should navigate up + // that hierarchy. + NavUtils.navigateUpFromSameTask(this); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + + setupSimplePreferencesScreen(); + } + + /** + * Shows the simplified settings UI if the device configuration if the + * device configuration dictates that a simplified, single-pane UI should be + * shown. + */ + private void setupSimplePreferencesScreen() { + if (!isSimplePreferences(this)) { + return; + } + + // In the simplified UI, fragments are not used at all and we instead + // use the older PreferenceActivity APIs. + + // Add 'general' preferences. + addPreferencesFromResource(R.xml.pref_general); + + // Add 'notifications' preferences, and a corresponding header. + PreferenceCategory fakeHeader = new PreferenceCategory(this); + fakeHeader.setTitle(R.string.pref_header_notifications); + getPreferenceScreen().addPreference(fakeHeader); + addPreferencesFromResource(R.xml.pref_notification); + + // Add 'data and sync' preferences, and a corresponding header. + fakeHeader = new PreferenceCategory(this); + fakeHeader.setTitle(R.string.pref_header_data_sync); + getPreferenceScreen().addPreference(fakeHeader); + addPreferencesFromResource(R.xml.pref_data_sync); + + // Bind the summaries of EditText/List/Dialog/Ringtone preferences to + // their values. When their values change, their summaries are updated + // to reflect the new value, per the Android Design guidelines. + bindPreferenceSummaryToValue(findPreference("example_text")); + bindPreferenceSummaryToValue(findPreference("example_list")); + bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone")); + bindPreferenceSummaryToValue(findPreference("sync_frequency")); + } + + /** {@inheritDoc} */ + @Override + public boolean onIsMultiPane() { + return isXLargeTablet(this) && !isSimplePreferences(this); + } + + /** + * Helper method to determine if the device has an extra-large screen. For + * example, 10" tablets are extra-large. + */ + private static boolean isXLargeTablet(Context context) { + return (context.getResources().getConfiguration().screenLayout + & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE; + } + + /** + * Determines whether the simplified settings UI should be shown. This is + * true if this is forced via {@link #ALWAYS_SIMPLE_PREFS}, or the device + * doesn't have newer APIs like {@link PreferenceFragment}, or the device + * doesn't have an extra-large screen. In these cases, a single-pane + * "simplified" settings UI should be shown. + */ + private static boolean isSimplePreferences(Context context) { + return ALWAYS_SIMPLE_PREFS + || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB + || !isXLargeTablet(context); + } + + /** {@inheritDoc} */ + @Override + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public void onBuildHeaders(List

target) { + if (!isSimplePreferences(this)) { + loadHeadersFromResource(R.xml.pref_headers, target); + } + } + + /** + * A preference value change listener that updates the preference's summary + * to reflect its new value. + */ + private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object value) { + String stringValue = value.toString(); + + if (preference instanceof ListPreference) { + // For list preferences, look up the correct display value in + // the preference's 'entries' list. + ListPreference listPreference = (ListPreference) preference; + int index = listPreference.findIndexOfValue(stringValue); + + // Set the summary to reflect the new value. + preference.setSummary( + index >= 0 + ? listPreference.getEntries()[index] + : null); + + } else if (preference instanceof RingtonePreference) { + // For ringtone preferences, look up the correct display value + // using RingtoneManager. + if (TextUtils.isEmpty(stringValue)) { + // Empty values correspond to 'silent' (no ringtone). + preference.setSummary(R.string.pref_ringtone_silent); + + } else { + Ringtone ringtone = RingtoneManager.getRingtone( + preference.getContext(), Uri.parse(stringValue)); + + if (ringtone == null) { + // Clear the summary if there was a lookup error. + preference.setSummary(null); + } else { + // Set the summary to reflect the new ringtone display + // name. + String name = ringtone.getTitle(preference.getContext()); + preference.setSummary(name); + } + } + + } else { + // For all other preferences, set the summary to the value's + // simple string representation. + preference.setSummary(stringValue); + } + return true; + } + }; + + /** + * Binds a preference's summary to its value. More specifically, when the + * preference's value is changed, its summary (line of text below the + * preference title) is updated to reflect the value. The summary is also + * immediately updated upon calling this method. The exact display format is + * dependent on the type of preference. + * + * @see #sBindPreferenceSummaryToValueListener + */ + private static void bindPreferenceSummaryToValue(Preference preference) { + // Set the listener to watch for value changes. + preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener); + + // Trigger the listener immediately with the preference's + // current value. + sBindPreferenceSummaryToValueListener.onPreferenceChange(preference, + PreferenceManager + .getDefaultSharedPreferences(preference.getContext()) + .getString(preference.getKey(), "")); + } + + /** + * This fragment shows general preferences only. It is used when the + * activity is showing a two-pane settings UI. + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class GeneralPreferenceFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pref_general); + + // Bind the summaries of EditText/List/Dialog/Ringtone preferences + // to their values. When their values change, their summaries are + // updated to reflect the new value, per the Android Design + // guidelines. + bindPreferenceSummaryToValue(findPreference("example_text")); + bindPreferenceSummaryToValue(findPreference("example_list")); + } + } + + /** + * This fragment shows notification preferences only. It is used when the + * activity is showing a two-pane settings UI. + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class NotificationPreferenceFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pref_notification); + + // Bind the summaries of EditText/List/Dialog/Ringtone preferences + // to their values. When their values change, their summaries are + // updated to reflect the new value, per the Android Design + // guidelines. + bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone")); + } + } + + /** + * This fragment shows data and sync preferences only. It is used when the + * activity is showing a two-pane settings UI. + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class DataSyncPreferenceFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pref_data_sync); + + // Bind the summaries of EditText/List/Dialog/Ringtone preferences + // to their values. When their values change, their summaries are + // updated to reflect the new value, per the Android Design + // guidelines. + bindPreferenceSummaryToValue(findPreference("sync_frequency")); + } + } +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/Trend.java b/mobile/src/main/java/com/ktind/cgm/bgscout/Trend.java new file mode 100644 index 0000000..c5c8e81 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/Trend.java @@ -0,0 +1,39 @@ +package com.ktind.cgm.bgscout; + +/** + * Created by klee24 on 8/2/14. + */ +public enum Trend { + NONE("None",0,"NONE"), + DOUBLEUP("Double up",1,"DoubleUp"), + SINGLEUP("Single up",2,"SingleUp"), + FORTYFIVEUP("Forty-five up",3,"FortyFiveUp"), + FLAT("Flat",4,"Flat"), + FORTYFIVEDOWN("Forty-five down",5,"FortyFiveDown"), + SINGLEDOWN("Single down",6,"SingleDown"), + DOUBLEDOWN("Double down",7,"DoubleDown"), + NOTCOMPUTE("Not computable",8,"NOT COMPUTABLE"), + RATEOUTRANGE("Rate out of Range",9,"RATE OUT OF RANGE"); + + private String stringVal; + private int intVal; + private String nsString="NONE"; + private Trend(String strVal, int integerVal,String nsStr){ + this.stringVal=strVal; + this.intVal=integerVal; + this.nsString=nsStr; + } + + public int getVal() { + return intVal; + } + + @Override + public String toString(){ + return stringVal; + } + + public String getNsString(){ + return nsString; + } +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/USB/CdcAcmSerialDriver.java b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/CdcAcmSerialDriver.java new file mode 100644 index 0000000..08d5e2d --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/CdcAcmSerialDriver.java @@ -0,0 +1,251 @@ +package com.ktind.cgm.bgscout.USB; + +import android.hardware.usb.*; +import android.util.Log; + +import java.io.IOException; + +//import com.hoho.android.usbserial.driver.UsbId; + +/** + * USB CDC/ACM serial driver implementation. + * + * @author mike wakerly (opensource@hoho.com) + * @see Universal + * Serial Bus Class Definitions for Communication Devices, v1.1 + */ +public class CdcAcmSerialDriver extends CommonUsbSerialDriver { + + private final String TAG = CdcAcmSerialDriver.class.getSimpleName(); + + private UsbInterface mControlInterface; + private UsbInterface mDataInterface; + + private UsbEndpoint mControlEndpoint; + private UsbEndpoint mReadEndpoint; + private UsbEndpoint mWriteEndpoint; + + private boolean mRts = false; + private boolean mDtr = false; + + private static final int USB_RECIP_INTERFACE = 0x01; + private static final int USB_RT_ACM = UsbConstants.USB_TYPE_CLASS | USB_RECIP_INTERFACE; + + private static final int SET_LINE_CODING = 0x20; // USB CDC 1.1 section 6.2 + private static final int GET_LINE_CODING = 0x21; + private static final int SET_CONTROL_LINE_STATE = 0x22; + private static final int SEND_BREAK = 0x23; + private static final String SET_POWER_ON_COMMAND = "echo 'on' > \"/sys/bus/usb/devices/1-1/power/level\""; + + public CdcAcmSerialDriver(UsbDevice device, UsbDeviceConnection connection) { + super(device, connection); + } + + @Override + public void open() throws IOException { + Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount()); + + Log.d(TAG, "Claiming control interface."); + mControlInterface = mDevice.getInterface(0); + Log.d(TAG, "Control iface=" + mControlInterface); + // class should be USB_CLASS_COMM + + if (!mConnection.claimInterface(mControlInterface, true)) { + throw new IOException("Could not claim control interface."); + } + mControlEndpoint = mControlInterface.getEndpoint(0); + Log.d(TAG, "Control endpoint direction: " + mControlEndpoint.getDirection()); + + Log.d(TAG, "Claiming data interface."); + mDataInterface = mDevice.getInterface(1); + Log.d(TAG, "data iface=" + mDataInterface); + // class should be USB_CLASS_CDC_DATA + + if (!mConnection.claimInterface(mDataInterface, true)) { + throw new IOException("Could not claim data interface."); + } + mReadEndpoint = mDataInterface.getEndpoint(1); + Log.d(TAG, "Read endpoint direction: " + mReadEndpoint.getDirection()); + mWriteEndpoint = mDataInterface.getEndpoint(0); + Log.d(TAG, "Write endpoint direction: " + mWriteEndpoint.getDirection()); + } + + private int sendAcmControlMessage(int request, int value, byte[] buf) { + return mConnection.controlTransfer( + USB_RT_ACM, request, value, 0, buf, buf != null ? buf.length : 0, 5000); + } + + @Override + public void close() throws IOException { + mConnection.close(); + } + + @Override + public int read(byte[] dest, int timeoutMillis) throws IOException { + final int numBytesRead; + synchronized (mReadBufferLock) { + int readAmt = Math.min(dest.length, mReadBuffer.length); + numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, readAmt, + timeoutMillis); + if (numBytesRead < 0) { + // This sucks: we get -1 on timeout, not 0 as preferred. + // We *should* use UsbRequest, except it has a bug/api oversight + // where there is no way to determine the number of bytes read + // in response :\ -- http://b.android.com/28023 + Log.e("G4Device","possible timeout (numBytesRead): "+numBytesRead); + Log.d("G4Device","timeoutMillis "+timeoutMillis); + Log.d("G4Device","readAmt "+readAmt); + return 0; + } + + System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead); + } + return numBytesRead; + } + + @Override + public int write(byte[] src, int timeoutMillis) throws IOException { + // TODO(mikey): Nearly identical to FtdiSerial write. Refactor. + int offset = 0; + + while (offset < src.length) { + final int writeLength; + final int amtWritten; + + synchronized (mWriteBufferLock) { + final byte[] writeBuffer; + + writeLength = Math.min(src.length - offset, mWriteBuffer.length); + if (offset == 0) { + writeBuffer = src; + } else { + // bulkTransfer does not support offsets, make a copy. + System.arraycopy(src, offset, mWriteBuffer, 0, writeLength); + writeBuffer = mWriteBuffer; + } + + amtWritten = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, writeLength, + timeoutMillis); + } + if (amtWritten <= 0) { + throw new IOException("Error writing " + writeLength + + " bytes at offset " + offset + " length=" + src.length); + } + + Log.d(TAG, "Wrote amt=" + amtWritten + " attempted=" + writeLength); + offset += amtWritten; + } + return offset; + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, int parity) { + byte stopBitsByte; + switch (stopBits) { + case STOPBITS_1: stopBitsByte = 0; break; + case STOPBITS_1_5: stopBitsByte = 1; break; + case STOPBITS_2: stopBitsByte = 2; break; + default: throw new IllegalArgumentException("Bad value for stopBits: " + stopBits); + } + + byte parityBitesByte; + switch (parity) { + case PARITY_NONE: parityBitesByte = 0; break; + case PARITY_ODD: parityBitesByte = 1; break; + case PARITY_EVEN: parityBitesByte = 2; break; + case PARITY_MARK: parityBitesByte = 3; break; + case PARITY_SPACE: parityBitesByte = 4; break; + default: throw new IllegalArgumentException("Bad value for parity: " + parity); + } + + byte[] msg = { + (byte) ( baudRate & 0xff), + (byte) ((baudRate >> 8 ) & 0xff), + (byte) ((baudRate >> 16) & 0xff), + (byte) ((baudRate >> 24) & 0xff), + stopBitsByte, + parityBitesByte, + (byte) dataBits}; + sendAcmControlMessage(SET_LINE_CODING, 0, msg); + } + + @Override + public boolean getCD() throws IOException { + return false; // TODO + } + + @Override + public boolean getCTS() throws IOException { + return false; // TODO + } + + @Override + public boolean getDSR() throws IOException { + return false; // TODO + } + + @Override + public boolean getDTR() throws IOException { + return mDtr; + } + + @Override + public void setDTR(boolean value) throws IOException { + mDtr = value; + setDtrRts(); + } + + @Override + public boolean getRI() throws IOException { + return false; // TODO + } + + @Override + public boolean getRTS() throws IOException { + return mRts; + } + + @Override + public void setRTS(boolean value) throws IOException { + mRts = value; + setDtrRts(); + } + + private void setDtrRts() { + int value = (mRts ? 0x2 : 0) | (mDtr ? 0x1 : 0); + sendAcmControlMessage(SET_CONTROL_LINE_STATE, value, null); + } + + + +/* public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap(); + supportedDevices.put(Integer.valueOf(UsbId.VENDOR_ARDUINO), + new int[] { + UsbId.ARDUINO_UNO, + UsbId.ARDUINO_UNO_R3, + UsbId.ARDUINO_MEGA_2560, + UsbId.ARDUINO_MEGA_2560_R3, + UsbId.ARDUINO_SERIAL_ADAPTER, + UsbId.ARDUINO_SERIAL_ADAPTER_R3, + UsbId.ARDUINO_MEGA_ADK, + UsbId.ARDUINO_MEGA_ADK_R3, + UsbId.ARDUINO_LEONARDO, + }); + supportedDevices.put(Integer.valueOf(UsbId.VENDOR_VAN_OOIJEN_TECH), + new int[] { + UsbId.VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL, + }); + supportedDevices.put(Integer.valueOf(UsbId.VENDOR_ATMEL), + new int[] { + UsbId.ATMEL_LUFA_CDC_DEMO_APP, + }); + supportedDevices.put(Integer.valueOf(UsbId.VENDOR_LEAFLABS), + new int[] { + UsbId.LEAFLABS_MAPLE, + }); + return supportedDevices; + }*/ + +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/USB/CommonUsbSerialDriver.java b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/CommonUsbSerialDriver.java new file mode 100644 index 0000000..7dc5c9c --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/CommonUsbSerialDriver.java @@ -0,0 +1,138 @@ +/* Copyright 2013 Google Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * Project home page: http://code.google.com/p/usb-serial-for-android/ + */ + +package com.ktind.cgm.bgscout.USB; + +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; + +import java.io.IOException; + + +/** + * A base class shared by several driver implementations. + * + * @author mike wakerly (opensource@hoho.com) + */ +abstract class CommonUsbSerialDriver implements UsbSerialDriver { + + public static final int DEFAULT_READ_BUFFER_SIZE = 16 * 1024; + public static final int DEFAULT_WRITE_BUFFER_SIZE = 16 * 1024; + + protected final UsbDevice mDevice; + protected final UsbDeviceConnection mConnection; + + protected final Object mReadBufferLock = new Object(); + protected final Object mWriteBufferLock = new Object(); + + /** Internal read buffer. Guarded by {@link #mReadBufferLock}. */ + protected byte[] mReadBuffer; + + /** Internal write buffer. Guarded by {@link #mWriteBufferLock}. */ + protected byte[] mWriteBuffer; + + public CommonUsbSerialDriver(UsbDevice device, UsbDeviceConnection connection) { + mDevice = device; + mConnection = connection; + + mReadBuffer = new byte[DEFAULT_READ_BUFFER_SIZE]; + mWriteBuffer = new byte[DEFAULT_WRITE_BUFFER_SIZE]; + } + + /** + * Returns the currently-bound USB device. + * + * @return the device + */ + public final UsbDevice getDevice() { + return mDevice; + } + + /** + * Sets the size of the internal buffer used to exchange data with the USB + * stack for read operations. Most users should not need to change this. + * + * @param bufferSize the size in bytes + */ + public final void setReadBufferSize(int bufferSize) { + synchronized (mReadBufferLock) { + if (bufferSize == mReadBuffer.length) { + return; + } + mReadBuffer = new byte[bufferSize]; + } + } + + /** + * Sets the size of the internal buffer used to exchange data with the USB + * stack for write operations. Most users should not need to change this. + * + * @param bufferSize the size in bytes + */ + public final void setWriteBufferSize(int bufferSize) { + synchronized (mWriteBufferLock) { + if (bufferSize == mWriteBuffer.length) { + return; + } + mWriteBuffer = new byte[bufferSize]; + } + } + + @Override + public abstract void open() throws IOException; + + @Override + public abstract void close() throws IOException; + + @Override + public abstract int read(final byte[] dest, final int timeoutMillis) throws IOException; + + @Override + public abstract int write(final byte[] src, final int timeoutMillis) throws IOException; + + @Override + public abstract void setParameters( + int baudRate, int dataBits, int stopBits, int parity) throws IOException; + + @Override + public abstract boolean getCD() throws IOException; + + @Override + public abstract boolean getCTS() throws IOException; + + @Override + public abstract boolean getDSR() throws IOException; + + @Override + public abstract boolean getDTR() throws IOException; + + @Override + public abstract void setDTR(boolean value) throws IOException; + + @Override + public abstract boolean getRI() throws IOException; + + @Override + public abstract boolean getRTS() throws IOException; + + @Override + public abstract void setRTS(boolean value) throws IOException; + +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/USB/HexDump.java b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/HexDump.java new file mode 100644 index 0000000..73c2da6 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/HexDump.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ktind.cgm.bgscout.USB; + +/** + * Clone of Android's HexDump class, for use in debugging. Cosmetic changes + * only. + */ +public class HexDump { + private final static char[] HEX_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + public static String dumpHexString(byte[] array) { + return dumpHexString(array, 0, array.length); + } + + public static String dumpHexString(byte[] array, int offset, int length) { + StringBuilder result = new StringBuilder(); + + byte[] line = new byte[16]; + int lineIndex = 0; + + result.append("\n0x"); + result.append(toHexString(offset)); + + for (int i = offset; i < offset + length; i++) { + if (lineIndex == 16) { + result.append(" "); + + for (int j = 0; j < 16; j++) { + if (line[j] > ' ' && line[j] < '~') { + result.append(new String(line, j, 1)); + } else { + result.append("."); + } + } + + result.append("\n0x"); + result.append(toHexString(i)); + lineIndex = 0; + } + + byte b = array[i]; + result.append(" "); + result.append(HEX_DIGITS[(b >>> 4) & 0x0F]); + result.append(HEX_DIGITS[b & 0x0F]); + + line[lineIndex++] = b; + } + + if (lineIndex != 16) { + int count = (16 - lineIndex) * 3; + count++; + for (int i = 0; i < count; i++) { + result.append(" "); + } + + for (int i = 0; i < lineIndex; i++) { + if (line[i] > ' ' && line[i] < '~') { + result.append(new String(line, i, 1)); + } else { + result.append("."); + } + } + } + + return result.toString(); + } + + public static String toHexString(byte b) { + return toHexString(toByteArray(b)); + } + + public static String toHexString(byte[] array) { + return toHexString(array, 0, array.length); + } + + public static String toHexString(byte[] array, int offset, int length) { + char[] buf = new char[length * 2]; + + int bufIndex = 0; + for (int i = offset; i < offset + length; i++) { + byte b = array[i]; + buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F]; + buf[bufIndex++] = HEX_DIGITS[b & 0x0F]; + } + + return new String(buf); + } + + public static String toHexString(int i) { + return toHexString(toByteArray(i)); + } + + public static byte[] toByteArray(byte b) { + byte[] array = new byte[1]; + array[0] = b; + return array; + } + + public static byte[] toByteArray(int i) { + byte[] array = new byte[4]; + + array[3] = (byte) (i & 0xFF); + array[2] = (byte) ((i >> 8) & 0xFF); + array[1] = (byte) ((i >> 16) & 0xFF); + array[0] = (byte) ((i >> 24) & 0xFF); + + return array; + } + + private static int toByte(char c) { + if (c >= '0' && c <= '9') + return (c - '0'); + if (c >= 'A' && c <= 'F') + return (c - 'A' + 10); + if (c >= 'a' && c <= 'f') + return (c - 'a' + 10); + + throw new RuntimeException("Invalid hex char '" + c + "'"); + } + + public static byte[] hexStringToByteArray(String hexString) { + int length = hexString.length(); + byte[] buffer = new byte[length / 2]; + + for (int i = 0; i < length; i += 2) { + buffer[i / 2] = (byte) ((toByte(hexString.charAt(i)) << 4) | toByte(hexString + .charAt(i + 1))); + } + + return buffer; + } +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/USB/SerialInputOutputManager.java b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/SerialInputOutputManager.java new file mode 100644 index 0000000..79f86de --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/SerialInputOutputManager.java @@ -0,0 +1,187 @@ +/* Copyright 2011 Google Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * Project home page: http://code.google.com/p/usb-serial-for-android/ + */ + +package com.ktind.cgm.bgscout.USB; + +import android.util.Log; + +import java.io.IOException; +import java.nio.ByteBuffer; + + +/** + * Utility class which services a {@link UsbSerialDriver} in its {@link #run()} + * method. + * + * @author mike wakerly (opensource@hoho.com) + */ +public class SerialInputOutputManager implements Runnable { + + private static final String TAG = SerialInputOutputManager.class.getSimpleName(); + private static final boolean DEBUG = true; + + private static final int READ_WAIT_MILLIS = 200; + private static final int BUFSIZ = 4096; + + private final UsbSerialDriver mDriver; + + private final ByteBuffer mReadBuffer = ByteBuffer.allocate(BUFSIZ); + + // Synchronized by 'mWriteBuffer' + private final ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ); + + private enum State { + STOPPED, + RUNNING, + STOPPING + } + + // Synchronized by 'this' + private State mState = State.STOPPED; + + // Synchronized by 'this' + private Listener mListener; + + public interface Listener { + /** + * Called when new incoming data is available. + */ + public void onNewData(byte[] data); + + /** + * Called when {@link com.ktind.cgm.bgscout.USB.SerialInputOutputManager#run()} aborts due to an + * error. + */ + public void onRunError(Exception e); + } + + /** + * Creates a new instance with no listener. + */ + public SerialInputOutputManager(UsbSerialDriver driver) { + this(driver, null); + } + + /** + * Creates a new instance with the provided listener. + */ + public SerialInputOutputManager(UsbSerialDriver driver, Listener listener) { + mDriver = driver; + mListener = listener; + } + + public synchronized void setListener(Listener listener) { + mListener = listener; + } + + public synchronized Listener getListener() { + return mListener; + } + + public void writeAsync(byte[] data) { + synchronized (mWriteBuffer) { + mWriteBuffer.put(data); + } + } + + public synchronized void stop() { + if (getState() == State.RUNNING) { + Log.i(TAG, "Stop requested"); + mState = State.STOPPING; + } + } + + private synchronized State getState() { + return mState; + } + + /** + * Continuously services the read and write buffers until {@link #stop()} is + * called, or until a driver exception is raised. + * + * NOTE(mikey): Uses inefficient read/write-with-timeout. + * TODO(mikey): Read asynchronously with {@link android.hardware.usb.UsbRequest#queue(java.nio.ByteBuffer, int)} + */ + @Override + public void run() { + synchronized (this) { + if (getState() != State.STOPPED) { + throw new IllegalStateException("Already running."); + } + mState = State.RUNNING; + } + + Log.i(TAG, "Running .."); + try { + while (true) { + if (getState() != State.RUNNING) { + Log.i(TAG, "Stopping mState=" + getState()); + break; + } + step(); + } + } catch (Exception e) { + Log.w(TAG, "Run ending due to exception: " + e.getMessage(), e); + final Listener listener = getListener(); + if (listener != null) { + listener.onRunError(e); + } + } finally { + synchronized (this) { + mState = State.STOPPED; + Log.i(TAG, "Stopped."); + } + } + } + + private void step() throws IOException { + // Handle incoming data. + int len = mDriver.read(mReadBuffer.array(), READ_WAIT_MILLIS); + if (len > 0) { + if (DEBUG) Log.d(TAG, "Read data len=" + len); + final Listener listener = getListener(); + if (listener != null) { + final byte[] data = new byte[len]; + mReadBuffer.get(data, 0, len); + listener.onNewData(data); + } + mReadBuffer.clear(); + } + + // Handle outgoing data. + byte[] outBuff = null; + synchronized (mWriteBuffer) { + if (mWriteBuffer.position() > 0) { + len = mWriteBuffer.position(); + outBuff = new byte[len]; + mWriteBuffer.rewind(); + mWriteBuffer.get(outBuff, 0, len); + mWriteBuffer.clear(); + } + } + if (outBuff != null) { + if (DEBUG) { + Log.d(TAG, "Writing data len=" + len); + } + mDriver.write(outBuff, READ_WAIT_MILLIS); + } + } + +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/USB/USBPower.java b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/USBPower.java new file mode 100644 index 0000000..18a246c --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/USBPower.java @@ -0,0 +1,44 @@ +package com.ktind.cgm.bgscout.USB; + +import android.util.Log; + +import java.io.DataOutputStream; + +public class USBPower { + + private static final String TAG = USBPower.class.getSimpleName(); + + private static final String SET_POWER_ON_COMMAND = "echo 'on' > \"/sys/bus/usb/devices/1-1/power/level\""; + private static final String SET_POWER_SUSPEND_COMMAND_A = "echo \"0\" > \"/sys/bus/usb/devices/1-1/power/autosuspend\""; + private static final String SET_POWER_SUSPEND_COMMAND_B = "echo \"auto\" > \"/sys/bus/usb/devices/1-1/power/level\""; + + public static void PowerOff() { + try { + runCommand(SET_POWER_SUSPEND_COMMAND_A); + runCommand(SET_POWER_SUSPEND_COMMAND_B); + Log.i(TAG, "PowerOff USB complete"); + } catch (Exception e) { + Log.e(TAG, "Unable to PowerOff USB"); + } + } + + public static void PowerOn(){ + try { + runCommand(SET_POWER_ON_COMMAND); + Log.i(TAG, "PowerOn USB complete"); + } catch (Exception e) { + Log.e(TAG, "Unable to PowerOn USB"); + } + } + + private static void runCommand(String command) throws Exception { + Process process = Runtime.getRuntime().exec("su"); + DataOutputStream os = new DataOutputStream(process.getOutputStream()); + os.writeBytes(command + "\n"); + os.flush(); + os.writeBytes("exit \n"); + os.flush(); + os.close(); + process.waitFor(); + } +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/USB/USBPowerState.java b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/USBPowerState.java new file mode 100644 index 0000000..966ae7d --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/USBPowerState.java @@ -0,0 +1,9 @@ +package com.ktind.cgm.bgscout.USB; + +/** + * Created by klee24 on 7/27/14. + */ +public enum USBPowerState { + ON, + OFF +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/USB/UsbSerialDriver.java b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/UsbSerialDriver.java new file mode 100644 index 0000000..e25c030 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/UsbSerialDriver.java @@ -0,0 +1,200 @@ +/* Copyright 2011 Google Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * Project home page: http://code.google.com/p/usb-serial-for-android/ + */ + +package com.ktind.cgm.bgscout.USB; + +import java.io.IOException; + +/** + * Driver interface for a USB serial device. + * + * @author mike wakerly (opensource@hoho.com) + */ +public interface UsbSerialDriver { + + /** 5 data bits. */ + public static final int DATABITS_5 = 5; + + /** 6 data bits. */ + public static final int DATABITS_6 = 6; + + /** 7 data bits. */ + public static final int DATABITS_7 = 7; + + /** 8 data bits. */ + public static final int DATABITS_8 = 8; + + /** No flow control. */ + public static final int FLOWCONTROL_NONE = 0; + + /** RTS/CTS input flow control. */ + public static final int FLOWCONTROL_RTSCTS_IN = 1; + + /** RTS/CTS output flow control. */ + public static final int FLOWCONTROL_RTSCTS_OUT = 2; + + /** XON/XOFF input flow control. */ + public static final int FLOWCONTROL_XONXOFF_IN = 4; + + /** XON/XOFF output flow control. */ + public static final int FLOWCONTROL_XONXOFF_OUT = 8; + + /** No parity. */ + public static final int PARITY_NONE = 0; + + /** Odd parity. */ + public static final int PARITY_ODD = 1; + + /** Even parity. */ + public static final int PARITY_EVEN = 2; + + /** Mark parity. */ + public static final int PARITY_MARK = 3; + + /** Space parity. */ + public static final int PARITY_SPACE = 4; + + /** 1 stop bit. */ + public static final int STOPBITS_1 = 1; + + /** 1.5 stop bits. */ + public static final int STOPBITS_1_5 = 3; + + /** 2 stop bits. */ + public static final int STOPBITS_2 = 2; + + /** + * Opens and initializes the device as a USB serial device. Upon success, + * caller must ensure that {@link #close()} is eventually called. + * + * @throws java.io.IOException on error opening or initializing the device. + */ + public void open() throws IOException; + + /** + * Closes the serial device. + * + * @throws java.io.IOException on error closing the device. + */ + public void close() throws IOException; + + /** + * Reads as many bytes as possible into the destination buffer. + * + * @param dest the destination byte buffer + * @param timeoutMillis the timeout for reading + * @return the actual number of bytes read + * @throws java.io.IOException if an error occurred during reading + */ + public int read(final byte[] dest, final int timeoutMillis) throws IOException; + + /** + * Writes as many bytes as possible from the source buffer. + * + * @param src the source byte buffer + * @param timeoutMillis the timeout for writing + * @return the actual number of bytes written + * @throws java.io.IOException if an error occurred during writing + */ + public int write(final byte[] src, final int timeoutMillis) throws IOException; + + /** + * Sets various serial port parameters. + * + * @param baudRate baud rate as an integer, for example {@code 115200}. + * @param dataBits one of {@link #DATABITS_5}, {@link #DATABITS_6}, + * {@link #DATABITS_7}, or {@link #DATABITS_8}. + * @param stopBits one of {@link #STOPBITS_1}, {@link #STOPBITS_1_5}, or + * {@link #STOPBITS_2}. + * @param parity one of {@link #PARITY_NONE}, {@link #PARITY_ODD}, + * {@link #PARITY_EVEN}, {@link #PARITY_MARK}, or + * {@link #PARITY_SPACE}. + * @throws java.io.IOException on error setting the port parameters + */ + public void setParameters( + int baudRate, int dataBits, int stopBits, int parity) throws IOException; + + /** + * Gets the CD (Carrier Detect) bit from the underlying UART. + * + * @return the current state, or {@code false} if not supported. + * @throws java.io.IOException if an error occurred during reading + */ + public boolean getCD() throws IOException; + + /** + * Gets the CTS (Clear To Send) bit from the underlying UART. + * + * @return the current state, or {@code false} if not supported. + * @throws java.io.IOException if an error occurred during reading + */ + public boolean getCTS() throws IOException; + + /** + * Gets the DSR (Data Set Ready) bit from the underlying UART. + * + * @return the current state, or {@code false} if not supported. + * @throws java.io.IOException if an error occurred during reading + */ + public boolean getDSR() throws IOException; + + /** + * Gets the DTR (Data Terminal Ready) bit from the underlying UART. + * + * @return the current state, or {@code false} if not supported. + * @throws java.io.IOException if an error occurred during reading + */ + public boolean getDTR() throws IOException; + + /** + * Sets the DTR (Data Terminal Ready) bit on the underlying UART, if + * supported. + * + * @param value the value to set + * @throws java.io.IOException if an error occurred during writing + */ + public void setDTR(boolean value) throws IOException; + + /** + * Gets the RI (Ring Indicator) bit from the underlying UART. + * + * @return the current state, or {@code false} if not supported. + * @throws java.io.IOException if an error occurred during reading + */ + public boolean getRI() throws IOException; + + /** + * Gets the RTS (Request To Send) bit from the underlying UART. + * + * @return the current state, or {@code false} if not supported. + * @throws java.io.IOException if an error occurred during reading + */ + public boolean getRTS() throws IOException; + + /** + * Sets the RTS (Request To Send) bit on the underlying UART, if + * supported. + * + * @param value the value to set + * @throws java.io.IOException if an error occurred during writing + */ + public void setRTS(boolean value) throws IOException; + +} diff --git a/mobile/src/main/java/com/ktind/cgm/bgscout/USB/UsbSerialProber.java b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/UsbSerialProber.java new file mode 100644 index 0000000..9e857f5 --- /dev/null +++ b/mobile/src/main/java/com/ktind/cgm/bgscout/USB/UsbSerialProber.java @@ -0,0 +1,164 @@ +/* Copyright 2011 Google Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * Project home page: http://code.google.com/p/usb-serial-for-android/ + */ + +package com.ktind.cgm.bgscout.USB; + +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbManager; + +import java.util.Map; + +//import com.hoho.android.usbserial.driver.Cp2102SerialDriver; +//import com.hoho.android.usbserial.driver.FtdiSerialDriver; + +/** + * Helper class to assist in detecting and building {@link UsbSerialDriver} + * instances from available hardware. + * + * @author mike wakerly (opensource@hoho.com) + */ +public enum UsbSerialProber { + + // TODO(mikey): Too much boilerplate. + + /** + * Prober for {@link FtdiSerialDriver}. + * + * @see FtdiSerialDriver + */ +/* FTDI_SERIAL { + @Override + public UsbSerialDriver getDevice(final UsbManager manager, final UsbDevice usbDevice) { + if (!testIfSupported(usbDevice, FtdiSerialDriver.getSupportedDevices())) { + return null; + } + final UsbDeviceConnection connection = manager.openDevice(usbDevice); + if (connection == null) { + return null; + } + return new FtdiSerialDriver(usbDevice, connection); + } + },*/ + + CDC_ACM_SERIAL { + @Override + public UsbSerialDriver getDevice(UsbManager manager, UsbDevice usbDevice) { +// if (!testIfSupported(usbDevice, CdcAcmSerialDriver.getSupportedDevices())) { +// return null; +// } + final UsbDeviceConnection connection = manager.openDevice(usbDevice); + if (connection == null) { + return null; + } + return new CdcAcmSerialDriver(usbDevice, connection); + } + }; //, + +/* SILAB_SERIAL { + @Override + public UsbSerialDriver getDevice(final UsbManager manager, final UsbDevice usbDevice) { + if (!testIfSupported(usbDevice, Cp2102SerialDriver.getSupportedDevices())) { + return null; + } + final UsbDeviceConnection connection = manager.openDevice(usbDevice); + if (connection == null) { + return null; + } + return new Cp2102SerialDriver(usbDevice, connection); + } + };*/ + + /** + * Builds a new {@link UsbSerialDriver} instance from the raw device, or + * returns null if it could not be built (for example, if the + * probe failed). + * + * @param manager the {@link android.hardware.usb.UsbManager} to use + * @param usbDevice the raw {@link android.hardware.usb.UsbDevice} to use + * @return the first available {@link UsbSerialDriver}, or {@code null} if + * no devices could be acquired + */ + public abstract UsbSerialDriver getDevice(final UsbManager manager, final UsbDevice usbDevice); + + /** + * Acquires and returns the first available serial device among all + * available {@link android.hardware.usb.UsbDevice}s, or returns {@code null} if no device could + * be acquired. + * + * @param usbManager the {@link android.hardware.usb.UsbManager} to use + * @return the first available {@link UsbSerialDriver}, or {@code null} if + * no devices could be acquired + */ + public static UsbSerialDriver acquire(final UsbManager usbManager) { + for (final UsbDevice usbDevice : usbManager.getDeviceList().values()) { + final UsbSerialDriver probedDevice = acquire(usbManager, usbDevice); + if (probedDevice != null) { + return probedDevice; + } + } + return null; + } + + /** + * Builds and returns a new {@link UsbSerialDriver} from the given + * {@link android.hardware.usb.UsbDevice}, or returns {@code null} if no drivers supported this + * device. + * + * @param usbManager the {@link android.hardware.usb.UsbManager} to use + * @param usbDevice the {@link android.hardware.usb.UsbDevice} to use + * @return a new {@link UsbSerialDriver}, or {@code null} if no devices + * could be acquired + */ + public static UsbSerialDriver acquire(final UsbManager usbManager, final UsbDevice usbDevice) { + for (final UsbSerialProber prober : values()) { + final UsbSerialDriver probedDevice = prober.getDevice(usbManager, usbDevice); + if (probedDevice != null) { + return probedDevice; + } + } + return null; + } + + /** + * Returns {@code true} if the given device is found in the vendor/product map. + * + * @param usbDevice the device to test + * @param supportedDevices map of vendor ids to product id(s) + * @return {@code true} if supported + */ + private static boolean testIfSupported(final UsbDevice usbDevice, + final Map supportedDevices) { + final int[] supportedProducts = supportedDevices.get( + Integer.valueOf(usbDevice.getVendorId())); + if (supportedProducts == null) { + return false; + } + + final int productId = usbDevice.getProductId(); + for (int supportedProductId : supportedProducts) { + if (productId == supportedProductId) { + return true; + } + } + return false; + } + +} diff --git a/mobile/src/main/res/drawable-hdpi/arrow45downblue.png b/mobile/src/main/res/drawable-hdpi/arrow45downblue.png new file mode 100644 index 0000000..cf01061 Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrow45downblue.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrow45downred.png b/mobile/src/main/res/drawable-hdpi/arrow45downred.png new file mode 100644 index 0000000..6fd665e Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrow45downred.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrow45downyellow.png b/mobile/src/main/res/drawable-hdpi/arrow45downyellow.png new file mode 100644 index 0000000..0d5dc49 Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrow45downyellow.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrow45upblue.png b/mobile/src/main/res/drawable-hdpi/arrow45upblue.png new file mode 100644 index 0000000..7a8a2bc Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrow45upblue.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrow45upred.png b/mobile/src/main/res/drawable-hdpi/arrow45upred.png new file mode 100644 index 0000000..53858c0 Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrow45upred.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrow45upyellow.png b/mobile/src/main/res/drawable-hdpi/arrow45upyellow.png new file mode 100644 index 0000000..d3835b5 Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrow45upyellow.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrowdoubledownblue.png b/mobile/src/main/res/drawable-hdpi/arrowdoubledownblue.png new file mode 100644 index 0000000..82ae153 Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowdoubledownblue.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrowdoubledownred.png b/mobile/src/main/res/drawable-hdpi/arrowdoubledownred.png new file mode 100644 index 0000000..067123c Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowdoubledownred.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrowdoubledownyellow.png b/mobile/src/main/res/drawable-hdpi/arrowdoubledownyellow.png new file mode 100644 index 0000000..45be6fc Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowdoubledownyellow.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrowdoubleupblue.png b/mobile/src/main/res/drawable-hdpi/arrowdoubleupblue.png new file mode 100644 index 0000000..0056e4d Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowdoubleupblue.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrowdoubleupred.png b/mobile/src/main/res/drawable-hdpi/arrowdoubleupred.png new file mode 100644 index 0000000..6900bb0 Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowdoubleupred.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrowdoubleupyellow.png b/mobile/src/main/res/drawable-hdpi/arrowdoubleupyellow.png new file mode 100644 index 0000000..d53674d Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowdoubleupyellow.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrowdownblue.png b/mobile/src/main/res/drawable-hdpi/arrowdownblue.png new file mode 100644 index 0000000..d4cf027 Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowdownblue.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrowdownred.png b/mobile/src/main/res/drawable-hdpi/arrowdownred.png new file mode 100644 index 0000000..c6c8332 Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowdownred.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrowdownyellow.png b/mobile/src/main/res/drawable-hdpi/arrowdownyellow.png new file mode 100644 index 0000000..9c67bb8 Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowdownyellow.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrowflatblue.png b/mobile/src/main/res/drawable-hdpi/arrowflatblue.png new file mode 100644 index 0000000..7fe4d5e Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowflatblue.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrowflatred.png b/mobile/src/main/res/drawable-hdpi/arrowflatred.png new file mode 100644 index 0000000..76dd031 Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowflatred.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrowflatyellow.png b/mobile/src/main/res/drawable-hdpi/arrowflatyellow.png new file mode 100644 index 0000000..18808ab Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowflatyellow.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrowupblue.png b/mobile/src/main/res/drawable-hdpi/arrowupblue.png new file mode 100644 index 0000000..8b1360f Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowupblue.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrowupred.png b/mobile/src/main/res/drawable-hdpi/arrowupred.png new file mode 100644 index 0000000..5aa24b0 Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowupred.png differ diff --git a/mobile/src/main/res/drawable-hdpi/arrowupyellow.png b/mobile/src/main/res/drawable-hdpi/arrowupyellow.png new file mode 100644 index 0000000..e33b40b Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/arrowupyellow.png differ diff --git a/mobile/src/main/res/drawable-hdpi/ic_launcher.png b/mobile/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..96a442e Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/mobile/src/main/res/drawable-hdpi/icon.png b/mobile/src/main/res/drawable-hdpi/icon.png new file mode 100644 index 0000000..6ca9c46 Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/icon.png differ diff --git a/mobile/src/main/res/drawable-hdpi/icon24x24.png b/mobile/src/main/res/drawable-hdpi/icon24x24.png new file mode 100644 index 0000000..dd8d454 Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/icon24x24.png differ diff --git a/mobile/src/main/res/drawable-hdpi/noneblue.png b/mobile/src/main/res/drawable-hdpi/noneblue.png new file mode 100644 index 0000000..f053146 Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/noneblue.png differ diff --git a/mobile/src/main/res/drawable-hdpi/nonered.png b/mobile/src/main/res/drawable-hdpi/nonered.png new file mode 100644 index 0000000..e36cb64 Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/nonered.png differ diff --git a/mobile/src/main/res/drawable-hdpi/noneyellow.png b/mobile/src/main/res/drawable-hdpi/noneyellow.png new file mode 100644 index 0000000..b899710 Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/noneyellow.png differ diff --git a/mobile/src/main/res/drawable-hdpi/questionmarkicon.png b/mobile/src/main/res/drawable-hdpi/questionmarkicon.png new file mode 100644 index 0000000..c7687b3 Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/questionmarkicon.png differ diff --git a/mobile/src/main/res/drawable-mdpi/ic_launcher.png b/mobile/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..359047d Binary files /dev/null and b/mobile/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/mobile/src/main/res/drawable-xhdpi/ic_launcher.png b/mobile/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000..71c6d76 Binary files /dev/null and b/mobile/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/mobile/src/main/res/drawable-xxhdpi/ic_launcher.png b/mobile/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..4df1894 Binary files /dev/null and b/mobile/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/mobile/src/main/res/layout/activity_main.xml b/mobile/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..790dfb7 --- /dev/null +++ b/mobile/src/main/res/layout/activity_main.xml @@ -0,0 +1,25 @@ + + + + +