├── .gitattributes ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── jarRepositories.xml └── misc.xml ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── moveTrackLib ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ └── org │ │ └── moveTrack │ │ ├── AppStatus.java │ │ ├── AutoDiscoverer.java │ │ ├── GyroListener.java │ │ ├── Handshaker.java │ │ ├── TrackingService.java │ │ ├── UDPGyroProviderClient.java │ │ └── math │ │ ├── MadgwickAHRS.java │ │ ├── Quaternion.java │ │ └── Vector3.java │ └── res │ ├── drawable │ ├── empty_tall_divider.xml │ ├── error_missing.xml │ ├── ic_baseline_computer_24.xml │ ├── ic_baseline_help_outline_24.xml │ ├── ic_baseline_home_24.xml │ └── not_missing.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── values-night │ └── themes.xml │ └── values │ └── colors.xml ├── moveTrackMobile ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── org │ │ └── moveTrack │ │ └── Mobile │ │ ├── MainActivity.java │ │ └── ui │ │ ├── ConnectFragment.java │ │ ├── GenericBindingFragment.java │ │ ├── SensorInfoFragment.java │ │ ├── debugLogFragment.java │ │ └── homeMenu.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_main.xml │ ├── fragment_connect.xml │ ├── fragment_debug_log.xml │ ├── fragment_home_menu.xml │ └── fragment_sensor_info.xml │ ├── menu │ └── main_menu.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── navigation │ └── nav_graph.xml │ ├── values-night │ └── themes.xml │ └── values │ ├── attrs.xml │ ├── sensors.xml │ ├── strings.xml │ └── themes.xml ├── moveTrackWear ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── org │ │ └── moveTrack │ │ └── Wear │ │ └── MainWear.java │ └── res │ ├── layout │ └── activity_mainwear.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── values-round │ └── strings.xml │ └── values │ ├── dimens.xml │ └── strings.xml └── settings.gradle /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | *.aab 5 | 6 | # Files for the ART/Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | # Uncomment the following line in case you need and you don't have the release build type files in your app 17 | # release/ 18 | 19 | # Gradle files 20 | .gradle/ 21 | build/ 22 | 23 | # Local configuration file (sdk path, etc) 24 | local.properties 25 | 26 | # Proguard folder generated by Eclipse 27 | proguard/ 28 | 29 | # Log Files 30 | *.log 31 | 32 | # Android Studio Navigation editor temp files 33 | .navigation/ 34 | 35 | # Android Studio captures folder 36 | captures/ 37 | 38 | # IntelliJ 39 | *.iml 40 | .idea/workspace.xml 41 | .idea/tasks.xml 42 | .idea/gradle.xml 43 | .idea/assetWizardSettings.xml 44 | .idea/dictionaries 45 | .idea/libraries 46 | # Android Studio 3 in .gitignore file. 47 | .idea/caches 48 | .idea/modules.xml 49 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 50 | .idea/navEditor.xml 51 | 52 | # Keystore files 53 | # Uncomment the following lines if you do not want to check your keystore files in. 54 | #*.jks 55 | #*.keystore 56 | 57 | # External native build folder generated in Android Studio 2.2 and later 58 | .externalNativeBuild 59 | 60 | # Google Services (e.g. APIs or Firebase) 61 | # google-services.json 62 | 63 | # Freeline 64 | freeline.py 65 | freeline/ 66 | freeline_project_description.json 67 | 68 | # fastlane 69 | fastlane/report.xml 70 | fastlane/Preview.html 71 | fastlane/screenshots 72 | fastlane/test_output 73 | fastlane/readme.md 74 | 75 | # Version control 76 | vcs.xml 77 | 78 | # lint 79 | lint/intermediates/ 80 | lint/generated/ 81 | lint/outputs/ 82 | lint/tmp/ 83 | # lint/reports/ 84 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | moveTrack -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 abb128 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![moveTrackLogo](https://github.com/Timocop/moveTrackVR/assets/22834512/b2c49134-e064-408c-800b-4175f71689d9) 2 | 3 | # owoTrackVR and moveTrackVR differences 4 | **moveTrackVR** utilizes un/calibrated sensor data instead of the (Game) Rotation Vector. This approach bypasses any automated calibration algorithms implemented by the phone. Many phones incorporate customized sensor algorithms, including drift correction and custom dead zones, which vary across different devices. Unfortunately, the (Game) Rotation Vector on most phones tends to be unreliable due to these customized algorithms. 5 | 6 | **moveTrackVR** is compatible with all **owoTrackVR** servers! 7 | 8 | ![image](https://github.com/Timocop/moveTrackVR/assets/22834512/666156d0-fbd3-49e3-a637-892e87fd83ef) 9 | 10 | ## 🎥 Video Demonstration with a Fairphone - First Edition 11 | [ExMoveTrack2.webm](https://github.com/Timocop/moveTrackVR/assets/22834512/5a5642d6-e2bc-4e15-8ace-cd44cdaf94ef) 12 | 13 | 14 | # moveTrackVR Android Application 15 | This application can be installed on an Android device to connect to [SlimeVR server](https://github.com/SlimeVR/SlimeVR-Server) or [PSMoveServiceEx - Virtual Device Manager](https://github.com/Timocop/PSMoveServiceEx-Virtual-Device-Manager) to replace devices orientation data. 16 | 17 | ## Device Compatibility 18 | All phones with gyroscope, accelerometer, and - optionally - magnetometer. 19 | 20 | This app may also be compatible with some Wear OS devices. 21 | 22 | ## Building 23 | The application can be easily built with Android Studio by opening the project in Android Studio. 24 | 25 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath "com.android.tools.build:gradle:4.1.1" 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | task clean(type: Delete) { 23 | delete rootProject.buildDir 24 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Nov 21 21:08:38 EET 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /moveTrackLib/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /moveTrackLib/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | } 4 | 5 | android { 6 | compileSdkVersion 30 7 | buildToolsVersion "30.0.2" 8 | 9 | defaultConfig { 10 | minSdkVersion 16 11 | targetSdkVersion 30 12 | versionCode 17 13 | versionName '9.5.5' 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_1_8 26 | targetCompatibility JavaVersion.VERSION_1_8 27 | } 28 | } 29 | 30 | dependencies { 31 | 32 | implementation 'androidx.appcompat:appcompat:1.1.0' 33 | implementation 'com.google.android.material:material:1.1.0' 34 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 35 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 36 | 37 | def nav_version = "2.3.1" 38 | 39 | // Java language implementation 40 | implementation "androidx.navigation:navigation-fragment:$nav_version" 41 | implementation "androidx.navigation:navigation-ui:$nav_version" 42 | 43 | // Feature module Support 44 | implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version" 45 | 46 | implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' 47 | } -------------------------------------------------------------------------------- /moveTrackLib/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /moveTrackLib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /moveTrackLib/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/java/org/moveTrack/AppStatus.java: -------------------------------------------------------------------------------- 1 | package org.moveTrack; 2 | 3 | import android.app.Activity; 4 | import android.app.Service; 5 | import android.content.Intent; 6 | import android.widget.TextView; 7 | 8 | import androidx.localbroadcastmanager.content.LocalBroadcastManager; 9 | 10 | public class AppStatus { 11 | public String statusi = ""; 12 | private Activity main_activity; 13 | private TextView statusLbl; 14 | private Service main_service; 15 | 16 | public AppStatus(Activity act, TextView tx) { 17 | main_activity = act; 18 | statusLbl = tx; 19 | main_service = null; 20 | } 21 | 22 | AppStatus(Service act) { 23 | main_service = act; 24 | update("Service Initialized"); 25 | } 26 | 27 | AppStatus() { 28 | main_activity = null; 29 | statusLbl = null; 30 | } 31 | 32 | public void update(String to) { 33 | statusi = to + "\n" + statusi; 34 | if (main_service != null) { 35 | Intent intent = new Intent("info-log"); 36 | intent.putExtra("message", to); 37 | LocalBroadcastManager.getInstance(main_service).sendBroadcast(intent); 38 | return; 39 | } 40 | if ((main_activity == null) || (statusLbl == null)) { 41 | return; 42 | } 43 | main_activity.runOnUiThread(() -> { 44 | statusLbl.setText(statusi); 45 | }); 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /moveTrackLib/src/main/java/org/moveTrack/AutoDiscoverer.java: -------------------------------------------------------------------------------- 1 | package org.moveTrack; 2 | 3 | import android.app.Activity; 4 | import android.content.DialogInterface; 5 | import android.content.Intent; 6 | import android.os.Build; 7 | import android.view.WindowManager; 8 | 9 | import androidx.appcompat.app.AlertDialog; 10 | 11 | import java.net.DatagramPacket; 12 | import java.net.DatagramSocket; 13 | import java.net.InetAddress; 14 | import java.net.SocketTimeoutException; 15 | import java.nio.ByteBuffer; 16 | 17 | 18 | public class AutoDiscoverer { 19 | static final int port = 35903; 20 | //static final String mcastAddress = "234.35.90.3"; 21 | // VPNs on Android will cause some issues with multicast, need to just broadcast :( 22 | static final String mcastAddress = "255.255.255.255"; 23 | public static boolean discoveryStillNecessary = true; 24 | public static boolean dialogShown = false; 25 | Activity act; 26 | ConfigSaver onConnect; 27 | public AutoDiscoverer(Activity c, ConfigSaver onYes) { 28 | act = c; 29 | onConnect = onYes; 30 | } 31 | 32 | private static DiscoveryResult attempt_discover_infoserver(int timeout) { 33 | try { 34 | DatagramSocket socket = new DatagramSocket(); 35 | { 36 | String handshake = "DISCOVERY"; 37 | DatagramPacket packet = new DatagramPacket(handshake.getBytes(), handshake.length(), 38 | InetAddress.getByName(mcastAddress), port); 39 | 40 | socket.send(packet); 41 | 42 | socket.setBroadcast(true); 43 | 44 | ByteBuffer buff = ByteBuffer.allocate(128); 45 | DatagramPacket pkt = new DatagramPacket(buff.array(), buff.capacity()); 46 | socket.setSoTimeout(timeout); 47 | socket.receive(pkt); 48 | 49 | 50 | String response = new String(buff.array()); 51 | 52 | 53 | for (String line : response.split("\n")) { 54 | try { 55 | int port = Integer.parseInt(line.split(":")[0]); 56 | String name = line.split(":")[1]; 57 | 58 | if (port > 0) { 59 | //discoveryStillNecessary = false; 60 | //alert(pkt.getAddress(), port, name); 61 | return DiscoveryResult.some(pkt.getAddress(), port, name); 62 | } 63 | } catch (NumberFormatException ignored) { 64 | } 65 | } 66 | } 67 | } catch (SocketTimeoutException ignored) { 68 | } catch (Exception e) { 69 | e.printStackTrace(); 70 | } 71 | 72 | return DiscoveryResult.none(); 73 | } 74 | 75 | public static DiscoveryResult attempt_discover(int timeout) { 76 | DiscoveryResult info_result = attempt_discover_infoserver(timeout); 77 | if (info_result.found) return info_result; 78 | 79 | return DiscoveryResult.none(); 80 | } 81 | 82 | private void plsDoConnect(InetAddress addr, int port) { 83 | Intent mainIntent = new Intent(act, TrackingService.class); 84 | mainIntent.putExtra("ipAddrTxt", addr.getHostAddress()); 85 | mainIntent.putExtra("port_no", port); 86 | 87 | ConfigSettings configSettings = onConnect.saveLoad(addr.getHostAddress(), port); 88 | 89 | mainIntent.putExtra("magnetometer", configSettings.magnetometerEnabled); 90 | mainIntent.putExtra("madgwickbeta", configSettings.madgwickBeta); 91 | mainIntent.putExtra("stabilization", configSettings.stabilization); 92 | mainIntent.putExtra("sensordata", configSettings.sensorData); 93 | mainIntent.putExtra("smartcorrection", configSettings.smartCorrection); 94 | 95 | // start service 96 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 97 | act.startForegroundService(mainIntent); 98 | } else { 99 | act.startService(mainIntent); 100 | } 101 | } 102 | 103 | private void alert(InetAddress addr, int port, String name) { 104 | dialogShown = true; 105 | act.runOnUiThread(() -> { 106 | try { 107 | AlertDialog.Builder alert = new AlertDialog.Builder(act); 108 | alert.setTitle("Automatic Discovery"); 109 | alert.setMessage("Connect to " + addr.getHostAddress() + ":" + String.valueOf(port) + " (" + name + ")?"); 110 | 111 | alert.setPositiveButton("Yes", new DialogInterface.OnClickListener() { 112 | public void onClick(DialogInterface dialog, int whichButton) { 113 | plsDoConnect(addr, port); 114 | } 115 | }); 116 | 117 | alert.setNegativeButton("No", new DialogInterface.OnClickListener() { 118 | public void onClick(DialogInterface dialog, int whichButton) { 119 | } 120 | }); 121 | 122 | alert.show(); 123 | } catch (WindowManager.BadTokenException ignored) { 124 | } 125 | }); 126 | } 127 | 128 | public void try_discover() { 129 | while (discoveryStillNecessary) { 130 | DiscoveryResult result = attempt_discover(5000); 131 | 132 | if (result.found) { 133 | discoveryStillNecessary = false; 134 | alert(result.server_address, result.port, result.name); 135 | break; 136 | } 137 | } 138 | } 139 | 140 | 141 | public interface ConfigSaver { 142 | ConfigSettings saveLoad(String ip_addr, int port); 143 | } 144 | 145 | public static class ConfigSettings { 146 | public boolean magnetometerEnabled; 147 | public float madgwickBeta; 148 | public boolean stabilization; 149 | public int sensorData; 150 | public boolean smartCorrection; 151 | } 152 | 153 | public static class DiscoveryResult { 154 | public boolean found; 155 | 156 | public InetAddress server_address; 157 | public int port; 158 | 159 | public String name; 160 | 161 | DiscoveryResult() { 162 | } 163 | 164 | public static DiscoveryResult none() { 165 | DiscoveryResult result = new DiscoveryResult(); 166 | result.found = false; 167 | 168 | return result; 169 | } 170 | 171 | public static DiscoveryResult some(InetAddress srv, int port, String name) { 172 | DiscoveryResult result = new DiscoveryResult(); 173 | result.found = true; 174 | result.server_address = srv; 175 | result.port = port; 176 | result.name = name; 177 | 178 | return result; 179 | } 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /moveTrackLib/src/main/java/org/moveTrack/GyroListener.java: -------------------------------------------------------------------------------- 1 | package org.moveTrack; 2 | 3 | import android.hardware.Sensor; 4 | import android.hardware.SensorEvent; 5 | import android.hardware.SensorEventListener; 6 | import android.hardware.SensorManager; 7 | import android.os.Handler; 8 | import android.util.Log; 9 | 10 | import org.moveTrack.math.MadgwickAHRS; 11 | import org.moveTrack.math.Quaternion; 12 | import org.moveTrack.math.Vector3; 13 | 14 | public class GyroListener implements SensorEventListener { 15 | private static final float NS2S = 1.0f / 1000000000.0f; 16 | 17 | private static final long MADGWICK_UPDATE_RATE_MS = 5; 18 | private static final long MADGWICK_SMARTRESET_MS = 2000; 19 | private static final float MADGWICK_SMARTRESET_GRAV_STABLE = 0.25f; 20 | private static final float MADGWICK_SMARTRESET_DEG_MIN = 5.0f; 21 | private static final float MADGWICK_SMARTRESET_DEG_MAX = 25.0f; 22 | private static final boolean MADGWICK_SMARTRESET_INSTANT = true; 23 | 24 | private static final float STABILIZATION_GYRO_MAX_DEG = 1.f; 25 | private static final float STABILIZATION_GYRO_MIN_DEG = 0.1f; 26 | 27 | public final static boolean OPTIMIZE_ROTATION_PACKET_SEND = true; 28 | 29 | 30 | UDPGyroProviderClient udpClient; 31 | private SensorManager sensorManager; 32 | private Sensor GyroSensor; 33 | private Sensor AccelSensor; 34 | private Sensor MagSensor; 35 | 36 | Quaternion rot_vec; 37 | private float[] gyro_vec; 38 | private float[] accel_vec; 39 | private float[] gav_vec; 40 | private float[] lin_accel_vec; 41 | private float[] mag_vec; 42 | 43 | private long last_send_timestamp; 44 | private long last_madgwick_timestamp; 45 | private long last_gyro_timestamp; 46 | private long elapsed_gyro_time; 47 | private int ROTATION_SENSOR_TYPE; 48 | private String sensor_type = ""; 49 | private MadgwickAHRS filter_madgwick; 50 | private MadgwickAHRS filter_madgwick_quick; 51 | private float madgwick_beta; 52 | private boolean madgwick_reset; 53 | 54 | private boolean use_stabilization; 55 | private boolean use_smartcorrection; 56 | private float smartcorrection_time; 57 | private int sensor_data; 58 | private Handler mHandler; 59 | 60 | GyroListener(SensorManager manager, UDPGyroProviderClient udpClient_v, AppStatus logger, AutoDiscoverer.ConfigSettings configSettings) throws Exception { 61 | sensorManager = manager; 62 | 63 | GyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); 64 | if (GyroSensor == null) { 65 | GyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED); 66 | if (GyroSensor == null) { 67 | logger.update("Gyroscope sensor could not be found, this data will be unavailable."); 68 | } else { 69 | logger.update("Uncalibrated gyroscope sensor found! Might cause sensor drift!"); 70 | } 71 | } else { 72 | logger.update("Gyroscope sensor found!"); 73 | } 74 | 75 | AccelSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 76 | if (AccelSensor == null) { 77 | AccelSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER_UNCALIBRATED); 78 | if (AccelSensor == null) { 79 | logger.update("Accelerometer sensor could not be found, this data will be unavailable."); 80 | } else { 81 | logger.update("Uncalibrated accelerometer sensor found! Might cause sensor drift!"); 82 | } 83 | } else { 84 | logger.update("Accelerometer sensor found!"); 85 | } 86 | 87 | if (configSettings.magnetometerEnabled) { 88 | MagSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); 89 | if (MagSensor == null) { 90 | logger.update("Magnetometer sensor could not be found, this data will be unavailable."); 91 | } else { 92 | logger.update("Magnetometer sensor found!"); 93 | } 94 | } else { 95 | logger.update("Magnetometer disabled by user!"); 96 | } 97 | 98 | madgwick_beta = configSettings.madgwickBeta; 99 | if (madgwick_beta < 0.0f) 100 | madgwick_beta = 0.0f; 101 | if (madgwick_beta > 1.0f) 102 | madgwick_beta = 1.0f; 103 | 104 | madgwick_reset = false; 105 | use_stabilization = configSettings.stabilization; 106 | sensor_data = configSettings.sensorData; 107 | use_smartcorrection = configSettings.smartCorrection; 108 | smartcorrection_time = 0.0f; 109 | 110 | rot_vec = new Quaternion(0.0,0.0,0.0,1.0); 111 | gyro_vec = new float[3]; 112 | accel_vec = new float[3]; 113 | gav_vec = new float[3]; 114 | lin_accel_vec = new float[3]; 115 | mag_vec = new float[3]; 116 | last_gyro_timestamp = 0; 117 | last_madgwick_timestamp = 0; 118 | last_send_timestamp = 0; 119 | elapsed_gyro_time = 0; 120 | filter_madgwick = new MadgwickAHRS(0.0f, madgwick_beta); 121 | filter_madgwick_quick = new MadgwickAHRS(0.0f, 0.2f); 122 | 123 | udpClient = udpClient_v; 124 | udpClient.set_listener(this); 125 | } 126 | 127 | public void register_listeners() { 128 | mHandler = new Handler(); 129 | sensorManager.registerListener(this, GyroSensor, SensorManager.SENSOR_DELAY_FASTEST, mHandler); 130 | sensorManager.registerListener(this, AccelSensor, SensorManager.SENSOR_DELAY_FASTEST, mHandler); 131 | sensorManager.registerListener(this, MagSensor, SensorManager.SENSOR_DELAY_FASTEST, mHandler); 132 | } 133 | 134 | public void stop() { 135 | sensorManager.unregisterListener(this); 136 | } 137 | 138 | @Override 139 | public void onSensorChanged(SensorEvent event) { 140 | if (event.timestamp == 0) 141 | return; 142 | 143 | if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE 144 | || event.sensor.getType() == Sensor.TYPE_GYROSCOPE_UNCALIBRATED) { 145 | float[] vec = new float[3]; 146 | vec[0] = event.values[0]; 147 | vec[1] = event.values[1]; 148 | vec[2] = event.values[2]; 149 | 150 | if (last_gyro_timestamp != 0) { 151 | final float gyroDelta = (event.timestamp - last_gyro_timestamp) * NS2S; 152 | 153 | gyro_vec[0] += vec[0] * gyroDelta; 154 | gyro_vec[1] += vec[1] * gyroDelta; 155 | gyro_vec[2] += vec[2] * gyroDelta; 156 | 157 | if ((event.timestamp - elapsed_gyro_time) * NS2S > MADGWICK_UPDATE_RATE_MS / 1000.0f) { 158 | float totalTime = (event.timestamp - elapsed_gyro_time) * NS2S; 159 | 160 | gyro_vec[0] /= totalTime; 161 | gyro_vec[1] /= totalTime; 162 | gyro_vec[2] /= totalTime; 163 | 164 | switch(sensor_data) { 165 | case 0: 166 | // Send nothing 167 | break; 168 | case 1: 169 | // Send minimal (compat owoTrack servers) 170 | udpClient.provide_owo_gyro(event.timestamp, gyro_vec); 171 | break; 172 | case 2: 173 | if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) { 174 | udpClient.provide_gyro(event.timestamp, gyro_vec); 175 | } 176 | else { 177 | udpClient.provide_uncalib_gyro(event.timestamp, gyro_vec); 178 | } 179 | break; 180 | } 181 | 182 | updateMadgwick(event.timestamp); 183 | elapsed_gyro_time = event.timestamp; 184 | 185 | gyro_vec[0] = 0.f; 186 | gyro_vec[1] = 0.f; 187 | gyro_vec[2] = 0.f; 188 | } 189 | } 190 | 191 | last_gyro_timestamp = event.timestamp; 192 | 193 | } else if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER 194 | || event.sensor.getType() == Sensor.TYPE_ACCELEROMETER_UNCALIBRATED) { 195 | float[] vec = new float[3]; 196 | vec[0] = event.values[0]; 197 | vec[1] = event.values[1]; 198 | vec[2] = event.values[2]; 199 | 200 | { 201 | final float alpha = 0.8f; 202 | 203 | gav_vec[0] = lowpass_filter(alpha, gav_vec[0], vec[0]); 204 | gav_vec[1] = lowpass_filter(alpha, gav_vec[1], vec[1]); 205 | gav_vec[2] = lowpass_filter(alpha, gav_vec[2], vec[2]); 206 | 207 | lin_accel_vec[0] = vec[0] - gav_vec[0]; 208 | lin_accel_vec[1] = vec[1] - gav_vec[1]; 209 | lin_accel_vec[2] = vec[2] - gav_vec[2]; 210 | } 211 | 212 | accel_vec = vec; 213 | 214 | switch(sensor_data) { 215 | case 0: 216 | // Send nothing 217 | break; 218 | case 1: 219 | // Send minimal (compat owoTrack servers) 220 | udpClient.provide_owo_accel(event.timestamp, lin_accel_vec); 221 | break; 222 | case 2: 223 | if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { 224 | udpClient.provide_accel(event.timestamp, vec); 225 | } 226 | else { 227 | udpClient.provide_uncalib_accel(event.timestamp, vec); 228 | } 229 | break; 230 | } 231 | 232 | } else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD 233 | || event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED) { 234 | float[] vec = new float[3]; 235 | vec[0] = event.values[0]; 236 | vec[1] = event.values[1]; 237 | vec[2] = event.values[2]; 238 | 239 | mag_vec = vec; 240 | 241 | switch(sensor_data) { 242 | case 0: 243 | // Send nothing 244 | break; 245 | case 1: 246 | // Send minimal (compat owoTrack servers) 247 | break; 248 | case 2: 249 | if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) { 250 | udpClient.provide_mag(event.timestamp, vec); 251 | } 252 | else { 253 | udpClient.provide_uncalib_mag(event.timestamp, vec); 254 | } 255 | break; 256 | } 257 | } 258 | } 259 | 260 | private void updateMadgwick(long timeStamp) { 261 | if (last_madgwick_timestamp != 0) { 262 | final float deltaTime = (timeStamp - last_madgwick_timestamp) * NS2S; 263 | 264 | { // Main adgwick 265 | float beta; 266 | 267 | if (madgwick_reset) { 268 | beta = 0.9f; 269 | } 270 | else { 271 | beta = getAdaptiveBeta(deltaTime); 272 | } 273 | 274 | final float old_beta = filter_madgwick.getBeta(); 275 | final float beta_smooth = lowpass_filter(0.1f, old_beta, beta); 276 | 277 | filter_madgwick.setBeta(beta_smooth); 278 | filter_madgwick.setSamplePeriod(deltaTime); 279 | 280 | if (MagSensor != null) { 281 | filter_madgwick.update( 282 | gyro_vec[0], gyro_vec[1], gyro_vec[2], 283 | accel_vec[0], accel_vec[1], accel_vec[2], 284 | mag_vec[0], mag_vec[1], mag_vec[2]); 285 | } else { 286 | filter_madgwick.update( 287 | gyro_vec[0], gyro_vec[1], gyro_vec[2], 288 | accel_vec[0], accel_vec[1], accel_vec[2]); 289 | } 290 | } 291 | 292 | if (use_smartcorrection) { // Quick Madgwick 293 | filter_madgwick_quick.setSamplePeriod(deltaTime); 294 | 295 | if (MagSensor != null) { 296 | filter_madgwick_quick.update( 297 | gyro_vec[0], gyro_vec[1], gyro_vec[2], 298 | accel_vec[0], accel_vec[1], accel_vec[2], 299 | mag_vec[0], mag_vec[1], mag_vec[2]); 300 | } else { 301 | filter_madgwick_quick.update( 302 | gyro_vec[0], gyro_vec[1], gyro_vec[2], 303 | accel_vec[0], accel_vec[1], accel_vec[2]); 304 | } 305 | } 306 | 307 | float[] quat = filter_madgwick.getQuaternion(); 308 | float[] quat2 = filter_madgwick_quick.getQuaternion(); 309 | Quaternion swapQuat = new Quaternion( 310 | quat[1], 311 | quat[2], 312 | quat[3], 313 | quat[0] 314 | ); 315 | Quaternion swapQuat2 = new Quaternion( 316 | quat2[1], 317 | quat2[2], 318 | quat2[3], 319 | quat2[0] 320 | ); 321 | 322 | float accel_g = 1.0f + (float)Math.sqrt( 323 | lin_accel_vec[0] * lin_accel_vec[0] + 324 | lin_accel_vec[1] * lin_accel_vec[1] + 325 | lin_accel_vec[2] * lin_accel_vec[2]); 326 | 327 | // If angle difference is too big, attempt to quick reset. 328 | if (use_smartcorrection) { 329 | if (madgwick_reset) { 330 | // Stop when deviation is too small 331 | if (Math.abs(calculateQuaternionAngle(swapQuat, swapQuat2)) < MADGWICK_SMARTRESET_DEG_MIN) { 332 | madgwick_reset = false; 333 | } 334 | 335 | // Stop if gravity is unstable 336 | if (accel_g > MADGWICK_SMARTRESET_GRAV_STABLE && accel_g < 1.f + (1.f - MADGWICK_SMARTRESET_GRAV_STABLE)) { 337 | madgwick_reset = false; 338 | } 339 | } 340 | else { 341 | if (Math.abs(calculateQuaternionAngle(swapQuat, swapQuat2)) > MADGWICK_SMARTRESET_DEG_MAX) { 342 | // Make sure smart madgwick its kind of stable 343 | if (accel_g > MADGWICK_SMARTRESET_GRAV_STABLE && accel_g < 1.f + (1.f - MADGWICK_SMARTRESET_GRAV_STABLE)) { 344 | smartcorrection_time += deltaTime; 345 | 346 | if (smartcorrection_time > (MADGWICK_SMARTRESET_MS / 1000.f)) { 347 | if (MADGWICK_SMARTRESET_INSTANT) { 348 | swapQuat = new Quaternion(swapQuat2); 349 | smartcorrection_time = 0.0f; 350 | 351 | filter_madgwick.setQuaternion( 352 | (float)swapQuat.getW(), 353 | (float)swapQuat.getX(), 354 | (float)swapQuat.getY(), 355 | (float)swapQuat.getZ()); 356 | } 357 | else { 358 | madgwick_reset = true; 359 | } 360 | } 361 | } 362 | else { 363 | if (smartcorrection_time > 0.0f) 364 | smartcorrection_time = 0.0f; 365 | } 366 | } 367 | else { 368 | if (smartcorrection_time > 0.0f) 369 | smartcorrection_time = 0.0f; 370 | } 371 | } 372 | } 373 | else { 374 | if (madgwick_reset) 375 | madgwick_reset = false; 376 | } 377 | 378 | // For some reason ROTATION_VECTOR quaternion and MADGWICK quaternion have a 90° yaw difference. 379 | // Add a 90° offset to make it compatible with owoTrack servers. 380 | swapQuat = new Quaternion(new Vector3(0.f, 0.f, 1.f), 90.f * (Math.PI / 180.f)).mulThis(swapQuat); 381 | float[] newQuat = new float[4]; 382 | newQuat[0] = (float) swapQuat.getX(); 383 | newQuat[1] = (float) swapQuat.getY(); 384 | newQuat[2] = (float) swapQuat.getZ(); 385 | newQuat[3] = (float) swapQuat.getW(); 386 | 387 | // Keep alive 388 | final float last_send = (timeStamp - last_send_timestamp) * NS2S; 389 | 390 | if (!OPTIMIZE_ROTATION_PACKET_SEND || last_send > 0.5f || !quat_equal(swapQuat, rot_vec)) { 391 | udpClient.provide_rot(timeStamp, newQuat); 392 | last_send_timestamp = timeStamp; 393 | 394 | rot_vec = swapQuat; 395 | } 396 | } 397 | 398 | last_madgwick_timestamp = timeStamp; 399 | } 400 | 401 | private boolean quat_equal(Quaternion q1, Quaternion q2) 402 | { 403 | final float EQUAL_TOLERANCE = 0.0001f; 404 | 405 | if (Math.abs(q1.getX() - q2.getX()) < EQUAL_TOLERANCE && 406 | Math.abs(q1.getY() - q2.getY()) < EQUAL_TOLERANCE && 407 | Math.abs(q1.getZ() - q2.getZ()) < EQUAL_TOLERANCE && 408 | Math.abs(q1.getW() - q2.getW()) < EQUAL_TOLERANCE) { 409 | return true; 410 | } 411 | 412 | return false; 413 | } 414 | 415 | private float getAdaptiveBeta(float deltaTime) { 416 | float beta = madgwick_beta; 417 | 418 | if (!use_stabilization) { 419 | // Do not allow no drift correction 420 | if (beta < 0.01f) 421 | beta = 0.01f; 422 | 423 | return beta; 424 | } 425 | 426 | float gyro_sq = (float) Math.sqrt(gyro_vec[0] * gyro_vec[0] + gyro_vec[1] * gyro_vec[1] + gyro_vec[2] * gyro_vec[2]) * deltaTime; 427 | gyro_sq = (float) (gyro_sq * (180.f / Math.PI)); 428 | 429 | float beta_multi = ((gyro_sq - STABILIZATION_GYRO_MIN_DEG) / STABILIZATION_GYRO_MAX_DEG); 430 | if (beta_multi < 0.0f) 431 | beta_multi = 0.f; 432 | if (beta_multi > 1.0f) 433 | beta_multi = 1.f; 434 | 435 | return (beta * beta_multi); 436 | } 437 | 438 | 439 | private float lowpass_filter(float alpha, float old_val, float new_val) 440 | { 441 | return alpha * new_val + (1.f - alpha) * old_val; 442 | } 443 | 444 | private double calculateQuaternionAngle(Quaternion q1, Quaternion q2) { 445 | q1.normalizeThis(); 446 | q2.normalizeThis(); 447 | 448 | float dotProduct = (float)(q1.getX() * q2.getX() + q1.getY() * q2.getY() + q1.getZ() * q2.getZ() + q1.getW() * q2.getW()); 449 | float angle = (float)(2 * Math.acos(Math.abs(dotProduct))); 450 | 451 | return Math.toDegrees(angle); 452 | } 453 | 454 | @Override 455 | public void onAccuracyChanged(Sensor sensor, int accuracy) { 456 | //magnetometerAccuracy = accuracy; 457 | //if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { 458 | // System.out.println("@@@ Accuracy for " + sensor.getStringType() + " : " + String.valueOf(accuracy)); 459 | //} 460 | 461 | //udpClient.provide_accuracy(accuracy); 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /moveTrackLib/src/main/java/org/moveTrack/Handshaker.java: -------------------------------------------------------------------------------- 1 | package org.moveTrack; 2 | 3 | import java.io.IOException; 4 | import java.net.DatagramPacket; 5 | import java.net.DatagramSocket; 6 | import java.net.InetAddress; 7 | import java.net.PortUnreachableException; 8 | import java.net.SocketTimeoutException; 9 | import java.nio.ByteBuffer; 10 | import java.util.Arrays; 11 | 12 | class HandshakeFailException extends Exception { 13 | public HandshakeFailException(String s) { 14 | super(s); 15 | } 16 | } 17 | 18 | public class Handshaker { 19 | // Android doesn't allow getting MAC address anymore, so we fake one 20 | // NOTE: A random mac gets generated and set by MainActivity using the 21 | // setMac method below, this is not a constant value. It's random per-device 22 | private static byte[] pseudo_mac = new byte[]{0, 69, 0, 0, 0, 0}; 23 | 24 | public static void setMac(long from) { 25 | ByteBuffer from_b = ByteBuffer.allocate(Long.BYTES); 26 | from_b.putLong(from); 27 | 28 | byte[] bytes = from_b.array(); 29 | 30 | for (int i = 0; i < Math.min(bytes.length, pseudo_mac.length); i++) { 31 | pseudo_mac[i] = bytes[i]; 32 | } 33 | } 34 | 35 | private static byte[] getMac(){ 36 | return pseudo_mac; 37 | } 38 | 39 | 40 | public static void insert_slime_info(ByteBuffer buff) { 41 | final int boardType = 13; 42 | final int imuType = 0; 43 | final int mcuType = 3; 44 | 45 | final int[] imuInfo = {0, 0, 0}; 46 | 47 | final int firmwareBuild = 8; 48 | 49 | final byte[] firmware = {'o', 'w', 'o', 'T', 'r', 'a', 'c', 'k', '8'}; // 9 bytes 50 | 51 | buff.putInt(boardType); // 4 bytes 52 | buff.putInt(imuType); // 4 bytes 53 | buff.putInt(mcuType); // 4 bytes 54 | 55 | // 4 * 3 = 12 bytes 56 | for (int info : imuInfo) buff.putInt(info); 57 | 58 | buff.putInt(firmwareBuild); // 4 bytes 59 | 60 | buff.put((byte) firmware.length); // 1 bytes 61 | buff.put(firmware); 62 | 63 | byte[] mac = getMac(); 64 | assert (mac.length == 6); 65 | buff.put(mac); // 6 bytes 66 | 67 | buff.put((byte) 0xFF); // top it off 68 | } 69 | 70 | public static HandshakeResult try_handshake(DatagramSocket socket, InetAddress ip, int port) throws HandshakeFailException { 71 | //if(ip.toString().endsWith(".255")) return true; 72 | 73 | byte[] buffer = new byte[64]; 74 | DatagramPacket handshake_receive = new DatagramPacket(buffer, 64); 75 | 76 | 77 | int tries = 0; 78 | boolean tryBroadcast = false; 79 | while (true) { 80 | int len = 12; 81 | 82 | // if the user is running an old version of owoTrackVR driver, 83 | // recvfrom() will fail as the max packet length the old driver 84 | // supported was around 28 bytes. to maintain backwards 85 | // compatibility the slime extensions are not sent after a 86 | // certain number of failures 87 | boolean sendSlimeExtensions = (tries < 10); 88 | if (sendSlimeExtensions) len += 36 + 9; 89 | 90 | ByteBuffer handshake_buff = ByteBuffer.allocate(len); 91 | handshake_buff.putInt(3); 92 | handshake_buff.putLong(0); 93 | 94 | if (sendSlimeExtensions) { 95 | try { 96 | insert_slime_info(handshake_buff); // 36 extra bytes 97 | } catch (Exception e) { 98 | e.printStackTrace(); 99 | } 100 | } 101 | 102 | tries++; 103 | tryBroadcast = tryBroadcast || (tries > 4); 104 | if (tries > 12) { 105 | throw new HandshakeFailException("Connection failed. Ensure IP and port are correct, that the server is running and not blocked by Windows Firewall (try changing your network type to private in Windows) or blocked by router, and that you're connected to the same network (you may need to disable Mobile Data)"); 106 | } 107 | 108 | try { 109 | if (socket == null) throw new HandshakeFailException("Socket is null!"); 110 | 111 | if (!tryBroadcast) { 112 | socket.send(new DatagramPacket(handshake_buff.array(), len, ip, port)); 113 | } else { 114 | socket.disconnect(); 115 | socket.setBroadcast(true); 116 | InetAddress broadcast_address = InetAddress.getByName("255.255.255.255"); 117 | socket.send(new DatagramPacket(handshake_buff.array(), len, broadcast_address, 6969)); 118 | } 119 | 120 | try { 121 | socket.setSoTimeout(250); 122 | socket.receive(handshake_receive); 123 | 124 | if (buffer[0] != 3) { 125 | throw new HandshakeFailException("Handshake failed, the server did not respond correctly. Ensure everything is up-to-date and that the port is correct."); 126 | } 127 | 128 | String result = new String( 129 | Arrays.copyOfRange(buffer, 1, 64), 130 | "ASCII"); 131 | 132 | result = result.substring(0, result.indexOf(0)); 133 | 134 | if (!result.startsWith("Hey OVR =D")) { 135 | throw new HandshakeFailException("Handshake failed, the server did not respond correctly in the header. Ensure everything is up-to-date and that the port is correct"); 136 | } 137 | 138 | int version = -1; 139 | try { 140 | version = Integer.parseInt(result.substring(11)); 141 | } catch (NumberFormatException e) { 142 | throw new HandshakeFailException("Handshake failed, server did not send an int"); 143 | } 144 | 145 | if (version != UDPGyroProviderClient.CURRENT_VERSION) { 146 | throw new HandshakeFailException("Handshake failed, mismatching version" 147 | + "\nServer version: " + String.valueOf(version) 148 | + "\nClient version: " + String.valueOf(UDPGyroProviderClient.CURRENT_VERSION) 149 | + "\nPlease make sure everything is up to date."); 150 | } 151 | 152 | boolean had_to_rediscover = (handshake_receive.getAddress() != ip) || (handshake_receive.getPort() != port); 153 | if (tryBroadcast && had_to_rediscover) { 154 | socket.connect(handshake_receive.getAddress(), handshake_receive.getPort()); 155 | socket.setBroadcast(false); 156 | } 157 | return HandshakeResult.some(handshake_receive.getAddress(), handshake_receive.getPort(), had_to_rediscover); 158 | } catch (SocketTimeoutException e) { 159 | continue; 160 | } 161 | } catch (PortUnreachableException e) { 162 | //throw new HandshakeFailException("Port is unreachable. Ensure that you've entered the correct IP and port, that the server is running and that you're on the same wifi network as the computer."); 163 | tryBroadcast = true; 164 | continue; 165 | } catch (IOException e) { 166 | throw new HandshakeFailException("Handshake IO exception: " + e.toString()); 167 | } 168 | } 169 | } 170 | 171 | public static class HandshakeResult { 172 | public boolean success; 173 | 174 | public boolean had_to_rediscover; 175 | public InetAddress server_address; 176 | public int port; 177 | 178 | HandshakeResult() { 179 | } 180 | 181 | public static Handshaker.HandshakeResult none() { 182 | Handshaker.HandshakeResult result = new Handshaker.HandshakeResult(); 183 | result.success = false; 184 | 185 | return result; 186 | } 187 | 188 | public static Handshaker.HandshakeResult some(InetAddress srv, int port, boolean had_to_rediscover) { 189 | Handshaker.HandshakeResult result = new Handshaker.HandshakeResult(); 190 | result.success = true; 191 | result.server_address = srv; 192 | result.port = port; 193 | result.had_to_rediscover = had_to_rediscover; 194 | 195 | return result; 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /moveTrackLib/src/main/java/org/moveTrack/TrackingService.java: -------------------------------------------------------------------------------- 1 | package org.moveTrack; 2 | 3 | import android.app.Notification; 4 | import android.app.NotificationChannel; 5 | import android.app.NotificationManager; 6 | import android.app.PendingIntent; 7 | import android.app.Service; 8 | import android.content.BroadcastReceiver; 9 | import android.content.Context; 10 | import android.content.Intent; 11 | import android.content.IntentFilter; 12 | import android.hardware.SensorManager; 13 | import android.net.ConnectivityManager; 14 | import android.net.Network; 15 | import android.net.NetworkCapabilities; 16 | import android.net.NetworkRequest; 17 | import android.os.Binder; 18 | import android.os.Build; 19 | import android.os.Bundle; 20 | import android.os.IBinder; 21 | import android.os.PowerManager; 22 | import android.os.VibrationEffect; 23 | import android.os.Vibrator; 24 | 25 | import androidx.core.app.NotificationCompat; 26 | import androidx.localbroadcastmanager.content.LocalBroadcastManager; 27 | 28 | public class TrackingService extends Service { 29 | private static TrackingService instance = null; 30 | private final IBinder localBinder = new TrackingBinder(); 31 | Runnable on_death = () -> { 32 | stopSelf(); 33 | LocalBroadcastManager.getInstance(TrackingService.this).sendBroadcast(new Intent("pls-let-me-die")); 34 | }; 35 | boolean ignoreWifi = false; 36 | ConnectivityManager.NetworkCallback callback = null; 37 | private UDPGyroProviderClient client; 38 | //private long last_screen_time = 0; 39 | BroadcastReceiver recenterReceiver = new BroadcastReceiver() { 40 | @Override 41 | public void onReceive(Context context, Intent intent) { 42 | if (client != null) 43 | client.button_pushed(); 44 | } 45 | }; 46 | private GyroListener listener; 47 | private PowerManager.WakeLock wakeLock; 48 | private String ip_address; 49 | private AppStatus stat; 50 | BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { 51 | @Override 52 | public void onReceive(Context context, Intent intent) { 53 | if (stat != null) stat.update("Killed."); 54 | if (on_death != null) on_death.run(); 55 | } 56 | }; 57 | 58 | public static boolean isInstanceCreated() { 59 | return instance != null; 60 | } 61 | 62 | @Override 63 | public void onCreate() { 64 | instance = this; 65 | } 66 | 67 | @Override 68 | public int onStartCommand(Intent intent, int flags, int startId) { 69 | if ((intent == null) || (intent.getExtras() == null)) { 70 | foregroundstuff(); 71 | return START_STICKY; 72 | } 73 | Bundle data = intent.getExtras(); 74 | ip_address = data.getString("ipAddrTxt"); 75 | int port_no = data.getInt("port_no"); 76 | 77 | AutoDiscoverer.ConfigSettings cfgSettings = new AutoDiscoverer.ConfigSettings(); 78 | cfgSettings.magnetometerEnabled = data.getBoolean("magnetometer"); 79 | cfgSettings.madgwickBeta = data.getFloat("madgwickbeta"); 80 | cfgSettings.stabilization = data.getBoolean("stabilization"); 81 | cfgSettings.sensorData = data.getInt("sensordata"); 82 | cfgSettings.smartCorrection = data.getBoolean("smartcorrection"); 83 | 84 | System.out.println("Start command"); 85 | foregroundstuff(); 86 | 87 | stat = new AppStatus((Service) this); 88 | client = new UDPGyroProviderClient(stat, this); 89 | try { 90 | listener = new GyroListener((SensorManager) getSystemService(Context.SENSOR_SERVICE), client, stat, cfgSettings); 91 | } catch (Exception e) { 92 | stat.update("on GyroListener: " + e.toString()); 93 | on_death.run(); 94 | return START_STICKY; 95 | } 96 | 97 | 98 | try { 99 | Thread thread = new Thread(() -> { 100 | if (!client.setTgt(ip_address, port_no)) { 101 | on_death.run(); 102 | } else { 103 | client.connect(on_death); 104 | if (client == null || !client.isConnected()) { 105 | on_death.run(); 106 | } 107 | } 108 | }); 109 | thread.start(); 110 | } catch (OutOfMemoryError err) { 111 | stat.update("Out of memory error when trying to spawn thread"); 112 | on_death.run(); 113 | return START_STICKY; 114 | } 115 | 116 | listener.register_listeners(); 117 | 118 | String tag = "owoTrackVRSync::BackgroundTrackingSync"; 119 | 120 | if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M && Build.MANUFACTURER.equals("Huawei")) { 121 | tag = "LocationManagerService"; 122 | } 123 | 124 | PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); 125 | wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag); 126 | wakeLock.acquire(); 127 | 128 | register_recenter_yaw(); 129 | 130 | ignoreWifi = false; 131 | lockWifi(); 132 | 133 | return START_STICKY; 134 | } 135 | 136 | private void lockWifi() { 137 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { 138 | try { 139 | ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 140 | 141 | if (callback == null) { 142 | callback = new ConnectivityManager.NetworkCallback() { 143 | public void onAvailable(Network network) { 144 | if (ignoreWifi) return; 145 | 146 | super.onAvailable(network); 147 | 148 | try { 149 | connectivityManager.bindProcessToNetwork(network); 150 | } catch (SecurityException ignored) { 151 | } 152 | } 153 | }; 154 | } 155 | 156 | connectivityManager.requestNetwork( 157 | new NetworkRequest.Builder().addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build(), 158 | callback 159 | ); 160 | } catch (Exception ignored) { 161 | } 162 | } 163 | } 164 | 165 | private void unlockWifi() { 166 | ignoreWifi = true; 167 | 168 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { 169 | 170 | try { 171 | ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 172 | connectivityManager.bindProcessToNetwork(null); 173 | if (callback != null) 174 | connectivityManager.unregisterNetworkCallback(callback); 175 | callback = null; 176 | } catch (Exception ignored) { 177 | } 178 | 179 | } 180 | } 181 | 182 | @Override 183 | public IBinder onBind(Intent intent) { 184 | 185 | return localBinder; 186 | } 187 | 188 | @Override 189 | public void onRebind(Intent intent) { 190 | super.onRebind(intent); 191 | } 192 | 193 | @Override 194 | public boolean onUnbind(Intent intent) { 195 | return super.onUnbind(intent); 196 | } 197 | 198 | public String getLog() { 199 | if (stat == null) { 200 | return "Service not started"; 201 | } 202 | return stat.statusi; 203 | } 204 | 205 | public boolean is_running() { 206 | return stat != null; 207 | } 208 | 209 | @Override 210 | public void onDestroy() { 211 | instance = null; 212 | LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent("cya-ded")); 213 | unlockWifi(); 214 | if (listener != null) { 215 | Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); 216 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 217 | v.vibrate(VibrationEffect.createOneShot((long) (200), (int) (255))); 218 | } else { 219 | v.vibrate(200); 220 | } 221 | listener.stop(); 222 | wakeLock.release(); 223 | 224 | unregisterReceiver(broadcastReceiver); 225 | unregisterReceiver(recenterReceiver); 226 | 227 | if (client != null) { 228 | client.stop(); 229 | client = null; 230 | } 231 | } 232 | 233 | } 234 | 235 | private void register_recenter_yaw() { 236 | IntentFilter screenStateFilter = new IntentFilter(); 237 | screenStateFilter.addAction(Intent.ACTION_SCREEN_ON); 238 | //screenStateFilter.addAction(Intent.ACTION_SCREEN_OFF); 239 | registerReceiver(recenterReceiver, screenStateFilter); 240 | } 241 | 242 | // stupid foreground stuff 243 | private void foregroundstuff() { 244 | NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 245 | 246 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 247 | nm.createNotificationChannel(new NotificationChannel("NOTIFICATION_CHANNEL_ID", "Foreground Service", NotificationManager.IMPORTANCE_DEFAULT)); 248 | } 249 | 250 | registerReceiver(broadcastReceiver, new IntentFilter("kill-ze-service")); 251 | 252 | Intent intent = new Intent("kill-ze-service"); 253 | PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_IMMUTABLE); 254 | 255 | Notification notification = new NotificationCompat.Builder(this, "NOTIFICATION_CHANNEL_ID") 256 | .setContentTitle("moveTrackVR") 257 | .setTicker("moveTrackVR") 258 | .setContentText("moveTrack service is running") 259 | .setSmallIcon(R.mipmap.ic_launcher) 260 | .addAction(0, "Stop", pendingIntent) 261 | .setOngoing(true).build(); 262 | 263 | startForeground(1001, notification); 264 | } 265 | 266 | public class TrackingBinder extends Binder { 267 | public TrackingService getService() { 268 | return TrackingService.this; 269 | } 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /moveTrackLib/src/main/java/org/moveTrack/math/MadgwickAHRS.java: -------------------------------------------------------------------------------- 1 | package org.moveTrack.math; 2 | 3 | 4 | public class MadgwickAHRS { 5 | 6 | private float samplePeriod; 7 | private float beta; 8 | private float[] quaternion; 9 | 10 | public MadgwickAHRS(float samplePeriod) { 11 | this(samplePeriod, 1f); 12 | } 13 | 14 | public MadgwickAHRS(float samplePeriod, float beta) { 15 | this.samplePeriod = samplePeriod; 16 | this.beta = beta; 17 | this.quaternion = new float[]{1f, 0f, 0f, 0f}; 18 | } 19 | 20 | public float getSamplePeriod() { 21 | return samplePeriod; 22 | } 23 | 24 | public void setSamplePeriod(float samplePeriod) { 25 | this.samplePeriod = samplePeriod; 26 | } 27 | 28 | public float getBeta() { 29 | return beta; 30 | } 31 | 32 | public void setBeta(float beta) { 33 | this.beta = beta; 34 | } 35 | 36 | public float[] getQuaternion() { 37 | return quaternion; 38 | } 39 | public void setQuaternion(float w, float x, float y, float z) { 40 | quaternion[0] = w; 41 | quaternion[1] = x; 42 | quaternion[2] = y; 43 | quaternion[3] = z; 44 | } 45 | 46 | public void update(float gx, float gy, float gz, float ax, float ay, 47 | float az, float mx, float my, float mz) { 48 | float q1 = quaternion[0], q2 = quaternion[1], q3 = quaternion[2], q4 = quaternion[3]; // short 49 | 50 | float norm; 51 | float hx, hy, _2bx, _2bz; 52 | float s1, s2, s3, s4; 53 | float qDot1, qDot2, qDot3, qDot4; 54 | 55 | // Auxiliary variables to avoid repeated arithmetic 56 | float _2q1mx; 57 | float _2q1my; 58 | float _2q1mz; 59 | float _2q2mx; 60 | float _4bx; 61 | float _4bz; 62 | float _2q1 = 2f * q1; 63 | float _2q2 = 2f * q2; 64 | float _2q3 = 2f * q3; 65 | float _2q4 = 2f * q4; 66 | float _2q1q3 = 2f * q1 * q3; 67 | float _2q3q4 = 2f * q3 * q4; 68 | float q1q1 = q1 * q1; 69 | float q1q2 = q1 * q2; 70 | float q1q3 = q1 * q3; 71 | float q1q4 = q1 * q4; 72 | float q2q2 = q2 * q2; 73 | float q2q3 = q2 * q3; 74 | float q2q4 = q2 * q4; 75 | float q3q3 = q3 * q3; 76 | float q3q4 = q3 * q4; 77 | float q4q4 = q4 * q4; 78 | 79 | // Normalise accelerometer measurement 80 | norm = (float) Math.sqrt(ax * ax + ay * ay + az * az); 81 | if (norm == 0f) 82 | return; // handle NaN 83 | norm = 1 / norm; // use reciprocal for division 84 | ax *= norm; 85 | ay *= norm; 86 | az *= norm; 87 | 88 | // Normalise magnetometer measurement 89 | norm = (float) Math.sqrt(mx * mx + my * my + mz * mz); 90 | if (norm == 0f) 91 | return; // handle NaN 92 | norm = 1 / norm; // use reciprocal for division 93 | mx *= norm; 94 | my *= norm; 95 | mz *= norm; 96 | 97 | // Reference direction of Earth's magnetic field 98 | _2q1mx = 2f * q1 * mx; 99 | _2q1my = 2f * q1 * my; 100 | _2q1mz = 2f * q1 * mz; 101 | _2q2mx = 2f * q2 * mx; 102 | hx = mx * q1q1 - _2q1my * q4 + _2q1mz * q3 + mx * q2q2 + _2q2 * my * q3 103 | + _2q2 * mz * q4 - mx * q3q3 - mx * q4q4; 104 | hy = _2q1mx * q4 + my * q1q1 - _2q1mz * q2 + _2q2mx * q3 - my * q2q2 105 | + my * q3q3 + _2q3 * mz * q4 - my * q4q4; 106 | _2bx = (float) Math.sqrt(hx * hx + hy * hy); 107 | _2bz = -_2q1mx * q3 + _2q1my * q2 + mz * q1q1 + _2q2mx * q4 - mz * q2q2 108 | + _2q3 * my * q4 - mz * q3q3 + mz * q4q4; 109 | _4bx = 2f * _2bx; 110 | _4bz = 2f * _2bz; 111 | 112 | // Gradient decent algorithm corrective step 113 | s1 = -_2q3 * (2f * q2q4 - _2q1q3 - ax) + _2q2 114 | * (2f * q1q2 + _2q3q4 - ay) - _2bz * q3 115 | * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) 116 | + (-_2bx * q4 + _2bz * q2) 117 | * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + _2bx 118 | * q3 119 | * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz); 120 | s2 = _2q4 * (2f * q2q4 - _2q1q3 - ax) + _2q1 121 | * (2f * q1q2 + _2q3q4 - ay) - 4f * q2 122 | * (1 - 2f * q2q2 - 2f * q3q3 - az) + _2bz * q4 123 | * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) 124 | + (_2bx * q3 + _2bz * q1) 125 | * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) 126 | + (_2bx * q4 - _4bz * q2) 127 | * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz); 128 | s3 = -_2q1 * (2f * q2q4 - _2q1q3 - ax) + _2q4 129 | * (2f * q1q2 + _2q3q4 - ay) - 4f * q3 130 | * (1 - 2f * q2q2 - 2f * q3q3 - az) + (-_4bx * q3 - _2bz * q1) 131 | * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) 132 | + (_2bx * q2 + _2bz * q4) 133 | * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) 134 | + (_2bx * q1 - _4bz * q3) 135 | * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz); 136 | s4 = _2q2 * (2f * q2q4 - _2q1q3 - ax) + _2q3 137 | * (2f * q1q2 + _2q3q4 - ay) + (-_4bx * q4 + _2bz * q2) 138 | * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) 139 | + (-_2bx * q1 + _2bz * q3) 140 | * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + _2bx 141 | * q2 142 | * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz); 143 | norm = 1f / (float) Math.sqrt(s1 * s1 + s2 * s2 + s3 * s3 + s4 * s4); // normalise 144 | // step 145 | // magnitude 146 | s1 *= norm; 147 | s2 *= norm; 148 | s3 *= norm; 149 | s4 *= norm; 150 | 151 | // Compute rate of change of quaternion 152 | qDot1 = 0.5f * (-q2 * gx - q3 * gy - q4 * gz) - beta * s1; 153 | qDot2 = 0.5f * (q1 * gx + q3 * gz - q4 * gy) - beta * s2; 154 | qDot3 = 0.5f * (q1 * gy - q2 * gz + q4 * gx) - beta * s3; 155 | qDot4 = 0.5f * (q1 * gz + q2 * gy - q3 * gx) - beta * s4; 156 | 157 | // Integrate to yield quaternion 158 | q1 += qDot1 * samplePeriod; 159 | q2 += qDot2 * samplePeriod; 160 | q3 += qDot3 * samplePeriod; 161 | q4 += qDot4 * samplePeriod; 162 | norm = 1f / (float) Math.sqrt(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4); // normalise 163 | // quaternion 164 | quaternion[0] = q1 * norm; 165 | quaternion[1] = q2 * norm; 166 | quaternion[2] = q3 * norm; 167 | quaternion[3] = q4 * norm; 168 | } 169 | 170 | public void update(float gx, float gy, float gz, float ax, float ay, 171 | float az) { 172 | float q1 = quaternion[0], q2 = quaternion[1], q3 = quaternion[2], q4 = quaternion[3]; // short 173 | // name 174 | // local 175 | // variable 176 | // for 177 | // readability 178 | float norm; 179 | float s1, s2, s3, s4; 180 | float qDot1, qDot2, qDot3, qDot4; 181 | 182 | // Auxiliary variables to avoid repeated arithmetic 183 | float _2q1 = 2f * q1; 184 | float _2q2 = 2f * q2; 185 | float _2q3 = 2f * q3; 186 | float _2q4 = 2f * q4; 187 | float _4q1 = 4f * q1; 188 | float _4q2 = 4f * q2; 189 | float _4q3 = 4f * q3; 190 | float _8q2 = 8f * q2; 191 | float _8q3 = 8f * q3; 192 | float q1q1 = q1 * q1; 193 | float q2q2 = q2 * q2; 194 | float q3q3 = q3 * q3; 195 | float q4q4 = q4 * q4; 196 | 197 | // Normalise accelerometer measurement 198 | norm = (float) Math.sqrt(ax * ax + ay * ay + az * az); 199 | if (norm == 0f) 200 | return; // handle NaN 201 | norm = 1 / norm; // use reciprocal for division 202 | ax *= norm; 203 | ay *= norm; 204 | az *= norm; 205 | 206 | // Gradient decent algorithm corrective step 207 | s1 = _4q1 * q3q3 + _2q3 * ax + _4q1 * q2q2 - _2q2 * ay; 208 | s2 = _4q2 * q4q4 - _2q4 * ax + 4f * q1q1 * q2 - _2q1 * ay - _4q2 + _8q2 209 | * q2q2 + _8q2 * q3q3 + _4q2 * az; 210 | s3 = 4f * q1q1 * q3 + _2q1 * ax + _4q3 * q4q4 - _2q4 * ay - _4q3 + _8q3 211 | * q2q2 + _8q3 * q3q3 + _4q3 * az; 212 | s4 = 4f * q2q2 * q4 - _2q2 * ax + 4f * q3q3 * q4 - _2q3 * ay; 213 | norm = 1f / (float) Math.sqrt(s1 * s1 + s2 * s2 + s3 * s3 + s4 * s4); // normalise 214 | // step 215 | // magnitude 216 | s1 *= norm; 217 | s2 *= norm; 218 | s3 *= norm; 219 | s4 *= norm; 220 | 221 | // Compute rate of change of quaternion 222 | qDot1 = 0.5f * (-q2 * gx - q3 * gy - q4 * gz) - beta * s1; 223 | qDot2 = 0.5f * (q1 * gx + q3 * gz - q4 * gy) - beta * s2; 224 | qDot3 = 0.5f * (q1 * gy - q2 * gz + q4 * gx) - beta * s3; 225 | qDot4 = 0.5f * (q1 * gz + q2 * gy - q3 * gx) - beta * s4; 226 | 227 | // Integrate to yield quaternion 228 | q1 += qDot1 * samplePeriod; 229 | q2 += qDot2 * samplePeriod; 230 | q3 += qDot3 * samplePeriod; 231 | q4 += qDot4 * samplePeriod; 232 | norm = 1f / (float) Math.sqrt(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4); // normalise 233 | // quaternion 234 | quaternion[0] = q1 * norm; 235 | quaternion[1] = q2 * norm; 236 | quaternion[2] = q3 * norm; 237 | quaternion[3] = q4 * norm; 238 | } 239 | 240 | } -------------------------------------------------------------------------------- /moveTrackLib/src/main/java/org/moveTrack/math/Quaternion.java: -------------------------------------------------------------------------------- 1 | package org.moveTrack.math; 2 | 3 | 4 | import static java.lang.Math.cos; 5 | import static java.lang.Math.sin; 6 | 7 | /** 8 | * Quaternions are data structures built from unicorn horns. 9 | *

10 | * I nabbed this implementation from The Internet. 11 | */ 12 | public final class Quaternion { 13 | private double x; 14 | private double y; 15 | private double z; 16 | private double w; 17 | //private float[] matrixs; 18 | 19 | public Quaternion(final Quaternion q) { 20 | this(q.x, q.y, q.z, q.w); 21 | } 22 | 23 | public Quaternion(double x, double y, double z, double w) { 24 | this.x = x; 25 | this.y = y; 26 | this.z = z; 27 | this.w = w; 28 | } 29 | 30 | public Quaternion(Vector3 axis, double angle) { 31 | set(axis, angle); 32 | } 33 | 34 | public static Quaternion create_from_axis_angle(float xx, float yy, float zz, float a) { 35 | // Here we calculate the sin( theta / 2) once for optimization 36 | float factor = (float) sin(a / 2.0); 37 | 38 | // Calculate the x, y and z of the quaternion 39 | float x = xx * factor; 40 | float y = yy * factor; 41 | float z = zz * factor; 42 | 43 | // Calcualte the w value by cos( theta / 2 ) 44 | float w = (float) cos(a / 2.0); 45 | 46 | Quaternion quat = new Quaternion(x, y, z, w); 47 | quat.normalizeThis(); 48 | return quat; 49 | } 50 | 51 | public void set(final Quaternion q) { 52 | //matrixs = null; 53 | this.x = q.x; 54 | this.y = q.y; 55 | this.z = q.z; 56 | this.w = q.w; 57 | } 58 | 59 | public double norm() { 60 | return Math.sqrt(dot(this)); 61 | } 62 | 63 | public double getW() { 64 | return w; 65 | } 66 | 67 | public double getX() { 68 | return x; 69 | } 70 | 71 | public double getY() { 72 | return y; 73 | } 74 | 75 | public double getZ() { 76 | return z; 77 | } 78 | 79 | /** 80 | * @param axis rotation axis, unit vector 81 | * @param angle the rotation angle 82 | * @return this 83 | */ 84 | public Quaternion set(Vector3 axis, double angle) { 85 | //matrixs = null; 86 | double s = (double) sin(angle / 2); 87 | w = (double) cos(angle / 2); 88 | x = axis.getX() * s; 89 | y = axis.getY() * s; 90 | z = axis.getZ() * s; 91 | return this; 92 | } 93 | 94 | public Quaternion mulThis(Quaternion q) { 95 | //matrixs = null; 96 | double nw = w * q.w - x * q.x - y * q.y - z * q.z; 97 | double nx = w * q.x + x * q.w + y * q.z - z * q.y; 98 | double ny = w * q.y + y * q.w + z * q.x - x * q.z; 99 | z = w * q.z + z * q.w + x * q.y - y * q.x; 100 | w = nw; 101 | x = nx; 102 | y = ny; 103 | return this; 104 | } 105 | 106 | public Quaternion scaleThis(double scale) { 107 | if (scale != 1) { 108 | //matrixs = null; 109 | w *= scale; 110 | x *= scale; 111 | y *= scale; 112 | z *= scale; 113 | } 114 | return this; 115 | } 116 | 117 | public Quaternion divThis(double scale) { 118 | if (scale != 1) { 119 | //matrixs = null; 120 | w /= scale; 121 | x /= scale; 122 | y /= scale; 123 | z /= scale; 124 | } 125 | return this; 126 | } 127 | 128 | public double dot(Quaternion q) { 129 | return x * q.x + y * q.y + z * q.z + w * q.w; 130 | } 131 | 132 | public boolean equals(Quaternion q) { 133 | return x == q.x && y == q.y && z == q.z && w == q.w; 134 | } 135 | 136 | public Quaternion interpolateThis(Quaternion q, double t) { 137 | if (!equals(q)) { 138 | double d = dot(q); 139 | double qx, qy, qz, qw; 140 | 141 | if (d < 0f) { 142 | qx = -q.x; 143 | qy = -q.y; 144 | qz = -q.z; 145 | qw = -q.w; 146 | d = -d; 147 | } else { 148 | qx = q.x; 149 | qy = q.y; 150 | qz = q.z; 151 | qw = q.w; 152 | } 153 | 154 | double f0, f1; 155 | 156 | if ((1 - d) > 0.1f) { 157 | double angle = (double) Math.acos(d); 158 | double s = (double) sin(angle); 159 | double tAngle = t * angle; 160 | f0 = (double) sin(angle - tAngle) / s; 161 | f1 = (double) sin(tAngle) / s; 162 | } else { 163 | f0 = 1 - t; 164 | f1 = t; 165 | } 166 | 167 | x = f0 * x + f1 * qx; 168 | y = f0 * y + f1 * qy; 169 | z = f0 * z + f1 * qz; 170 | w = f0 * w + f1 * qw; 171 | } 172 | 173 | return this; 174 | } 175 | 176 | public Quaternion normalizeThis() { 177 | return divThis(norm()); 178 | } 179 | 180 | public Quaternion interpolate(Quaternion q, double t) { 181 | return new Quaternion(this).interpolateThis(q, t); 182 | } 183 | 184 | /** 185 | * Converts this Quaternion into a matrix, returning it as a float array. 186 | */ 187 | public float[] toMatrix() { 188 | float[] matrixs = new float[16]; 189 | toMatrix(matrixs); 190 | return matrixs; 191 | } 192 | 193 | /** 194 | * Converts this Quaternion into a matrix, placing the values into the given array. 195 | * 196 | * @param matrixs 16-length float array. 197 | */ 198 | public final void toMatrix(float[] matrixs) { 199 | matrixs[3] = 0.0f; 200 | matrixs[7] = 0.0f; 201 | matrixs[11] = 0.0f; 202 | matrixs[12] = 0.0f; 203 | matrixs[13] = 0.0f; 204 | matrixs[14] = 0.0f; 205 | matrixs[15] = 1.0f; 206 | 207 | matrixs[0] = (float) (1.0f - (2.0f * ((y * y) + (z * z)))); 208 | matrixs[1] = (float) (2.0f * ((x * y) - (z * w))); 209 | matrixs[2] = (float) (2.0f * ((x * z) + (y * w))); 210 | 211 | matrixs[4] = (float) (2.0f * ((x * y) + (z * w))); 212 | matrixs[5] = (float) (1.0f - (2.0f * ((x * x) + (z * z)))); 213 | matrixs[6] = (float) (2.0f * ((y * z) - (x * w))); 214 | 215 | matrixs[8] = (float) (2.0f * ((x * z) - (y * w))); 216 | matrixs[9] = (float) (2.0f * ((y * z) + (x * w))); 217 | matrixs[10] = (float) (1.0f - (2.0f * ((x * x) + (y * y)))); 218 | } 219 | 220 | public float yaw() { 221 | return (float) Math.atan2(2.0f * (w * z + x * y), w * w + x * x - y * y - z * z); 222 | } 223 | } -------------------------------------------------------------------------------- /moveTrackLib/src/main/java/org/moveTrack/math/Vector3.java: -------------------------------------------------------------------------------- 1 | package org.moveTrack.math; 2 | 3 | public final class Vector3 { 4 | private double x, y, z; 5 | 6 | public Vector3(double ix, double iy, double iz) { 7 | x = ix; 8 | y = iy; 9 | z = iz; 10 | } 11 | 12 | public double getX() { 13 | return x; 14 | } 15 | 16 | public double getY() { 17 | return y; 18 | } 19 | 20 | public double getZ() { 21 | return z; 22 | } 23 | 24 | public void set(double ix, double iy, double iz) { 25 | x = ix; 26 | y = iy; 27 | z = iz; 28 | } 29 | 30 | public double magnitude() { 31 | return Math.sqrt(x * x + y * y + z * z); 32 | } 33 | 34 | public void multiply(double f) { 35 | x *= f; 36 | y *= f; 37 | z *= f; 38 | } 39 | 40 | public void normalise() { 41 | double mag = magnitude(); 42 | x /= mag; 43 | y /= mag; 44 | z /= mag; 45 | } 46 | } -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/drawable/empty_tall_divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/drawable/error_missing.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/drawable/ic_baseline_computer_24.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/drawable/ic_baseline_help_outline_24.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/drawable/ic_baseline_home_24.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/drawable/not_missing.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-hdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-hdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-mdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-mdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-xhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-xhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-xxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-xxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timocop/moveTrackVR/c99ab44c9fe1e34622bc63070909e88d7103eb2d/moveTrackLib/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /moveTrackLib/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #F80000 4 | #255 5 | #18EEFF 6 | #1BB3FF 7 | #2ADAFF 8 | #017784 9 | #FF000000 10 | #FFFFFFFF 11 | -------------------------------------------------------------------------------- /moveTrackMobile/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /moveTrackMobile/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | compileSdkVersion 31 7 | 8 | defaultConfig { 9 | applicationId 'org.movevrtrackersync' 10 | minSdkVersion 16 11 | targetSdkVersion 31 12 | versionCode 19 13 | versionName '9.7.9' 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_1_8 26 | targetCompatibility JavaVersion.VERSION_1_8 27 | } 28 | } 29 | 30 | dependencies { 31 | 32 | implementation 'androidx.appcompat:appcompat:1.1.0' 33 | implementation 'com.google.android.material:material:1.1.0' 34 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 35 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 36 | 37 | def nav_version = "2.3.1" 38 | 39 | // Java language implementation 40 | implementation "androidx.navigation:navigation-fragment:$nav_version" 41 | implementation "androidx.navigation:navigation-ui:$nav_version" 42 | 43 | // Feature module Support 44 | implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version" 45 | 46 | implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' 47 | implementation project(':moveTrackLib') 48 | 49 | 50 | } -------------------------------------------------------------------------------- /moveTrackMobile/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /moveTrackMobile/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /moveTrackMobile/src/main/java/org/moveTrack/Mobile/MainActivity.java: -------------------------------------------------------------------------------- 1 | package org.moveTrack.Mobile; 2 | 3 | 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.hardware.Sensor; 7 | import android.hardware.SensorManager; 8 | import android.os.Bundle; 9 | 10 | import androidx.appcompat.app.AppCompatActivity; 11 | import androidx.navigation.NavController; 12 | import androidx.navigation.Navigation; 13 | import androidx.navigation.ui.NavigationUI; 14 | 15 | import com.google.android.material.bottomnavigation.BottomNavigationView; 16 | 17 | import org.moveTrack.AutoDiscoverer; 18 | import org.moveTrack.Handshaker; 19 | import org.moveTrack.Mobile.ui.ConnectFragment; 20 | 21 | public class MainActivity extends AppCompatActivity { 22 | 23 | public static boolean[] sensor_exist; 24 | public static NavController contr; 25 | private static String missingSensorMessage = ""; 26 | 27 | public static boolean getSensorExists(int sensor) { 28 | if ((sensor < 0) || (sensor >= 3)) return false; 29 | return sensor_exist[sensor]; 30 | } 31 | 32 | public static boolean hasAnySensorsAtAll() { 33 | return getSensorExists(0) || getSensorExists(1); 34 | } 35 | 36 | public static String getSensorText() { 37 | return missingSensorMessage; 38 | } 39 | 40 | private void fillSensorArray() { 41 | SensorManager man = (SensorManager) getSystemService(Context.SENSOR_SERVICE); 42 | 43 | sensor_exist = new boolean[3]; 44 | 45 | sensor_exist[0] = ( 46 | man.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null || 47 | man.getDefaultSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED) != null 48 | ); 49 | sensor_exist[1] = ( 50 | man.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null || 51 | man.getDefaultSensor(Sensor.TYPE_ACCELEROMETER_UNCALIBRATED) != null 52 | ); 53 | sensor_exist[2] = man.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) != null; 54 | 55 | missingSensorMessage = ""; 56 | 57 | if (!hasAnySensorsAtAll()) { 58 | missingSensorMessage = getString(R.string.sensors_missing_all); 59 | } 60 | 61 | } 62 | 63 | private void ensureUUIDSet() { 64 | SharedPreferences prefs = getSharedPreferences("FakeMAC", Context.MODE_PRIVATE); 65 | 66 | long val = -1; 67 | if (!prefs.contains("FakeMACValue")) { 68 | SharedPreferences.Editor editor = prefs.edit(); 69 | val = (new java.util.Random()).nextLong(); 70 | editor.putLong("FakeMACValue", val); 71 | editor.apply(); 72 | } else { 73 | val = prefs.getLong("FakeMACValue", 1); 74 | } 75 | 76 | Handshaker.setMac(val); 77 | } 78 | 79 | private AutoDiscoverer.ConfigSettings connect(String ip, int port) { 80 | SharedPreferences prefs = ConnectFragment.get_prefs(this); 81 | SharedPreferences.Editor editor = prefs.edit(); 82 | 83 | editor.putString("ip_address", ip); 84 | editor.putInt("port", port); 85 | 86 | editor.apply(); 87 | 88 | 89 | contr.navigate(R.id.connectFragment); 90 | 91 | AutoDiscoverer.ConfigSettings configSettings = new AutoDiscoverer.ConfigSettings(); 92 | configSettings.magnetometerEnabled = prefs.getBoolean("magnetometer", true); 93 | configSettings.madgwickBeta = prefs.getFloat("madgwickbeta", 0.1f); 94 | configSettings.stabilization = prefs.getBoolean("stabilization", false); 95 | configSettings.sensorData = prefs.getInt("sensordata", 0); 96 | configSettings.smartCorrection = prefs.getBoolean("smartcorrection", true); 97 | return configSettings; 98 | } 99 | 100 | private void runDiscovery() { 101 | if (!hasAnySensorsAtAll()) return; 102 | if (!AutoDiscoverer.discoveryStillNecessary) return; 103 | 104 | try { 105 | AutoDiscoverer disc = new AutoDiscoverer(this, this::connect); 106 | Thread thrd = new Thread(disc::try_discover); 107 | thrd.start(); 108 | } catch (OutOfMemoryError ignored) { 109 | } 110 | } 111 | 112 | @Override 113 | protected void onCreate(Bundle savedInstanceState) { 114 | super.onCreate(savedInstanceState); 115 | 116 | ensureUUIDSet(); 117 | fillSensorArray(); 118 | 119 | setContentView(R.layout.activity_main); 120 | 121 | contr = Navigation.findNavController(this, R.id.fragment); 122 | 123 | BottomNavigationView nav = findViewById(R.id.nav_view); 124 | 125 | NavigationUI.setupWithNavController(nav, contr); 126 | 127 | runDiscovery(); 128 | } 129 | 130 | @Override 131 | protected void onDestroy() { 132 | super.onDestroy(); 133 | } 134 | 135 | @Override 136 | protected void onPause() { 137 | super.onPause(); 138 | } 139 | 140 | @Override 141 | protected void onResume() { 142 | super.onResume(); 143 | } 144 | } -------------------------------------------------------------------------------- /moveTrackMobile/src/main/java/org/moveTrack/Mobile/ui/ConnectFragment.java: -------------------------------------------------------------------------------- 1 | package org.moveTrack.Mobile.ui; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.SharedPreferences; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.Button; 12 | import android.widget.EditText; 13 | import android.widget.RadioButton; 14 | import android.widget.SeekBar; 15 | import android.widget.Switch; 16 | import android.widget.TextView; 17 | 18 | import androidx.fragment.app.Fragment; 19 | 20 | import org.moveTrack.Mobile.MainActivity; 21 | import org.moveTrack.Mobile.R; 22 | import org.moveTrack.TrackingService; 23 | 24 | /** 25 | * A simple {@link Fragment} subclass. 26 | * Use the {@link ConnectFragment#newInstance} factory method to 27 | * create an instance of this fragment. 28 | */ 29 | public class ConnectFragment extends GenericBindingFragment { 30 | 31 | final static String CONN_DATA = "CONNECTION_DATA_PREF"; 32 | Button connect_button = null; 33 | EditText ipAddrTxt = null; 34 | EditText portTxt = null; 35 | Switch magBox = null; 36 | SeekBar madgwickBetaBox = null; 37 | Switch stabilizationBox = null; 38 | RadioButton sensorDataDisabled = null; 39 | RadioButton sensorDataCompat = null; 40 | RadioButton sensorDataAll = null; 41 | Switch smartCorrection = null; 42 | SharedPreferences.OnSharedPreferenceChangeListener listener = new SharedPreferences.OnSharedPreferenceChangeListener() { 43 | @Override 44 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, java.lang.String s) { 45 | if (s.equals("ip_address")) { 46 | ipAddrTxt.setText(sharedPreferences.getString(s, "")); 47 | } 48 | if (s.equals("port")) { 49 | portTxt.setText(String.valueOf(sharedPreferences.getInt(s, 6969))); 50 | } 51 | } 52 | }; 53 | public ConnectFragment() { 54 | } 55 | 56 | public static SharedPreferences get_prefs(Context c) { 57 | return c.getSharedPreferences(CONN_DATA, Context.MODE_PRIVATE); 58 | } 59 | 60 | public static ConnectFragment newInstance() { 61 | ConnectFragment fragment = new ConnectFragment(); 62 | Bundle args = new Bundle(); 63 | fragment.setArguments(args); 64 | return fragment; 65 | } 66 | 67 | public SharedPreferences get_prefs() { 68 | return get_prefs(getContext()); 69 | } 70 | 71 | @Override 72 | protected void onSetStatus(String to) { 73 | if (curr_view == null) return; 74 | 75 | TextView text = curr_view.findViewById(R.id.statusText); 76 | 77 | if (text != null) 78 | text.setText(to.split("\n")[0]); 79 | } 80 | 81 | @Override 82 | protected void onConnectionStatus(boolean to) { 83 | if (connect_button != null) 84 | connect_button.setText(to ? "Disconnect" : "Connect"); 85 | 86 | if (ipAddrTxt != null) 87 | ipAddrTxt.setEnabled(!to); 88 | 89 | if (portTxt != null) 90 | portTxt.setEnabled(!to); 91 | 92 | if (magBox != null) 93 | magBox.setEnabled(!to); 94 | 95 | if (madgwickBetaBox != null) 96 | madgwickBetaBox.setEnabled(!to); 97 | 98 | if (stabilizationBox != null) 99 | stabilizationBox.setEnabled(!to); 100 | 101 | if (sensorDataDisabled != null) 102 | sensorDataDisabled.setEnabled(!to); 103 | 104 | if (sensorDataCompat != null) 105 | sensorDataCompat.setEnabled(!to); 106 | 107 | if (sensorDataAll != null) 108 | sensorDataAll.setEnabled(!to); 109 | 110 | if (smartCorrection != null) 111 | smartCorrection.setEnabled(!to); 112 | } 113 | 114 | @Override 115 | public void onDestroy() { 116 | get_prefs().unregisterOnSharedPreferenceChangeListener(listener); 117 | save_data(); 118 | 119 | super.onDestroy(); 120 | } 121 | 122 | @Override 123 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 124 | Bundle savedInstanceState) { 125 | curr_view = inflater.inflate(R.layout.fragment_connect, container, false); 126 | 127 | connect_button = curr_view.findViewById(R.id.connectButton); 128 | ipAddrTxt = curr_view.findViewById(R.id.editIP); 129 | portTxt = curr_view.findViewById(R.id.editPort); 130 | magBox = curr_view.findViewById(R.id.editMagnetometer); 131 | madgwickBetaBox = curr_view.findViewById(R.id.seekMadgwickBeta); 132 | stabilizationBox = curr_view.findViewById(R.id.editStabilization); 133 | sensorDataDisabled = curr_view.findViewById(R.id.radioSensordata0); 134 | sensorDataCompat = curr_view.findViewById(R.id.radioSensordata1); 135 | sensorDataAll = curr_view.findViewById(R.id.radioSensordata2); 136 | smartCorrection = curr_view.findViewById(R.id.editSmartCorrection); 137 | 138 | 139 | if (!MainActivity.hasAnySensorsAtAll()) { 140 | connect_button.setEnabled(false); 141 | ipAddrTxt.setEnabled(false); 142 | portTxt.setEnabled(false); 143 | magBox.setEnabled(false); 144 | madgwickBetaBox.setEnabled(false); 145 | stabilizationBox.setEnabled(false); 146 | sensorDataDisabled.setEnabled(false); 147 | sensorDataCompat.setEnabled(false); 148 | sensorDataAll.setEnabled(false); 149 | smartCorrection.setEnabled(false); 150 | 151 | TextView statusText = curr_view.findViewById(R.id.statusText); 152 | statusText.setText(R.string.sensors_missing_all); 153 | } else { 154 | SharedPreferences prefs = get_prefs(); 155 | 156 | ipAddrTxt.setText(prefs.getString("ip_address", "")); 157 | portTxt.setText(String.valueOf(prefs.getInt("port", 6969))); 158 | magBox.setChecked(prefs.getBoolean("magnetometer", true)); 159 | madgwickBetaBox.setProgress((int) (prefs.getFloat("madgwickbeta", 0.1f) * 10.f)); 160 | stabilizationBox.setChecked(prefs.getBoolean("stabilization", false)); 161 | switch(prefs.getInt("sensordata", 0)) { 162 | case 0: 163 | sensorDataDisabled.setChecked(true); 164 | sensorDataCompat.setChecked(false); 165 | sensorDataAll.setChecked(false); 166 | break; 167 | case 1: 168 | sensorDataDisabled.setChecked(false); 169 | sensorDataCompat.setChecked(true); 170 | sensorDataAll.setChecked(false); 171 | break; 172 | case 2: 173 | sensorDataDisabled.setChecked(false); 174 | sensorDataCompat.setChecked(false); 175 | sensorDataAll.setChecked(true); 176 | break; 177 | } 178 | smartCorrection.setChecked(prefs.getBoolean("smartcorrection", true)); 179 | 180 | connect_button.setOnClickListener(v -> onConnect(false)); 181 | 182 | prefs.registerOnSharedPreferenceChangeListener(listener); 183 | 184 | onConnectionStatus(TrackingService.isInstanceCreated()); 185 | } 186 | 187 | return curr_view; 188 | } 189 | 190 | private String get_ip_address() { 191 | String filtered_ip = String.valueOf(ipAddrTxt.getText()).replaceAll("[^0-9\\.]", ""); 192 | ipAddrTxt.setText(filtered_ip); 193 | 194 | return filtered_ip; 195 | } 196 | 197 | private int get_port() { 198 | String filtered_port = String.valueOf(portTxt.getText()).replaceAll("[^0-9]", ""); 199 | portTxt.setText(filtered_port); 200 | 201 | int val = 6969; 202 | try { 203 | val = Integer.parseInt(filtered_port); 204 | } catch (NumberFormatException ignored) { 205 | } 206 | 207 | return val; 208 | } 209 | 210 | private boolean get_mag() { 211 | return magBox.isChecked(); 212 | } 213 | 214 | private float get_madgwickbeta() { 215 | return (madgwickBetaBox.getProgress() / 10.f); 216 | } 217 | 218 | private boolean get_stabilization() { 219 | return stabilizationBox.isChecked(); 220 | } 221 | 222 | private int get_sensordata() { 223 | if(sensorDataDisabled.isChecked()) 224 | return 0; 225 | if(sensorDataCompat.isChecked()) 226 | return 1; 227 | if(sensorDataAll.isChecked()) 228 | return 2; 229 | 230 | return 0; 231 | } 232 | private boolean get_smartconnection() { return smartCorrection.isChecked(); } 233 | 234 | private void onConnect(boolean auto) { 235 | if ((service_v != null) && (service_v.is_running())) { 236 | onSetStatus("Killing service..."); 237 | Intent intent = new Intent("kill-ze-service"); 238 | getContext().sendBroadcast(intent); 239 | return; 240 | } 241 | 242 | 243 | onConnectionStatus(true); 244 | 245 | Intent mainIntent = new Intent(getContext(), TrackingService.class); 246 | if (auto) { 247 | mainIntent.putExtra("ipAddrTxt", "255.255.255.255"); 248 | } else { 249 | mainIntent.putExtra("ipAddrTxt", get_ip_address()); 250 | } 251 | 252 | mainIntent.putExtra("port_no", get_port()); 253 | mainIntent.putExtra("magnetometer", get_mag()); 254 | mainIntent.putExtra("madgwickbeta", get_madgwickbeta()); 255 | mainIntent.putExtra("stabilization", get_stabilization()); 256 | mainIntent.putExtra("sensordata", get_sensordata()); 257 | mainIntent.putExtra("smartcorrection", get_smartconnection()); 258 | 259 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 260 | getContext().startForegroundService(mainIntent); 261 | } else { 262 | getContext().startService(mainIntent); 263 | } 264 | } 265 | 266 | public void save_data() { 267 | if (!MainActivity.hasAnySensorsAtAll()) 268 | return; 269 | 270 | if (ipAddrTxt == null || 271 | portTxt == null || 272 | magBox == null || 273 | madgwickBetaBox == null || 274 | stabilizationBox == null || 275 | sensorDataDisabled == null || 276 | sensorDataCompat == null || 277 | sensorDataAll == null || 278 | smartCorrection == null) 279 | return; 280 | 281 | SharedPreferences prefs = get_prefs(); 282 | SharedPreferences.Editor editor = prefs.edit(); 283 | 284 | editor.putString("ip_address", get_ip_address()); 285 | editor.putInt("port", get_port()); 286 | editor.putBoolean("magnetometer", get_mag()); 287 | editor.putFloat("madgwickbeta", get_madgwickbeta()); 288 | editor.putBoolean("stabilization", get_stabilization()); 289 | editor.putInt("sensordata", get_sensordata()); 290 | editor.putBoolean("smartcorrection", get_smartconnection()); 291 | 292 | editor.apply(); 293 | } 294 | } -------------------------------------------------------------------------------- /moveTrackMobile/src/main/java/org/moveTrack/Mobile/ui/GenericBindingFragment.java: -------------------------------------------------------------------------------- 1 | package org.moveTrack.Mobile.ui; 2 | 3 | import static android.content.Context.BIND_AUTO_CREATE; 4 | 5 | import android.content.BroadcastReceiver; 6 | import android.content.ComponentName; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.content.IntentFilter; 10 | import android.content.ServiceConnection; 11 | import android.os.Bundle; 12 | import android.os.IBinder; 13 | import android.view.View; 14 | 15 | import androidx.fragment.app.Fragment; 16 | import androidx.localbroadcastmanager.content.LocalBroadcastManager; 17 | 18 | import org.moveTrack.Mobile.MainActivity; 19 | import org.moveTrack.TrackingService; 20 | 21 | public abstract class GenericBindingFragment extends Fragment { 22 | 23 | public static String status_string = ""; 24 | View curr_view; 25 | TrackingService service_v = null; 26 | private ServiceConnection trackingConnection = new ServiceConnection() { 27 | @Override 28 | public void onServiceConnected(ComponentName name, IBinder service) { 29 | TrackingService.TrackingBinder binderBridge = (TrackingService.TrackingBinder) service; 30 | service_v = binderBridge.getService(); 31 | 32 | onConnectionStatus(service_v.is_running()); 33 | 34 | if ((!service_v.is_running()) && (status_string.length() > 0)) { 35 | onSetStatus(status_string); 36 | } else { 37 | status_string = service_v.getLog(); 38 | onSetStatus(status_string); 39 | } 40 | } 41 | 42 | @Override 43 | public void onServiceDisconnected(ComponentName name) { 44 | service_v = null; 45 | 46 | onConnectionStatus(false); 47 | } 48 | }; 49 | private BroadcastReceiver logReceiver = new BroadcastReceiver() { 50 | @Override 51 | public void onReceive(Context context, Intent intent) { 52 | switch (intent.getAction()) { 53 | case "info-log": 54 | onConnectionStatus(true); 55 | String data = intent.getStringExtra("message"); 56 | onSetStatus(data); 57 | status_string = data + "\n" + status_string; 58 | return; 59 | case "cya-ded": 60 | onConnectionStatus(false); 61 | return; 62 | case "pls-let-me-die": 63 | doBinding(false); 64 | doBinding(true); 65 | // Navigation.findNavController(curr_view).navigateUp(); 66 | return; 67 | 68 | } 69 | } 70 | }; 71 | 72 | protected abstract void onSetStatus(String to); 73 | 74 | protected abstract void onConnectionStatus(boolean to); 75 | 76 | private void doBinding(boolean is_bound) { 77 | if (is_bound) { 78 | Intent intent = new Intent(getContext(), TrackingService.class); 79 | getContext().bindService(intent, trackingConnection, BIND_AUTO_CREATE); 80 | } else { 81 | getContext().unbindService(trackingConnection); 82 | } 83 | } 84 | 85 | @Override 86 | public void onDestroy() { 87 | super.onDestroy(); 88 | if (!MainActivity.hasAnySensorsAtAll()) return; 89 | doBinding(false); 90 | LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(logReceiver); 91 | } 92 | 93 | 94 | @Override 95 | public void onCreate(Bundle savedInstanceState) { 96 | super.onCreate(savedInstanceState); 97 | if (!MainActivity.hasAnySensorsAtAll()) return; 98 | 99 | doBinding(true); 100 | 101 | LocalBroadcastManager.getInstance(getContext()).registerReceiver(logReceiver, new IntentFilter("info-log")); 102 | LocalBroadcastManager.getInstance(getContext()).registerReceiver(logReceiver, new IntentFilter("cya-ded")); 103 | LocalBroadcastManager.getInstance(getContext()).registerReceiver(logReceiver, new IntentFilter("pls-let-me-die")); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /moveTrackMobile/src/main/java/org/moveTrack/Mobile/ui/SensorInfoFragment.java: -------------------------------------------------------------------------------- 1 | package org.moveTrack.Mobile.ui; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.os.Bundle; 6 | import android.util.AttributeSet; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.ImageView; 11 | import android.widget.TextView; 12 | 13 | import androidx.fragment.app.Fragment; 14 | 15 | import org.moveTrack.Mobile.MainActivity; 16 | import org.moveTrack.Mobile.R; 17 | 18 | /** 19 | * A simple {@link Fragment} subclass. 20 | * Use the {@link SensorInfoFragment#newInstance} factory method to 21 | * create an instance of this fragment. 22 | */ 23 | public class SensorInfoFragment extends Fragment { 24 | String sensorName = "err"; 25 | int sensorID = -2; 26 | View main_view; 27 | 28 | public SensorInfoFragment() { 29 | // Required empty public constructor 30 | } 31 | 32 | public static SensorInfoFragment newInstance(String sensorName, int sensorID) { 33 | SensorInfoFragment fragment = new SensorInfoFragment(); 34 | Bundle args = new Bundle(); 35 | fragment.setArguments(args); 36 | return fragment; 37 | } 38 | 39 | @Override 40 | public void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | 43 | } 44 | 45 | @Override 46 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 47 | Bundle savedInstanceState) { 48 | main_view = inflater.inflate(R.layout.fragment_sensor_info, container, false); 49 | 50 | if (MainActivity.getSensorExists(sensorID)) { 51 | ((ImageView) main_view.findViewById(R.id.radio_btn)).setImageResource(R.drawable.not_missing); 52 | ((TextView) main_view.findViewById(R.id.sensor_name)).setText(sensorName); 53 | } else { 54 | ((ImageView) main_view.findViewById(R.id.radio_btn)).setImageResource(R.drawable.error_missing); 55 | ((TextView) main_view.findViewById(R.id.sensor_name)).setText("Missing: " + sensorName); 56 | } 57 | 58 | return main_view; 59 | } 60 | 61 | @Override 62 | public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) { 63 | super.onInflate(context, attrs, savedInstanceState); 64 | 65 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SensorInfoFragment); 66 | 67 | sensorName = String.valueOf(a.getText(R.styleable.SensorInfoFragment_sensorName)); 68 | sensorID = a.getInt(R.styleable.SensorInfoFragment_sensorID, -2); 69 | 70 | a.recycle(); 71 | } 72 | } -------------------------------------------------------------------------------- /moveTrackMobile/src/main/java/org/moveTrack/Mobile/ui/debugLogFragment.java: -------------------------------------------------------------------------------- 1 | package org.moveTrack.Mobile.ui; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import androidx.fragment.app.Fragment; 9 | 10 | import org.moveTrack.AppStatus; 11 | import org.moveTrack.Mobile.R; 12 | 13 | /** 14 | * A simple {@link Fragment} subclass. 15 | * Use the {@link debugLogFragment#newInstance} factory method to 16 | * create an instance of this fragment. 17 | */ 18 | public class debugLogFragment extends GenericBindingFragment { 19 | AppStatus stat; 20 | 21 | public debugLogFragment() { 22 | } 23 | 24 | public static debugLogFragment newInstance() { 25 | debugLogFragment fragment = new debugLogFragment(); 26 | Bundle args = new Bundle(); 27 | fragment.setArguments(args); 28 | return fragment; 29 | } 30 | 31 | @Override 32 | protected void onSetStatus(String to) { 33 | if (stat != null) stat.update(to); 34 | } 35 | 36 | @Override 37 | protected void onConnectionStatus(boolean to) { 38 | 39 | } 40 | 41 | @Override 42 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 43 | Bundle savedInstanceState) { 44 | curr_view = inflater.inflate(R.layout.fragment_debug_log, container, false); 45 | stat = new AppStatus(getActivity(), curr_view.findViewById(R.id.debugText)); 46 | return curr_view; 47 | } 48 | } -------------------------------------------------------------------------------- /moveTrackMobile/src/main/java/org/moveTrack/Mobile/ui/homeMenu.java: -------------------------------------------------------------------------------- 1 | package org.moveTrack.Mobile.ui; 2 | 3 | import android.content.Intent; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.Button; 10 | import android.widget.TextView; 11 | 12 | import androidx.fragment.app.Fragment; 13 | 14 | import org.moveTrack.Mobile.MainActivity; 15 | import org.moveTrack.Mobile.R; 16 | import org.moveTrack.TrackingService; 17 | 18 | public class homeMenu extends Fragment { 19 | 20 | 21 | public homeMenu() { 22 | } 23 | 24 | @Override 25 | public void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | } 28 | 29 | @Override 30 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 31 | Bundle savedInstanceState) { 32 | View v = inflater.inflate(R.layout.fragment_home_menu, container, false); 33 | 34 | Button autoConnectButton = v.findViewById(R.id.autoconnectButton); 35 | TextView sensorWarning = v.findViewById(R.id.sensorWarningTextView); 36 | sensorWarning.setText(MainActivity.getSensorText()); 37 | 38 | if (!MainActivity.hasAnySensorsAtAll()) { 39 | TextView sleepWarning = v.findViewById(R.id.sleepWarningText); 40 | sleepWarning.setText(""); 41 | autoConnectButton.setVisibility(View.GONE); 42 | } else { 43 | autoConnectButton.setOnClickListener(p -> autoConnect()); 44 | } 45 | 46 | return v; 47 | } 48 | 49 | private void autoConnect() { 50 | if (TrackingService.isInstanceCreated()) return; 51 | 52 | Intent mainIntent = new Intent(getContext(), TrackingService.class); 53 | mainIntent.putExtra("ipAddrTxt", "255.255.255.255"); 54 | mainIntent.putExtra("port_no", 6969); 55 | mainIntent.putExtra("magnetometer", true); 56 | mainIntent.putExtra("madgwickbeta", 0.1f); 57 | 58 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 59 | getContext().startForegroundService(mainIntent); 60 | } else { 61 | getContext().startService(mainIntent); 62 | } 63 | MainActivity.contr.navigate(R.id.connectFragment); 64 | } 65 | } -------------------------------------------------------------------------------- /moveTrackMobile/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /moveTrackMobile/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /moveTrackMobile/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 23 | 24 | 29 | -------------------------------------------------------------------------------- /moveTrackMobile/src/main/res/layout/fragment_connect.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | 17 | 18 | 23 | 24 | 29 | 30 | 38 | 39 | 47 | 48 |