diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..d99be75 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,43 @@ +plugins { + alias(libs.plugins.androidApplication) +} + +android { + namespace = "com.astatin3.android_position_tracking" + compileSdk = 34 + + defaultConfig { + applicationId = "com.astatin3.android_position_tracking" + minSdk = 24 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} + +dependencies { + + implementation(libs.appcompat) + implementation(libs.material) + implementation(libs.activity) + implementation(libs.constraintlayout) + testImplementation(libs.junit) + androidTestImplementation(libs.ext.junit) + androidTestImplementation(libs.espresso.core) +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/astatin3/android_position_tracking/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/astatin3/android_position_tracking/ExampleInstrumentedTest.java new file mode 100644 index 0000000..15817bf --- /dev/null +++ b/app/src/androidTest/java/com/astatin3/android_position_tracking/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.astatin3.android_position_tracking; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.astatin3.android_position_tracking", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e6ee8e6 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/astatin3/android_position_tracking/MainActivity.java b/app/src/main/java/com/astatin3/android_position_tracking/MainActivity.java new file mode 100644 index 0000000..5888b59 --- /dev/null +++ b/app/src/main/java/com/astatin3/android_position_tracking/MainActivity.java @@ -0,0 +1,66 @@ +package com.astatin3.android_position_tracking; + +import static android.util.Half.EPSILON; + +import android.content.pm.ActivityInfo; +import android.os.Bundle; + +import androidx.activity.EdgeToEdge; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Bundle; +import android.content.Context; +import android.widget.TextView; + +public class MainActivity extends AppCompatActivity { + TextView textX, textY, textZ; + Position o; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_main); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + textX = findViewById(R.id.textX); + textY = findViewById(R.id.textY); + textZ = findViewById(R.id.textZ); + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + + o = new Position(this); + +// o.startListening(ori_listener); + } + + private Position.Listener ori_listener = new Position.Listener() { + @Override + public void onPositionChanged(float x, float y, float z) { + textX.setText(String.valueOf((int)(x*100))); + textY.setText(String.valueOf((int)(y*100))); + textZ.setText(String.valueOf((int)(z*100))); + System.out.println(x); + } + }; + + public void onResume() { + super.onResume(); + o.startListening(ori_listener); +// sensorManager.registerListener(gyroListener, sensor, SensorManager.SENSOR_DELAY_NORMAL); + } + public void onStop() { + super.onStop(); + o.stopListening(); +// sensorManager.unregisterListener(gyroListener); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/astatin3/android_position_tracking/Orientation.java b/app/src/main/java/com/astatin3/android_position_tracking/Orientation.java new file mode 100644 index 0000000..fa9d20f --- /dev/null +++ b/app/src/main/java/com/astatin3/android_position_tracking/Orientation.java @@ -0,0 +1,122 @@ +package com.astatin3.android_position_tracking; + +import android.app.Activity; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +//import android.support.annotation.Nullable; +import android.view.Surface; +import android.view.WindowManager; + +import androidx.annotation.Nullable; + +// Copied from https://github.com/kplatfoot/android-rotation-sensor-sample/blob/master/app/src/main/java/com/kviation/sample/orientation/Orientation.java +public class Orientation implements SensorEventListener { + + public interface Listener { + void onOrientationChanged(float[] rotation); + } + + private static final int SENSOR_DELAY_MICROS = 16 * 1000; // 16ms + + private final WindowManager mWindowManager; + + private final SensorManager mSensorManager; + + @Nullable + private final Sensor mRotationSensor; + + private int mLastAccuracy; + private Listener mListener; + + public Orientation(Activity activity) { + mWindowManager = activity.getWindow().getWindowManager(); + mSensorManager = (SensorManager) activity.getSystemService(Activity.SENSOR_SERVICE); + + // Can be null if the sensor hardware is not available + mRotationSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); + } + + public void startListening(Listener listener) { + if (mListener == listener) { + return; + } + mListener = listener; + if (mRotationSensor == null) { + System.out.println("Rotation vector sensor not available; will not provide orientation data."); + return; + } + mSensorManager.registerListener(this, mRotationSensor, SENSOR_DELAY_MICROS); + } + + public void stopListening() { + mSensorManager.unregisterListener(this); + mListener = null; + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + if (mLastAccuracy != accuracy) { + mLastAccuracy = accuracy; + } + } + + @Override + public void onSensorChanged(SensorEvent event) { + if (mListener == null) { + return; + } + if (mLastAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) { + return; + } + if (event.sensor == mRotationSensor) { + updateOrientation(event.values); + } + } + + @SuppressWarnings("SuspiciousNameCombination") + private void updateOrientation(float[] rotationVector) { + float[] rotationMatrix = new float[9]; + SensorManager.getRotationMatrixFromVector(rotationMatrix, rotationVector); + + final int worldAxisForDeviceAxisX; + final int worldAxisForDeviceAxisY; + + // Remap the axes as if the device screen was the instrument panel, + // and adjust the rotation matrix for the device orientation. + switch (mWindowManager.getDefaultDisplay().getRotation()) { + case Surface.ROTATION_0: + default: + worldAxisForDeviceAxisX = SensorManager.AXIS_X; + worldAxisForDeviceAxisY = SensorManager.AXIS_Z; + break; + case Surface.ROTATION_90: + worldAxisForDeviceAxisX = SensorManager.AXIS_Z; + worldAxisForDeviceAxisY = SensorManager.AXIS_MINUS_X; + break; + case Surface.ROTATION_180: + worldAxisForDeviceAxisX = SensorManager.AXIS_MINUS_X; + worldAxisForDeviceAxisY = SensorManager.AXIS_MINUS_Z; + break; + case Surface.ROTATION_270: + worldAxisForDeviceAxisX = SensorManager.AXIS_MINUS_Z; + worldAxisForDeviceAxisY = SensorManager.AXIS_X; + break; + } + + float[] adjustedRotationMatrix = new float[9]; + SensorManager.remapCoordinateSystem(rotationMatrix, worldAxisForDeviceAxisX, + worldAxisForDeviceAxisY, adjustedRotationMatrix); + + // Transform rotation matrix into azimuth/pitch/roll + float[] orientation = new float[3]; + SensorManager.getOrientation(adjustedRotationMatrix, orientation); + + + mListener.onOrientationChanged(new float[]{ + (float)Math.toDegrees(orientation[0]), + (float)Math.toDegrees(orientation[1]), + (float)Math.toDegrees(orientation[2])}); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/astatin3/android_position_tracking/Position.java b/app/src/main/java/com/astatin3/android_position_tracking/Position.java new file mode 100644 index 0000000..bea3b91 --- /dev/null +++ b/app/src/main/java/com/astatin3/android_position_tracking/Position.java @@ -0,0 +1,191 @@ +package com.astatin3.android_position_tracking; + +import android.app.Activity; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.view.WindowManager; + +import androidx.annotation.Nullable; + +public class Position implements SensorEventListener { + public interface Listener { + void onPositionChanged(float x, float y, float z); + } + private Orientation o; + float[] cur_rotation; + private final SensorManager mSensorManager; + private static final int SENSOR_DELAY_MICROS = 16 * 1000; // 16ms + @Nullable + private final Sensor mAccelSensor; + + private int mLastAccuracy; + private Listener mListener; + +// private float[] acceleration = new float[3]; // Accelerometer values +// private float[] rotation = new float[4]; // Gyroscope values +// private float[] globalAcceleration = new float[3]; // Global accelerometer values +// private float[] position = new float[3]; // Device position + + public Position(Activity activity){ +// mWindowManager = activity.getWindow().getWindowManager(); + mSensorManager = (SensorManager) activity.getSystemService(Activity.SENSOR_SERVICE); + + // Can be null if the sensor hardware is not available + mAccelSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION); + + o = new Orientation(activity); + } + + @Override + public void onSensorChanged(SensorEvent event) { + if (mListener == null) { + return; + } + if (mLastAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) { + return; + } + if (event.sensor == mAccelSensor) { + updateAccel(event.values); + } + } + + public void stopListening() { + mSensorManager.unregisterListener(this); + mListener = null; + o.stopListening(); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + if (mLastAccuracy != accuracy) { + mLastAccuracy = accuracy; + } + } + + public void startListening(Position.Listener listener) { + if (mListener == listener) { + return; + } + mListener = listener; + if (mAccelSensor == null) { + System.out.println("Rotation vector sensor not available; will not provide orientation data."); + return; + } + + o.startListening(new Orientation.Listener() { + @Override + public void onOrientationChanged(float[] rotation) { + cur_rotation = rotation; + } + }); + + mSensorManager.registerListener(this, mAccelSensor, SENSOR_DELAY_MICROS); + } + + private float[] position = {0, 0, 0}; + private float[] velocity = {0, 0, 0}; + private float lastUpdateTime = 0; + private float[] lastAccel = {0, 0, 0}; + private static final float ALPHA = 0.1f; // Low-pass filter factor + private static final float NOISE_THRESHOLD = 0.1f; // Noise threshold + + public void updateAccel(float[] accelVector) { + float currentTime = System.nanoTime() / 1e9f; // Convert to seconds + float deltaTime = currentTime - lastUpdateTime; + + if (lastUpdateTime == 0) { + lastUpdateTime = currentTime; + System.arraycopy(accelVector, 0, lastAccel, 0, 3); + return; + } + + // Apply low-pass filter + for (int i = 0; i < 3; i++) { + accelVector[i] = lowPassFilter(lastAccel[i], accelVector[i]); + } + + // Apply threshold-based noise reduction + for (int i = 0; i < 3; i++) { + if (Math.abs(accelVector[i]) < NOISE_THRESHOLD) { + accelVector[i] = 0; + } + } + + accelVector = rotatePosition(accelVector, cur_rotation); + + for (int i = 0; i < 3; i++) { + // Update velocity: v = v0 + a * t + velocity[i] += accelVector[i] * deltaTime; + + // Update position: x = x0 + v * t + (1/2) * a * t^2 + position[i] += velocity[i] * deltaTime + 0.5f * accelVector[i] * deltaTime * deltaTime; + } + + lastUpdateTime = currentTime; + System.arraycopy(accelVector, 0, lastAccel, 0, 3); + + mListener.onPositionChanged(position[0], position[1], position[2]); + } + + private float lowPassFilter(float lastValue, float currentValue) { + return ALPHA * lastValue + (1 - ALPHA) * currentValue; + } + + public static float[] rotatePosition(float[] position, float[] rotation) { + if (position.length != 3 || rotation.length != 3) { + throw new IllegalArgumentException("Both position and rotation must have 3 components"); + } + + // Convert degrees to radians + float pitch = (float) Math.toRadians(rotation[0]); + float yaw = (float) Math.toRadians(rotation[1]); + float roll = (float) Math.toRadians(rotation[2]); + + // Precompute trigonometric functions + float cp = (float) Math.cos(pitch); + float sp = (float) Math.sin(pitch); + float cy = (float) Math.cos(yaw); + float sy = (float) Math.sin(yaw); + float cr = (float) Math.cos(roll); + float sr = (float) Math.sin(roll); + + // Create rotation matrix + float[][] rotMatrix = new float[3][3]; + rotMatrix[0][0] = cy * cr + sy * sp * sr; + rotMatrix[0][1] = -cy * sr + sy * sp * cr; + rotMatrix[0][2] = sy * cp; + rotMatrix[1][0] = sr * cp; + rotMatrix[1][1] = cr * cp; + rotMatrix[1][2] = -sp; + rotMatrix[2][0] = -sy * cr + cy * sp * sr; + rotMatrix[2][1] = sy * sr + cy * sp * cr; + rotMatrix[2][2] = cy * cp; + + // Apply rotation + float[] rotatedPosition = new float[3]; + for (int i = 0; i < 3; i++) { + rotatedPosition[i] = + rotMatrix[i][0] * position[0] + + rotMatrix[i][1] * position[1] + + rotMatrix[i][2] * position[2]; + } + + return rotatedPosition; + } + // Add a calibration method to remove initial offset +// public void calibrate(int numSamples) { +// float[] sumAccel = {0, 0, 0}; +// for (int i = 0; i < numSamples; i++) { +// float[] sample = getSensorReading(); // Implement this method to get raw sensor data +// for (int j = 0; j < 3; j++) { +// sumAccel[j] += sample[j]; +// } +// } +// for (int i = 0; i < 3; i++) { +// sumAccel[i] /= numSamples; +// } +// // Store this average as the zero offset and subtract it from future readings +// } +} diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..c7ed406 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,63 @@ + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..1726183 --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..c8524cd --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,5 @@ + + + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..5fa6ab9 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + android-position-tracking + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..bf7394c --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,9 @@ + + + + +