diff --git a/README.md b/README.md index fbf9a75..e6ec27b 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,45 @@ -# -This app allows one to use their smartphone as a computer mouse. The movement of the smartphone is measured over an accelerometer and is then transmitted over Bluetooth to a computer. +
+ + + SmartMouse logo and text + + +

SmartMouse is an app that aims to make a smartphone usable as a normal computer mouse. It does estimate its position by processing data from the integrated accelerometer and gyroscope with a custom sensor-fusion algorithm. This data is then transmitted to a host computer over the Bluetooth HID Profile. The mouse buttons are recreated using the Touchscreen.

+
+ +--- + +## State +Currently, this app is still in early development. The position estimation is currently not always correct, which leads to different artifacts and wrong movements of the cursor. Because of that, this app is not yet ready for practical use. + +## Features +This app is defined by the following three core features: + +- **Real Position**: The real position of the device is estimated like a normal mouse using the device's internal sensors. +- **Serverless Connection**: To connect to a target device, the Bluetooth HID Profile is used. Thus, **no** special software or server is required on the target device. +- **Powerful Interface**: The UI of this app is designed to make it usable for everyone. However, powerful customization of the algorithm and interface is still possible over the advanced settings. ## Installation -To install this app, you can download an APK from the [release section](https://github.com/VirtCode/SmartMouse/releases). This debug APK can then be installed on an Android device. Sometimes, sideloading apps must be enabled in the device settings. +To install this app, you can download a built debug APK from the [release section](https://github.com/VirtCode/SmartMouse/releases). This APK can easily be installed on your Android device by just opening it. In some cases, sideloading must be enabled in the device settings in to install it. + +After installation, the app should be self-explanatory. First, add your computer to the devices on the "Connect" page. Then connect to it and open the "Mouse" page. Now you can use your device just like a normal mouse, by moving it on a surface and using the touchscreen to click and scroll. + +## Processing Algorithm +This app uses a custom sensor-fusion algorithm to estimate the position of the device that is developed specifically for mouse-like movements. + +*This algorithm will be documented here in the future.* + +## Debugging +**The following steps and procedures are only intended for debugging and are NOT required for normal usage.** + +For general debugging or just to navigate the source code conveniently, clone this repository and open the Gradle project in Android Studio or a similar IDE. + +To debug the algorithm, another method should be used. This app includes special debugging features. Sensor and processing data can be transmitted in real-time over the network and stored as CSV files on a computer. These files may be analyzed more precisely with data analysis software. Please note that a major delay in the normal transmission via Bluetooth may occur when the debugging transmission is also active. -Please note that this app only supports Android version 9 or higher. +Follow these steps to set up a debugging environment: -## Debug Environment -To debug this app or to navigate its source code conveniently, you may clone this repository and open the Gradle project in Android Studio or another IDE. +1. Download [SensorServer](https://github.com/VirtCode/SensorServer) on your computer, move it to a directory you know, and run it in a terminal like described on its GitHub page. +2. Configure debugging in your SmartMouse app on your smartphone by enabling advanced settings and enabling "Debug Transmission" in the debugging category. Change the hostname to the local IP of your computer. Restart the app for the settings to take effect. +3. Now use the app like normal. When you enter the mouse interface, a transmission is automatically started. This transmission is ended when you leave the mouse interface. The ID of this transmission can be seen in the output of the SensorServer. -## Note -This project is part of a matura paper. \ No newline at end of file +Now you are ready to analyze transmissions which are stored as CSV in your folder by SensorServer. diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1e6d06b..da6ac99 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,8 @@ + + @@ -12,7 +14,7 @@ android:allowBackup="true" android:icon="@mipmap/icon" android:label="@string/app_name" - android:supportsRtl="true" + android:supportsRtl="false" android:theme="@style/Theme.SmartphoneMouse"> {}, new Parameters(PreferenceManager.getDefaultSharedPreferences(context))); + fetchSensor(); + } + + /** + * Fetches the sensor from the system. + */ + private void fetchSensor() { + manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + accelerometer = manager.getDefaultSensor(MovementHandler.SENSOR_TYPE_ACCELEROMETER); + gyroscope = manager.getDefaultSensor(MovementHandler.SENSOR_TYPE_GYROSCOPE); + } + + /** + * Registers itself as a listener. + */ + private void register() { + if (registered) return; + manager.registerListener(this, accelerometer, MovementHandler.SAMPLING_RATE); + manager.registerListener(this, gyroscope, MovementHandler.SAMPLING_RATE); + + registered = true; + } + + /** + * Unregisters itself as a listener. + */ + private void unregister() { + if (!registered) return; + manager.unregisterListener(this, accelerometer); + manager.unregisterListener(this, gyroscope); + + registered = false; + } + + /** + * Starts the measuring process. + * + * @param doneListener listener that is executed once the process has finished + */ + public void calibrate(Calibration.StateListener doneListener) { + calibration.setListener((state) -> { + if (state == Calibration.STATE_END) unregister(); + + doneListener.update(state); + }); + + begun = false; + calibration.startCalibration(); + + register(); + } + + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor.getType() == MovementHandler.SENSOR_TYPE_ACCELEROMETER) { + + if (!begun) { + begun = true; + firstTime = event.timestamp; + } + + + float time = (event.timestamp - firstTime) * NANO_FULL_FACTOR; + Vec3f acceleration = new Vec3f(event.values[0], event.values[1], event.values[2]); + + calibration.data(time, acceleration, gyroSample); + + } else if (event.sensor.getType() == MovementHandler.SENSOR_TYPE_GYROSCOPE) { + this.gyroSample = new Vec3f(event.values[0], event.values[1], event.values[2]); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) {} +} diff --git a/app/src/main/java/ch/virt/smartphonemouse/customization/DefaultSettings.java b/app/src/main/java/ch/virt/smartphonemouse/customization/DefaultSettings.java index 146ccca..32efa5e 100644 --- a/app/src/main/java/ch/virt/smartphonemouse/customization/DefaultSettings.java +++ b/app/src/main/java/ch/virt/smartphonemouse/customization/DefaultSettings.java @@ -1,6 +1,7 @@ package ch.virt.smartphonemouse.customization; import android.content.SharedPreferences; +import ch.virt.smartphonemouse.mouse.Parameters; /** * This class is used for restoring the settings to their factory defaults. @@ -27,15 +28,24 @@ public static void set(SharedPreferences preferences) { edit.putBoolean("populated", true); edit.putBoolean("showUsage", true); - edit.putBoolean("debugEnabled", false); + edit.putBoolean("advanced", false); edit.apply(); + defaultDebug(preferences); defaultInterface(preferences); defaultMovement(preferences); defaultCommunication(preferences); } + private static void defaultDebug(SharedPreferences preferences) { + preferences.edit() + .putBoolean("debugEnabled", false) + .putString("debugHost", "undefined") + .putInt("debugPort", 55555) + .apply(); + } + /** * Writes the default interface settings * @@ -54,11 +64,11 @@ private static void defaultInterface(SharedPreferences preferences) { edit.putFloat("interfaceVisualsIntensity", 0.5f); edit.putBoolean("interfaceVibrationsEnable", true); - edit.putInt("interfaceVibrationsButtonIntensity", 100); + edit.putInt("interfaceVibrationsButtonIntensity", 50); edit.putInt("interfaceVibrationsButtonLength", 30); - edit.putInt("interfaceVibrationsScrollIntensity", 50); + edit.putInt("interfaceVibrationsScrollIntensity", 25); edit.putInt("interfaceVibrationsScrollLength", 20); - edit.putInt("interfaceVibrationsSpecialIntensity", 100); + edit.putInt("interfaceVibrationsSpecialIntensity", 50); edit.putInt("interfaceVibrationsSpecialLength", 50); edit.putFloat("interfaceLayoutHeight", 0.3f); @@ -73,33 +83,7 @@ private static void defaultInterface(SharedPreferences preferences) { * @param preferences preferences to write in */ private static void defaultMovement(SharedPreferences preferences) { - SharedPreferences.Editor edit = preferences.edit(); - - edit.putFloat("movementSensitivity", 13); - - edit.putBoolean("movementScaleEnable", true); - - edit.putBoolean("movementSamplingCalibrated", false); - edit.putInt("movementSamplingRealRate", 200); - - edit.putInt("movementLowPassOrder", 1); - edit.putFloat("movementLowPassCutoff", 0.05f); - - edit.putFloat("movementFreezerFreezingThreshold", 0.1f); - edit.putFloat("movementFreezerUnfreezingThreshold", 0.04f); - edit.putInt("movementFreezerUnfreezingSamples", 10); - - edit.putFloat("movementNoiseThreshold", 0.04f); - edit.putInt("movementNoiseResetSamples", 20); - - edit.putInt("movementCacheDurationMinimal", 5); - edit.putInt("movementCacheDurationMaximal", 10); - edit.putFloat("movementCacheReleaseThreshold", 0.05f); - - edit.putInt("movementScalePower", 2); - edit.putFloat("movementScaleSplit", 0.1f); - - edit.apply(); + new Parameters(preferences).reset(); } /** diff --git a/app/src/main/java/ch/virt/smartphonemouse/customization/SamplingRateCalibrator.java b/app/src/main/java/ch/virt/smartphonemouse/customization/SamplingRateCalibrator.java deleted file mode 100644 index df75a03..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/customization/SamplingRateCalibrator.java +++ /dev/null @@ -1,166 +0,0 @@ -package ch.virt.smartphonemouse.customization; - -import android.content.Context; -import android.content.SharedPreferences; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; - -import androidx.preference.PreferenceManager; - -import java.util.Timer; -import java.util.TimerTask; - -import ch.virt.smartphonemouse.mouse.MovementHandler; - -/** - * This class is used to measure and save the sampling rate of the inbuilt accelerometer. - */ -public class SamplingRateCalibrator implements SensorEventListener { - - private static final int TEST_LENGTH = 5000; - private static final float NANO_FULL_FACTOR = 1e-9f; - - private SensorManager manager; - private Sensor sensor; - private boolean registered; - - private boolean running; - private long lastTime; - private long delays; - private int amount; - - private final Context context; - - /** - * Creates the calibrator. - * - * @param context context to use - */ - public SamplingRateCalibrator(Context context) { - this.context = context; - - fetchSensor(); - } - - /** - * Fetches the sensor from the system. - */ - private void fetchSensor() { - manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - sensor = manager.getDefaultSensor(MovementHandler.SENSOR_TYPE); - } - - /** - * Registers itself as a listener. - */ - private void register() { - if (registered) return; - manager.registerListener(this, sensor, MovementHandler.SAMPLING_RATE); - - registered = true; - } - - /** - * Unregisters itself as a listener. - */ - private void unregister() { - if (!registered) return; - manager.unregisterListener(this, sensor); - - registered = false; - } - - /** - * Starts the measuring process. - * - * @param doneListener listener that is executed once the process has finished - */ - public void calibrate(DoneListener doneListener) { - register(); - prepareTest(); - running = true; - - new Timer().schedule(new TimerTask() { - @Override - public void run() { - running = false; - unregister(); - - int rate = finishTest(); - - doneListener.done(rate); - } - }, TEST_LENGTH); - - } - - /** - * Initializes the variables for the process. - */ - private void prepareTest() { - lastTime = 0; - delays = 0; - amount = 0; - } - - /** - * Finishes the measuring process by processing the results and saving it into the preferences. - */ - private int finishTest() { - long averageDelay = delays / amount; - float averageDelaySecond = averageDelay * NANO_FULL_FACTOR; - - int samplesPerSecond = Math.round(1f / averageDelaySecond); - - SharedPreferences.Editor edit = PreferenceManager.getDefaultSharedPreferences(context).edit(); - edit.putBoolean("movementSamplingCalibrated", true); - edit.putInt("movementSamplingRealRate", samplesPerSecond); - edit.apply(); - - return samplesPerSecond; - } - - /** - * Returns how long the measuring process approximately will go. - * - * @return length of the process in milliseconds - */ - public int getTestLength() { - return TEST_LENGTH; - } - - @Override - public void onSensorChanged(SensorEvent event) { - if (!running) return; - if (lastTime == 0) { - lastTime = event.timestamp; - return; - } - - long delay = event.timestamp - lastTime; - - amount++; - delays += delay; - - lastTime = event.timestamp; - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - } - - /** - * This interface is a listener used for when the measuring process is done. - */ - public interface DoneListener { - - /** - * Called when the process is done. - * - * @param samplingRate sampling rate that was measured - */ - void done(int samplingRate); - } -} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/Calibration.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/Calibration.java new file mode 100644 index 0000000..ce80f8c --- /dev/null +++ b/app/src/main/java/ch/virt/smartphonemouse/mouse/Calibration.java @@ -0,0 +1,130 @@ +package ch.virt.smartphonemouse.mouse; + +import android.util.Log; +import ch.virt.smartphonemouse.mouse.components.WindowAverage; +import ch.virt.smartphonemouse.mouse.math.Vec3f; + +import java.util.ArrayList; +import java.util.List; + +public class Calibration { + + private static final String TAG = "Calibration"; + + public static final int STATE_START = 0; + public static final int STATE_SAMPLING = 1; + public static final int STATE_NOISE = 3; + public static final int STATE_END = 4; + + + int state = STATE_START; + private StateListener listener; + + boolean started = false; + float startTime = 0; + + private int samples; + + private List accelerationNoise; + private List rotationNoise; + private WindowAverage gravityAverage; + private WindowAverage noiseAverage; + + private final Parameters params; + + private float durationSampling; + private float durationNoise; + + public Calibration(StateListener listener, Parameters params) { + this.listener = listener; + this.params = params; + } + + public void setListener(StateListener listener) { + this.listener = listener; + } + + public void startCalibration() { + Log.d(TAG, "Starting Sampling Rate Calibration"); + samples = 0; + this.durationSampling = params.getCalibrationSamplingTime(); + + updateState(STATE_SAMPLING); + } + + private void startNoise() { + Log.d(TAG, "Starting Noise Level Calibration"); + + float samplingRate = samples / durationSampling; + params.calibrateSamplingRate(samplingRate); + + accelerationNoise = new ArrayList<>(); + rotationNoise = new ArrayList<>(); + + gravityAverage = new WindowAverage(params.getLengthWindowGravity()); + noiseAverage = new WindowAverage(params.getLengthWindowNoise()); + + this.durationNoise = params.getCalibrationNoiseTime(); + + updateState(STATE_NOISE); + } + + private void endCalibration() { + Log.d(TAG, "Ending Calibration"); + + params.calibrateNoiseLevels(accelerationNoise, rotationNoise); + params.setCalibrated(true); + updateState(STATE_END); + } + + public void data(float time, Vec3f acceleration, Vec3f angularVelocity) { + if (!started) { + startTime = time; + started = true; + } + + if (state == STATE_SAMPLING) { + + if (time - startTime > durationSampling) { + startNoise(); + } + + samples++; + + + } else if (state == STATE_NOISE) { + + if (time - startTime > durationNoise) { + endCalibration(); + } + + float acc = acceleration.xy().abs(); + + // Remove gravity or rather lower frequencies + float gravity = gravityAverage.avg(acc); + acc -= gravity; + acc = Math.abs(acc); + + // Remove noise + acc = noiseAverage.avg(acc); + + // Calculate the rotation activation + float rot = Math.abs(angularVelocity.z); + + accelerationNoise.add(acc); + rotationNoise.add(rot); + } + } + + private void updateState(int state) { + this.state = state; + started = false; + + listener.update(state); + } + + public interface StateListener { + void update(int state); + } + +} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/MovementHandler.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/MovementHandler.java index ef13718..564b2f5 100644 --- a/app/src/main/java/ch/virt/smartphonemouse/mouse/MovementHandler.java +++ b/app/src/main/java/ch/virt/smartphonemouse/mouse/MovementHandler.java @@ -5,8 +5,10 @@ import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; - import androidx.preference.PreferenceManager; +import ch.virt.smartphonemouse.mouse.math.Vec2f; +import ch.virt.smartphonemouse.mouse.math.Vec3f; +import ch.virt.smartphonemouse.transmission.DebugTransmitter; /** * This class handles and calculates the movement of the mouse @@ -15,19 +17,24 @@ public class MovementHandler implements SensorEventListener { private static final float NANO_FULL_FACTOR = 1e-9f; - public static final int SENSOR_TYPE = Sensor.TYPE_ACCELEROMETER; + public static final int SENSOR_TYPE_ACCELEROMETER = Sensor.TYPE_ACCELEROMETER; + public static final int SENSOR_TYPE_GYROSCOPE = Sensor.TYPE_GYROSCOPE; public static final int SAMPLING_RATE = SensorManager.SENSOR_DELAY_FASTEST; private SensorManager manager; - private Sensor sensor; - - private Context context; - private MouseInputs inputs; + private Sensor accelerometer; + private Sensor gyroscope; private boolean registered; - private long lastSample = 0; - private Pipeline xLine, yLine; + + private Vec3f gyroSample = new Vec3f(); // TODO: Make this a buffer to accommodate for vastly different sampling rates + private long lastTime = 0; + private long firstTime = 0; + private Processing processing; + + private final Context context; + private final MouseInputs inputs; /** * Creates a movement handler. @@ -40,17 +47,13 @@ public MovementHandler(Context context, MouseInputs inputs) { this.inputs = inputs; fetchSensor(); - create(); } /** * Creates the signal processing pipelines. */ - public void create() { - int sampleRate = PreferenceManager.getDefaultSharedPreferences(context).getInt("communicationTransmissionRate", 200); - - xLine = new Pipeline(sampleRate, new PipelineConfig(PreferenceManager.getDefaultSharedPreferences(context))); - yLine = new Pipeline(sampleRate, new PipelineConfig(PreferenceManager.getDefaultSharedPreferences(context))); + public void create(DebugTransmitter debug) { + processing = new Processing(debug, new Parameters(PreferenceManager.getDefaultSharedPreferences(context))); } /** @@ -58,7 +61,8 @@ public void create() { */ private void fetchSensor() { manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - sensor = manager.getDefaultSensor(SENSOR_TYPE); + accelerometer = manager.getDefaultSensor(SENSOR_TYPE_ACCELEROMETER); + gyroscope = manager.getDefaultSensor(SENSOR_TYPE_GYROSCOPE); } /** @@ -66,9 +70,11 @@ private void fetchSensor() { */ public void register() { if (registered) return; - manager.registerListener(this, sensor, SAMPLING_RATE); + manager.registerListener(this, accelerometer, SAMPLING_RATE); + manager.registerListener(this, gyroscope, SAMPLING_RATE); - lastSample = 0; + lastTime = 0; + firstTime = 0; registered = true; } @@ -77,7 +83,8 @@ public void register() { */ public void unregister() { if (!registered) return; - manager.unregisterListener(this, sensor); + manager.unregisterListener(this, accelerometer); + manager.unregisterListener(this, gyroscope); registered = false; } @@ -85,18 +92,29 @@ public void unregister() { @Override public void onSensorChanged(SensorEvent event) { if (!registered) return; // Ignore Samples when the listener is not registered - if (lastSample == 0) { // Ignore First sample, because there is no delta - lastSample = event.timestamp; - return; - } - float delta = (event.timestamp - lastSample) * NANO_FULL_FACTOR; // Delta in Seconds + if (event.sensor.getType() == SENSOR_TYPE_ACCELEROMETER) { + + if (firstTime == 0) { // Ignore First sample, because there is no delta + lastTime = event.timestamp; + firstTime = event.timestamp; + return; + } + + float delta = (event.timestamp - lastTime) * NANO_FULL_FACTOR; + float time = (event.timestamp - firstTime) * NANO_FULL_FACTOR; + Vec3f acceleration = new Vec3f(event.values[0], event.values[1], event.values[2]); - // Calculate and Submit values - inputs.changeXPosition(xLine.nextForDistance(delta, event.values[0])); - inputs.changeYPosition(-yLine.nextForDistance(delta, event.values[1])); + Vec2f distance = processing.next(time, delta, acceleration, gyroSample); + inputs.changeXPosition(distance.x); + inputs.changeYPosition(-distance.y); - lastSample = event.timestamp; + lastTime = event.timestamp; + + } else if (event.sensor.getType() == SENSOR_TYPE_GYROSCOPE) { + // Here we assume that the samples arrive in chronological order (which is crucial anyway), so we will always have the latest sample in this variable + this.gyroSample = new Vec3f(event.values[0], event.values[1], event.values[2]); + } } @Override diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/Parameters.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/Parameters.java new file mode 100644 index 0000000..e676ed5 --- /dev/null +++ b/app/src/main/java/ch/virt/smartphonemouse/mouse/Parameters.java @@ -0,0 +1,143 @@ +package ch.virt.smartphonemouse.mouse; + +import android.content.SharedPreferences; +import android.util.Log; + +import java.util.List; + +public class Parameters { + + private static final String TAG = "Parameters"; + + private static final float CALIBRATION_SAMPLING = 5; + private static final float CALIBRATION_NOISE = 10; + + private static final float NOISE_RATIO_ACCELERATION = 1f; + private static final float NOISE_RATIO_ROTATION = 1f; + private static final float NOISE_FACTOR_ACCELERATION = 1.2f; + private static final float NOISE_FACTOR_ROTATION = 1.2f; + + private static final float DURATION_THRESHOLD = 0.1f; + private static final float DURATION_WINDOW_GRAVITY = 0.02f; + private static final float DURATION_WINDOW_NOISE = 0.01f; + private static final float DURATION_GRAVITY = 2f; + + private static final float SENSITIVITY = 15000f; + + // Disable this part of the processing because it doesn't work as it should for some reason + private static final boolean ENABLE_GRAVITY_ROTATION = false; + + private final SharedPreferences prefs; + + public Parameters(SharedPreferences preferences) { + this.prefs = preferences; + } + + public void reset() { + SharedPreferences.Editor edit = prefs.edit(); + + edit.putBoolean("movementCalibrated", false); + + edit.putFloat("movementSensitivity", SENSITIVITY); + + edit.putFloat("movementCalibrationSampling", CALIBRATION_SAMPLING); + edit.putFloat("movementCalibrationNoise", CALIBRATION_NOISE); + + edit.putFloat("movementNoiseRatioAcceleration", NOISE_RATIO_ACCELERATION); + edit.putFloat("movementNoiseRatioRotation", NOISE_RATIO_ROTATION); + edit.putFloat("movementNoiseFactorAcceleration", NOISE_FACTOR_ACCELERATION); + edit.putFloat("movementNoiseFactorRotation", NOISE_FACTOR_ROTATION); + + edit.putFloat("movementDurationThreshold", DURATION_THRESHOLD); + edit.putFloat("movementDurationWindowGravity", DURATION_WINDOW_GRAVITY); + edit.putFloat("movementDurationWindowNoise", DURATION_WINDOW_NOISE); + edit.putFloat("movementDurationGravity", DURATION_GRAVITY); + + edit.putBoolean("movementEnableGravityRotation", ENABLE_GRAVITY_ROTATION); + + edit.apply(); + } + + public void setCalibrated(boolean calibrated) { + prefs.edit().putBoolean("movementCalibrated", calibrated).apply(); + } + + public boolean isCalibrated() { + return prefs.getBoolean("movementCalibrated", false); + } + + public float getCalibrationNoiseTime() { + return prefs.getFloat("movementCalibrationNoise", CALIBRATION_NOISE); + } + + public float getCalibrationSamplingTime() { + return prefs.getFloat("movementCalibrationSampling", CALIBRATION_SAMPLING); + } + + public void calibrateSamplingRate(float samplingRate) { + SharedPreferences.Editor edit = prefs.edit(); + + edit.putFloat("movementSampling", samplingRate); + + edit.apply(); + } + + public void calibrateNoiseLevels(List accelerationNoise, List rotationNoise) { + + // Sort arrays and get at ratio (same as removing top X% and getting the largest) + accelerationNoise.sort(Float::compareTo); + rotationNoise.sort(Float::compareTo); + + float accelerationSample = accelerationNoise.get((int) ((accelerationNoise.size() - 1) * prefs.getFloat("movementNoiseRatioAcceleration", NOISE_RATIO_ACCELERATION))); + float rotationSample = rotationNoise.get((int) ((rotationNoise.size() - 1) * prefs.getFloat("movementNoiseRatioRotation", NOISE_RATIO_ROTATION))); + + // Multiply factors + accelerationSample *= prefs.getFloat("movementNoiseFactorAcceleration", NOISE_FACTOR_ACCELERATION); + rotationSample *= prefs.getFloat("movementNoiseFactorRotation", NOISE_FACTOR_ROTATION); + + // Persist + SharedPreferences.Editor edit = prefs.edit(); + + edit.putFloat("movementThresholdAcceleration", accelerationSample); + edit.putFloat("movementThresholdRotation", rotationSample); + + edit.apply(); + } + + public int getLengthWindowGravity() { + return Math.round(prefs.getFloat("movementDurationWindowGravity", DURATION_WINDOW_GRAVITY) + * prefs.getFloat("movementSampling", 500)); + } + + public int getLengthWindowNoise() { + return Math.round(prefs.getFloat("movementDurationWindowNoise", DURATION_WINDOW_NOISE) + * prefs.getFloat("movementSampling", 500)); + } + + public int getLengthThreshold() { + return Math.round(prefs.getFloat("movementDurationThreshold", DURATION_THRESHOLD) + * prefs.getFloat("movementSampling", 500)); + } + + public int getLengthGravity() { + return Math.round(prefs.getFloat("movementDurationGravity", DURATION_GRAVITY) + * prefs.getFloat("movementSampling", 500)); + } + + public float getSensitivity() { + return prefs.getFloat("movementSensitivity", SENSITIVITY); + } + + public float getThresholdAcceleration() { + return prefs.getFloat("movementThresholdAcceleration", 0.03f); + } + + public float getThresholdRotation() { + return prefs.getFloat("movementThresholdRotation", 0.01f); + } + + public boolean getEnableGravityRotation() { + return prefs.getBoolean("movementEnableGravityRotation", ENABLE_GRAVITY_ROTATION); + } + +} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/Pipeline.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/Pipeline.java deleted file mode 100644 index b687f03..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/mouse/Pipeline.java +++ /dev/null @@ -1,137 +0,0 @@ -package ch.virt.smartphonemouse.mouse; - -import ch.virt.smartphonemouse.mouse.elements.Freezer; -import ch.virt.smartphonemouse.mouse.elements.Integration; -import ch.virt.smartphonemouse.mouse.elements.LowPassFilter; -import ch.virt.smartphonemouse.mouse.elements.NoiseCancelling; -import ch.virt.smartphonemouse.mouse.elements.PersistentIntegration; -import ch.virt.smartphonemouse.mouse.elements.Scaler; -import ch.virt.smartphonemouse.mouse.elements.Sensitivity; -import ch.virt.smartphonemouse.mouse.elements.SignCache; - -/** - * This class handles all calculation to get from the raw sensor data to the distance output. - * In order to do the calculation it uses various different components. - */ -public class Pipeline { - - private int sampleRate; - private PipelineConfig config; - - private LowPassFilter lowPass; - private Freezer freezer; - private NoiseCancelling noise; - private PersistentIntegration velocityIntegration; - private Integration distanceIntegration; - private SignCache cache; - private Scaler scaler; - private Sensitivity sensitivity; - - private boolean debugging; - private float[] debuggingValues; - - /** - * Creates a signal processing pipeline. - * - * @param sampleRate sample rate at which the new samples will be provided - * @param config configuration for all different components of this pipeline - */ - public Pipeline(int sampleRate, PipelineConfig config) { - this.config = config; - this.sampleRate = sampleRate; - create(); - } - - /** - * Initializes all components. - */ - private void create() { - - velocityIntegration = new PersistentIntegration(); - distanceIntegration = new Integration(); - - lowPass = new LowPassFilter(config.getLowPassOrder(), sampleRate, config.getLowPassCutoff()); - - freezer = new Freezer(config.getFreezerFreezingThreshold(), config.getFreezerUnfreezingThreshold(), config.getFreezerUnfreezingSamples()); - - noise = new NoiseCancelling(config.getNoiseCancellingThreshold(), config.getNoiseFinalSamples(), velocityIntegration); - - cache = new SignCache(config.getCacheMinimalDuration(), config.getCacheMaximalDuration(), config.getCacheReleasingThreshold(), velocityIntegration, distanceIntegration); - - scaler = new Scaler(config.isScalerEnabled(), config.getScalerPower(), config.getScalerSplit()); - - sensitivity = new Sensitivity(config.getSensitivityFactor()); - - debuggingValues = new float[12]; - } - - /** - * Calculates the current distance delta from a new acceleration sample - * - * @param delta time passed since the last sample (in seconds) - * @param unfiltered new acceleration sample from the accelerometer - * @return new distance delta - */ - public float nextForDistance(float delta, float unfiltered) { - int i = 0; - if (debugging) debuggingValues[i++] = unfiltered; - - float subtract = (float) lowPass.filter(unfiltered); // Low pass for subtraction base - if (debugging) debuggingValues[i++] = subtract; - subtract = freezer.next(subtract, unfiltered); // Freeze if necessary - if (debugging) debuggingValues[i++] = subtract; - - float acceleration = unfiltered - subtract; // Subtract the Low Passed and frozen - if (debugging) debuggingValues[i++] = acceleration; - acceleration = noise.cancel(acceleration); // Cancel the acceleration if too low - if (debugging) debuggingValues[i++] = acceleration; - - float velocity = velocityIntegration.integrate(delta, acceleration); // Integrate velocity - if (debugging) debuggingValues[i++] = velocity; - velocity = cache.velocity(velocity, acceleration); // Do first caching action - if (debugging) debuggingValues[i++] = velocity; - velocity = noise.velocity(velocity); - if (debugging) debuggingValues[i++] = velocity; - velocity = scaler.scale(velocity); // Scale velocity - if (debugging) debuggingValues[i++] = velocity; - - float distance = distanceIntegration.integrate(delta, velocity); // Integrate distance - if (debugging) debuggingValues[i++] = distance; - distance = cache.distance(distance, delta); // Do second caching action - if (debugging) debuggingValues[i++] = distance; - distance = sensitivity.scale(distance); // Scale distance to its sensitivity - if (debugging) debuggingValues[i++] = distance; - - return distance; - } - - /** - * Enables debugging for this pipeline. - * This will place the values in between the acceleration and distance into an array for every sample. - */ - public void enableDebugging() { - debugging = true; - } - - /** - * Returns all values in between the acceleration and distance in an array. - * Make sure to enable debugging to get content in this array. - * - * @return values in between - */ - public float[] getDebuggingValues() { - return debuggingValues; - } - - /** - * Resets the pipeline to its initial state. - */ - public void reset() { - lowPass.reset(); - freezer.reset(); - noise.reset(); - velocityIntegration.reset(); - cache.reset(); - distanceIntegration.reset(); - } -} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/PipelineConfig.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/PipelineConfig.java deleted file mode 100644 index 3f614b4..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/mouse/PipelineConfig.java +++ /dev/null @@ -1,118 +0,0 @@ -package ch.virt.smartphonemouse.mouse; - -import android.content.SharedPreferences; - -/** - * This class loads and contains all configuration data needed to configure a single pipeline. - */ -public class PipelineConfig { - - // Acceleration Low Pass - private int lowPassOrder; - private float lowPassCutoff; - - // Acceleration Freezer - private float freezerFreezingThreshold; - private float freezerUnfreezingThreshold; - private int freezerUnfreezingSamples; - - // Noise Cancelling - private float noiseCancellingThreshold; - private int noiseFinalSamples; - - // Sign Cache - private int cacheMinimalDuration; - private int cacheMaximalDuration; - private float cacheReleasingThreshold; - - // Scaler - private boolean scalerEnabled; - private int scalerPower; - private float scalerSplit; - - // Sensitivity - private float sensitivityFactor; - - /** - * Loads all config entries into this class. - * - * @param preferences preferences to load from. - */ - public PipelineConfig(SharedPreferences preferences) { - lowPassOrder = preferences.getInt("movementLowPassOrder", 1); - lowPassCutoff = preferences.getFloat("movementLowPassCutoff", 0.1f); - - freezerFreezingThreshold = preferences.getFloat("movementFreezerFreezingThreshold", 0.1f); - freezerUnfreezingThreshold = preferences.getFloat("movementFreezerUnfreezingThreshold", 0.04f); - freezerUnfreezingSamples = preferences.getInt("movementFreezerUnfreezingSamples", 10); - - noiseCancellingThreshold = preferences.getFloat("movementNoiseThreshold", 0.04f); - noiseFinalSamples = preferences.getInt("movementNoiseResetSamples", 20); - - cacheMinimalDuration = preferences.getInt("movementCacheDurationMinimal", 5); - cacheMaximalDuration = preferences.getInt("movementCacheDurationMaximal", 10); - cacheReleasingThreshold = preferences.getFloat("movementCacheReleaseThreshold", 0.05f); - - scalerEnabled = preferences.getBoolean("movementScaleEnable", true); - scalerPower = preferences.getInt("movementScalePower", 2); - scalerSplit = preferences.getFloat("movementScaleSplit", 0.1f); - - sensitivityFactor = preferences.getFloat("movementSensitivity", 13); - } - - public int getLowPassOrder() { - return lowPassOrder; - } - - public float getLowPassCutoff() { - return lowPassCutoff; - } - - public float getFreezerFreezingThreshold() { - return freezerFreezingThreshold; - } - - public float getFreezerUnfreezingThreshold() { - return freezerUnfreezingThreshold; - } - - public int getFreezerUnfreezingSamples() { - return freezerUnfreezingSamples; - } - - public float getNoiseCancellingThreshold() { - return noiseCancellingThreshold; - } - - public int getNoiseFinalSamples() { - return noiseFinalSamples; - } - - public int getCacheMinimalDuration() { - return cacheMinimalDuration; - } - - public int getCacheMaximalDuration() { - return cacheMaximalDuration; - } - - public float getCacheReleasingThreshold() { - return cacheReleasingThreshold; - } - - public int getScalerPower() { - return scalerPower; - } - - public float getScalerSplit() { - return scalerSplit; - } - - public boolean isScalerEnabled() { - return scalerEnabled; - } - - public float getSensitivityFactor() { - return sensitivityFactor; - } -} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/Processing.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/Processing.java new file mode 100644 index 0000000..46a6ad5 --- /dev/null +++ b/app/src/main/java/ch/virt/smartphonemouse/mouse/Processing.java @@ -0,0 +1,169 @@ +package ch.virt.smartphonemouse.mouse; + +import ch.virt.smartphonemouse.mouse.components.*; +import ch.virt.smartphonemouse.mouse.math.Vec2f; +import ch.virt.smartphonemouse.mouse.math.Vec3f; +import ch.virt.smartphonemouse.transmission.DebugTransmitter; + +public class Processing { + + private Trapezoid3f rotationDeltaTrapezoid; + + private WindowAverage activeGravityAverage; + private WindowAverage activeNoiseAverage; + private OrThreshold activeThreshold; + private boolean lastActive; + + private WindowAverage3f gravityInactiveAverage; + private Vec3f gravityCurrent; + + private Trapezoid2f distanceVelocityTrapezoid; + private Vec2f distanceVelocity; + private Trapezoid2f distanceDistanceTrapezoid; + private float sensitivity; + + + // Gravity Rotation is disabled by default because it currently does not work as expected + private boolean enableGravityRotation; + + + private final DebugTransmitter debug; + + public Processing(DebugTransmitter debug, Parameters parameters) { + this.debug = debug; + + // Create and configure components + rotationDeltaTrapezoid = new Trapezoid3f(); + + activeGravityAverage = new WindowAverage(parameters.getLengthWindowGravity()); + activeNoiseAverage = new WindowAverage(parameters.getLengthWindowNoise()); + activeThreshold = new OrThreshold(parameters.getLengthThreshold(), parameters.getThresholdAcceleration(), parameters.getThresholdRotation()); + + gravityInactiveAverage = new WindowAverage3f(parameters.getLengthGravity()); + gravityCurrent = new Vec3f(); + + distanceVelocityTrapezoid = new Trapezoid2f(); + distanceDistanceTrapezoid = new Trapezoid2f(); + distanceVelocity = new Vec2f(); + sensitivity = parameters.getSensitivity(); + + enableGravityRotation = parameters.getEnableGravityRotation(); + } + + public static void registerDebugColumns(DebugTransmitter debug) { + debug.registerColumn("time", Float.class); + debug.registerColumn("acceleration", Vec3f.class); + debug.registerColumn("angular-velocity", Vec3f.class); + debug.registerColumn("active-acc-abs", Float.class); + debug.registerColumn("active-acc-grav", Float.class); + debug.registerColumn("active-acc", Float.class); + debug.registerColumn("active-rot", Float.class); + debug.registerColumn("active", Boolean.class); + debug.registerColumn("gravity", Vec3f.class); + debug.registerColumn("acceleration-linear", Vec2f.class); + debug.registerColumn("velocity", Vec2f.class); + debug.registerColumn("distance", Vec2f.class); + } + + public Vec2f next(float time, float delta, Vec3f acceleration, Vec3f angularVelocity) { + // Stage debug values + debug.stageFloat(time); + debug.stageVec3f(acceleration); + debug.stageVec3f(angularVelocity); + + // Integrate rotation to distance, since that is used more often + Vec3f rotationDelta = rotationDeltaTrapezoid.trapezoid(delta, angularVelocity); + + boolean active = active(acceleration, angularVelocity); + debug.stageBoolean(active); + + Vec2f linearAcceleration = gravity(active, acceleration, rotationDelta); + debug.stageVec2f(linearAcceleration); + + Vec2f distance = distance(delta, active, linearAcceleration, rotationDelta); + debug.stageVec2f(distanceVelocity); // Do this here because it did not fit into the method + debug.stageVec2f(distance); + + // Handle active changes "globally" for optimization + lastActive = active; + + debug.commit(); + return distance; + } + + public boolean active(Vec3f acceleration, Vec3f angularVelocity) { + + // Calculate the acceleration activation + float acc = acceleration.xy().abs(); + debug.stageFloat(acc); + + // Remove gravity or rather lower frequencies + float gravity = activeGravityAverage.avg(acc); + debug.stageFloat(gravity); + acc -= gravity; + acc = Math.abs(acc); + + // Remove noise + acc = activeNoiseAverage.avg(acc); + debug.stageFloat(acc); + + // Calculate the rotation activation + float rot = Math.abs(angularVelocity.z); + debug.stageFloat(rot); + + // Do the threshold + return activeThreshold.active(acc, rot); + } + + public Vec2f gravity(boolean active, Vec3f acceleration, Vec3f rotationDelta) { + + // Differentiate between the user being active or not + if (active) { + + // Reset average for next phase + if (!lastActive) { + gravityInactiveAverage.reset(); + } + + // Rotate current gravity + if (enableGravityRotation) gravityCurrent.rotate(rotationDelta.copy().negative()); + + } else { + // Just calculate the average of the samples + gravityCurrent = gravityInactiveAverage.avg(acceleration); + } + + debug.stageVec3f(gravityCurrent); + + // Subtract the gravity + return acceleration.xy().subtract(gravityCurrent.xy()); + } + + public Vec2f distance(float delta, boolean active, Vec2f linearAcceleration, Vec3f rotationDelta) { + + // Only calculate if it is active for optimization + if (active){ + + // Counter-rotate the velocity + distanceVelocity.rotate(-rotationDelta.z); + + // Integrate to distance + distanceVelocity.add(distanceVelocityTrapezoid.trapezoid(delta, linearAcceleration)); + return distanceDistanceTrapezoid.trapezoid(delta, distanceVelocity).multiply(sensitivity); + + } else { + + // Reset stuff + if (lastActive) { + distanceVelocity = new Vec2f(); + + // Clean the trapezoids because they contain a last value + distanceVelocityTrapezoid.reset(); + distanceDistanceTrapezoid.reset(); + } + + return new Vec2f(); + } + } + +} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/components/OrThreshold.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/components/OrThreshold.java new file mode 100644 index 0000000..ed166d1 --- /dev/null +++ b/app/src/main/java/ch/virt/smartphonemouse/mouse/components/OrThreshold.java @@ -0,0 +1,42 @@ +package ch.virt.smartphonemouse.mouse.components; + +/** + * This class represents a threshold which operates based on two values. + * It uses the values in an OR fashion, activating when one or both values surpass their threshold. + * It also takes a time to drop off, after the last active value pair. + */ +public class OrThreshold { + + private final int dropoff; + private final float firstThreshold, secondThreshold; + + private int lastActive; + + /** + * @param dropoff amount of samples it takes from the last active sample to be inactive + * @param firstThreshold threshold the first value has to surpass + * @param secondThreshold threshold the second value has to surpass + */ + public OrThreshold(int dropoff, float firstThreshold, float secondThreshold) { + this.dropoff = dropoff; + this.firstThreshold = firstThreshold; + this.secondThreshold = secondThreshold; + + this.lastActive = dropoff; + } + + /** + * Takes two new values and determines whether the threshold is activated + * @param first first value + * @param second second value + * @return whether the threshold is active + */ + public boolean active(float first, float second) { + if (first > firstThreshold || second > secondThreshold) lastActive = 0; + else lastActive++; + + return lastActive <= dropoff; + } + + +} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/components/Trapezoid2f.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/components/Trapezoid2f.java new file mode 100644 index 0000000..39e784f --- /dev/null +++ b/app/src/main/java/ch/virt/smartphonemouse/mouse/components/Trapezoid2f.java @@ -0,0 +1,31 @@ +package ch.virt.smartphonemouse.mouse.components; + +import ch.virt.smartphonemouse.mouse.math.Vec2f; + +/** + * This class is used to calculate a number of following trapezoids. Here with a Vec2 as a value + */ +public class Trapezoid2f { + + private Vec2f last = new Vec2f(); + + /** + * Calculates the current trapezoid + * @param delta delta time to the last value + * @param next current value + * @return trapezoid + */ + public Vec2f trapezoid(float delta, Vec2f next) { + Vec2f result = last.mean(next).multiply(delta); + last = next; + return result; + } + + /** + * Resets the last value + */ + public void reset() { + last = new Vec2f(); + } + +} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/components/Trapezoid3f.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/components/Trapezoid3f.java new file mode 100644 index 0000000..55db09f --- /dev/null +++ b/app/src/main/java/ch/virt/smartphonemouse/mouse/components/Trapezoid3f.java @@ -0,0 +1,31 @@ +package ch.virt.smartphonemouse.mouse.components; + +import ch.virt.smartphonemouse.mouse.math.Vec3f; + +/** + * This class is used to calculate a number of following trapezoids. Here with a Vec3 as a value + */ +public class Trapezoid3f { + + private Vec3f last = new Vec3f(); + + /** + * Calculates the current trapezoid + * @param delta delta time to the last value + * @param next current value + * @return trapezoid + */ + public Vec3f trapezoid(float delta, Vec3f next) { + Vec3f result = last.mean(next).multiply(delta); + last = next; + return result; + } + + /** + * Resets the last value + */ + public void reset() { + last = new Vec3f(); + } + +} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/components/WindowAverage.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/components/WindowAverage.java new file mode 100644 index 0000000..0093a14 --- /dev/null +++ b/app/src/main/java/ch/virt/smartphonemouse/mouse/components/WindowAverage.java @@ -0,0 +1,44 @@ +package ch.virt.smartphonemouse.mouse.components; + +/** + * This class represents an average which operates in a window, remembering a given value of past samples to do the averaging with. + */ +public class WindowAverage { + + private int index; + private float[] elements; + + /** + * @param length length of the window in values + */ + public WindowAverage(int length) { + this.elements = new float[length]; + index = 0; + } + + /** + * Takes the next sample and calculates the current average + * @param next next sample + * @return current average, including next sample + */ + public float avg(float next) { + elements[index % elements.length] = next; + index++; + + float total = 0f; + int amount = Math.min(elements.length, index); + for (int i = 0; i < amount; i++) { + total += elements[i]; + } + + return total / amount; + } + + /** + * Resets the average + */ + public void reset() { + elements = new float[elements.length]; + index = 0; + } +} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/components/WindowAverage3f.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/components/WindowAverage3f.java new file mode 100644 index 0000000..a2e76a0 --- /dev/null +++ b/app/src/main/java/ch/virt/smartphonemouse/mouse/components/WindowAverage3f.java @@ -0,0 +1,49 @@ +package ch.virt.smartphonemouse.mouse.components; + +import ch.virt.smartphonemouse.mouse.math.Vec3f; + +/** + * This class represents an average which operates in a window, remembering a given value of past samples to do the averaging with. + * Here with a Vec3f as a value. + */ +public class WindowAverage3f { + + private int index; + private Vec3f[] elements; + + /** + * @param length length of the window in values + */ + public WindowAverage3f(int length) { + this.elements = new Vec3f[length]; + index = 0; + } + + /** + * Takes the next sample and calculates the current average + * @param next next sample + * @return current average, including next sample + */ + public Vec3f avg(Vec3f next) { + + elements[index % elements.length] = next; + index++; + + + Vec3f total = new Vec3f(); + int amount = Math.min(elements.length, index); + for (int i = 0; i < amount; i++) { + total.add(elements[i]); + } + + return total.divide(amount); + } + + /** + * Resets the average + */ + public void reset() { + elements = new Vec3f[elements.length]; + index = 0; + } +} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/Freezer.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/Freezer.java deleted file mode 100644 index 9064404..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/Freezer.java +++ /dev/null @@ -1,69 +0,0 @@ -package ch.virt.smartphonemouse.mouse.elements; - -/** - * This class holds the freezer component of the signal processing pipeline. - * The freezer ensures that the subtracted gravity gets occasionally frozen so that it is not affected by the movement of the smartphone. - */ -public class Freezer { - - private final float freezingThreshold; - private final float unfreezingThreshold; - private final int unfreezingSamples; - - private boolean frozen; - private int unfreezing; - private float subtract; - - /** - * Creates a freezer component. - * - * @param freezingThreshold how much acceleration is required for the freezer to kick in - * @param unfreezingThreshold how less acceleration is required for the freezing process to end - * @param unfreezingSamples how many samples in a row must be below said threshold - */ - public Freezer(float freezingThreshold, float unfreezingThreshold, int unfreezingSamples) { - this.freezingThreshold = freezingThreshold; - this.unfreezingThreshold = unfreezingThreshold; - this.unfreezingSamples = unfreezingSamples; - } - - /** - * Calculates the next sample - * - * @param lowPassed acceleration after the low pass filter (gravity) - * @param acceleration unfiltered acceleration - * @return new gravity value, that may be frozen - */ - public float next(float lowPassed, float acceleration) { - if (frozen) { - - if (Math.abs(acceleration - lowPassed) < unfreezingThreshold) - unfreezing++; // Count up if it may be unfrozen - else unfreezing = 0; - - if (unfreezing == unfreezingSamples) { // Unfreeze if enough samples in a row - frozen = false; - subtract = lowPassed; - } - - } else { - subtract = lowPassed; - - if (Math.abs(acceleration - subtract) > freezingThreshold) { // Freeze if below threshold - frozen = true; - unfreezing = 0; - } - } - - return subtract; - } - - /** - * Resets the current state of the freezer. - */ - public void reset(){ - frozen = false; - unfreezing = 0; - subtract = 0; - } -} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/Integration.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/Integration.java deleted file mode 100644 index a688524..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/Integration.java +++ /dev/null @@ -1,27 +0,0 @@ -package ch.virt.smartphonemouse.mouse.elements; - -/** - * This class handles basic integration that is not persistent and can is used for the signal processing. - */ -public class Integration { - - private float last; - - /** - * Integrates with the newly given sample. - * - * @param delta time delta since the last sample - * @param value new sample - * @return integrated delta - */ - public float integrate(float delta, float value) { - return ((last + (last = value)) / 2) * delta; - } - - /** - * Resets previously stored variables. - */ - public void reset() { - last = 0; - } -} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/LowPassFilter.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/LowPassFilter.java deleted file mode 100644 index 0f4cb91..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/LowPassFilter.java +++ /dev/null @@ -1,21 +0,0 @@ -package ch.virt.smartphonemouse.mouse.elements; - -import uk.me.berndporr.iirj.Bessel; - -/** - * This class wraps a bessel filter from the iirj library as a low pass filter and provides it for the signal processing. - */ -public class LowPassFilter extends Bessel { - - /** - * Creates a low pass filter. - * - * @param order order of the filter - * @param sampleRate sample rate with which samples are expected - * @param cutoff cutoff frequency of the filter - */ - public LowPassFilter(int order, int sampleRate, float cutoff) { - super(); - lowPass(order, sampleRate, cutoff); - } -} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/NoiseCancelling.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/NoiseCancelling.java deleted file mode 100644 index 6b359c3..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/NoiseCancelling.java +++ /dev/null @@ -1,65 +0,0 @@ -package ch.virt.smartphonemouse.mouse.elements; - -/** - * This class holds the noise cancelling component for the signal processing. - * The noise cancelling removes all amplitudes below a certain value. It also does support clearing the velocity if there has been only noise for a certain amount of time. - */ -public class NoiseCancelling { - - private final float cancellingThreshold; - private final int finalCancellingSamples; - - private final Integration velocityIntegration; - - private int noise; - - /** - * Creates a noise cancelling component. - * - * @param cancellingThreshold all signal below this threshold is regarded as noise - * @param finalCancellingSamples how many samples in a row must be noise in order to reset the velocity - * @param velocityIntegration integration of the velocity that may be reset - */ - public NoiseCancelling(float cancellingThreshold, int finalCancellingSamples, Integration velocityIntegration) { - this.cancellingThreshold = cancellingThreshold; - this.finalCancellingSamples = finalCancellingSamples; - this.velocityIntegration = velocityIntegration; - } - - /** - * Process the values that may be cancelled, presumably the acceleration. - * - * @param value current acceleration value - * @return possibly canceled value - */ - public float cancel(float value) { - - if (Math.abs(value) <= cancellingThreshold) { // Below Threshold - noise++; - return 0; - } else noise = 0; - - return value; - } - - /** - * Process the velocity to potentially reset it. - * - * @param value current velocity value - * @return possibly reset velocity - */ - public float velocity(float value) { - if (noise > finalCancellingSamples) { - velocityIntegration.reset(); - return 0; // Cancel if much noise - } - return value; - } - - /** - * Resets the noise cancelling. - */ - public void reset(){ - noise = 0; - } -} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/PersistentIntegration.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/PersistentIntegration.java deleted file mode 100644 index 2f024cb..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/PersistentIntegration.java +++ /dev/null @@ -1,24 +0,0 @@ -package ch.virt.smartphonemouse.mouse.elements; - -/** - * This class handles integration that is persistent (just like normal integration) - */ -public class PersistentIntegration extends Integration { - - private float whole; - - @Override - public float integrate(float delta, float value) { - - whole += super.integrate(delta, value); - return whole; - - } - - @Override - public void reset() { - super.reset(); - - whole = 0; - } -} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/Scaler.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/Scaler.java deleted file mode 100644 index 871f192..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/Scaler.java +++ /dev/null @@ -1,37 +0,0 @@ -package ch.virt.smartphonemouse.mouse.elements; - -/** - * This class is a component for the signal processing that scales the signal. - * The signal gets scaled so that low values get even lower, and high values even higher. - */ -public class Scaler { - - private final boolean enabled; - private final int power; - private final float split; - - /** - * Creates a scaler. - * - * @param enabled whether the scaler is enabled - * @param power the power of the function that is used for scaling - * @param split values over this value get higher, values lower than it get lower - */ - public Scaler(boolean enabled, int power, float split) { - this.enabled = enabled; - this.power = power; - this.split = split; - } - - /** - * Scales a value with the function. - * - * @param value value that is scaled - * @return the scaled value - */ - public float scale(float value) { - if (!enabled) return value; - - return (float) (Math.pow(Math.abs(value), power) * (split / Math.pow(split, power))) * (value > 0 ? 1 : -1); - } -} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/Sensitivity.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/Sensitivity.java deleted file mode 100644 index a28b8aa..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/Sensitivity.java +++ /dev/null @@ -1,45 +0,0 @@ -package ch.virt.smartphonemouse.mouse.elements; - -/** - * This component of the signal processing is used to scale the signal by a fixed factor. - * The factor of this scaling is made up of a base and a power, because (i think) humans do only feel exponential increase. - */ -public class Sensitivity { - - private final int base; - private final float power; - - private float factor; - - /** - * Creates a sensitivity component. - * - * @param base base of the factor - * @param power power of the factor - */ - public Sensitivity(int base, float power) { - this.base = base; - this.power = power; - - factor = (float) Math.pow(base, power); // Factor can be pre calculated since the base and power do not change - } - - /** - * Creates a sensitivity component with the base of two. - * - * @param power power of the factor - */ - public Sensitivity(float power) { - this(2, power); // Two, so one unit is double - } - - /** - * Scales the value by the factor. - * - * @param value value to be scaled - * @return scaled value - */ - public float scale(float value) { - return value * factor; - } -} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/SignCache.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/SignCache.java deleted file mode 100644 index 66d16f1..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/mouse/elements/SignCache.java +++ /dev/null @@ -1,110 +0,0 @@ -package ch.virt.smartphonemouse.mouse.elements; - -/** - * The sign cache component caches the velocity after the sign switches. If quickly after that, the acceleration rises drastically, the cached values get used again. - * This component is used to remove invalid movements that occur after a sign change. - */ -public class SignCache { - - private final int minimalDuration; - private final int maximalDuration; - private final float releasingThreshold; - - private final Integration velocityIntegration; - private final Integration distanceIntegration; - - private boolean caching; - private boolean latelyChanged; - private float lastValue; - private int duration; - private float cachedDistance; - - /** - * Creates a sign cache. - * - * @param minimalDuration minimal duration the signal gets cached - * @param maximalDuration maximal duration the signal gets cached - * @param releasingThreshold how high the acceleration has to be in order for the values in the cache to get used again - * @param velocityIntegration integration for the velocity that may be cleared - * @param distanceIntegration integration for the distance so the distance loss can be compensated - */ - public SignCache(int minimalDuration, int maximalDuration, float releasingThreshold, Integration velocityIntegration, Integration distanceIntegration) { - this.minimalDuration = minimalDuration; - this.maximalDuration = maximalDuration; - this.releasingThreshold = releasingThreshold; - this.velocityIntegration = velocityIntegration; - this.distanceIntegration = distanceIntegration; - } - - /** - * Potentially caches the velocity. - * - * @param value current velocity value - * @param acceleration current acceleration value - * @return possibly canceled velocity - */ - public float velocity(float value, float acceleration) { - if (value != 0 && lastValue != 0 && (lastValue > 0 != value > 0)) { // If sign switched start caching - caching = true; - duration = 0; - cachedDistance = 0; - } - - lastValue = value; - - if (caching) { - - if (duration >= minimalDuration) { - - if (Math.abs(acceleration) >= releasingThreshold) { // If releasing soon enough, just return to normal - caching = false; - latelyChanged = true; - return value; - } - - if (duration >= maximalDuration) { // If not released, reset velocity - velocityIntegration.reset(); - caching = false; - return 0; - } - } - - duration++; - return 0; - } - - return value; - } - - /** - * Potentially caches the moved distance. - * - * @param value current distance value - * @param delta time delta since the last value - * @return possibly cached distance - */ - public float distance(float value, float delta) { - if (caching) { // If caching, add values up - - cachedDistance += distanceIntegration.integrate(delta, lastValue); - - return 0; - } else if (latelyChanged) { // If changed to non caching, release distance Cache - latelyChanged = false; - - return value + cachedDistance; - - } else return value; - } - - /** - * Resets the cache to its defaults. - */ - public void reset() { - caching = false; - latelyChanged = false; - lastValue = 0; - duration = 0; - cachedDistance = 0; - } -} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/math/Vec2f.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/math/Vec2f.java new file mode 100644 index 0000000..7342e38 --- /dev/null +++ b/app/src/main/java/ch/virt/smartphonemouse/mouse/math/Vec2f.java @@ -0,0 +1,92 @@ +package ch.virt.smartphonemouse.mouse.math; + +/** + * This class represents a vector consisting of two floats. + */ +public class Vec2f { + public float x, y; + + public Vec2f() {} + public Vec2f(float x, float y) { + this.x = x; + this.y = y; + } + + /** + * Creates a copy of the vector + */ + public Vec2f copy() { + return new Vec2f(x, y); + } + + /** + * Returns the absolute value, aka. length of the vector + */ + public float abs() { + return (float) Math.sqrt(x*x + y*y); + } + + /** + * Adds another vector to this one + */ + public Vec2f add(Vec2f other) { + x += other.x; + y += other.y; + + return this; + } + + /** + * Adds another vector to this one + */ + public Vec2f subtract(Vec2f other) { + x -= other.x; + y -= other.y; + + return this; + } + + /** + * Multiplies or scales this vector by an amount + */ + public Vec2f multiply(float factor) { + x *= factor; + y *= factor; + + return this; + } + + /** + * Divides this vector by a given amount + */ + public Vec2f divide(float divisor) { + x /= divisor; + y /= divisor; + + return this; + } + + /** + * Calculates the average between this and a given other vector. Not affecting this instance. + */ + public Vec2f mean(Vec2f second) { + return this.copy().add(second).divide(2); + } + + /** + * Rotates this vector by a given rotation + * @param rotation rotation in radians + */ + public Vec2f rotate(float rotation) { + float c = (float) Math.cos(rotation); + float s = (float) Math.sin(rotation); + + float newX = c * x + (-s) * y; + float newY = s * x + c * y; + + this.x = newX; + this.y = newY; + + return this; + } +} diff --git a/app/src/main/java/ch/virt/smartphonemouse/mouse/math/Vec3f.java b/app/src/main/java/ch/virt/smartphonemouse/mouse/math/Vec3f.java new file mode 100644 index 0000000..7029457 --- /dev/null +++ b/app/src/main/java/ch/virt/smartphonemouse/mouse/math/Vec3f.java @@ -0,0 +1,134 @@ +package ch.virt.smartphonemouse.mouse.math; + +/** + * This class represents a three-dimensional vector, based on floats + */ +public class Vec3f { + public float x, y, z; + + public Vec3f() {} + public Vec3f(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Returns the x and y axes as a 2d vector + */ + public Vec2f xy() { + return new Vec2f(x, y); + } + + /** + * Returns the y and z axes as a 2d vector + */ + public Vec2f yz() { + return new Vec2f(y, z); + } + + /** + * Returns the x and z axes as a 2d vector + */ + public Vec2f xz() { + return new Vec2f(x, z); + } + + /** + * Returns the absolute value or length of the vector + */ + public float abs() { + return (float) Math.sqrt(x*x + y*y + z*z); + } + + /** + * Creates a copy of this vector + */ + public Vec3f copy() { + return new Vec3f(x, y, z); + } + + /** + * Adds another vector to this one + */ + public Vec3f add(Vec3f other) { + x += other.x; + y += other.y; + z += other.z; + + return this; + } + + /** + * Adds another vector to this one + */ + public Vec3f subtract(Vec3f other) { + x -= other.x; + y -= other.y; + z -= other.z; + + return this; + } + + /** + * Multiplies or scales this vector by an amount + */ + public Vec3f multiply(float factor) { + x *= factor; + y *= factor; + z *= factor; + + return this; + } + + /** + * Divides this vector by a given amount + */ + public Vec3f divide(float divisor) { + x /= divisor; + y /= divisor; + z /= divisor; + + return this; + } + + /** + * Negates this vector + */ + public Vec3f negative() { + return this.multiply(-1f); + } + + /** + * Calculates the average between this and a given other vector. Not affecting this instance. + */ + public Vec3f mean(Vec3f second) { + return this.copy().add(second).divide(2); + } + + /** + * Rotates this vector by all three axis by using euler angles in a xyz fashion + * @param rotation vector containing the rotation for each axis as radians + */ + public Vec3f rotate(Vec3f rotation) { + + // Calculate sines and cosines that are used (for optimization) + float sa = (float) Math.sin(rotation.x); + float ca = (float) Math.cos(rotation.x); + float sb = (float) Math.sin(rotation.y); + float cb = (float) Math.cos(rotation.y); + float sc = (float) Math.sin(rotation.z); + float cc = (float) Math.cos(rotation.z); + + // Apply the rotation (matrix used: xyz) + float newX = cb * cc * x + cb * (-sc) * y + sb * z; + float newY = ((-sa) * (-sb) * cb + ca * sc) * x + ((-sa) * (-sb) * (-sc) + ca * cc) * y + (-sa) * cb * z; + float newZ = (ca * (-sb) * cc + sa * sc) * x + (ca * (-sb) * (-sc) + sa * cc) * y + ca * cb * z; + + this.x = newX; + this.y = newY; + this.z = newZ; + + return this; + } +} diff --git a/app/src/main/java/ch/virt/smartphonemouse/transmission/DebugTransmitter.java b/app/src/main/java/ch/virt/smartphonemouse/transmission/DebugTransmitter.java new file mode 100644 index 0000000..ab8180d --- /dev/null +++ b/app/src/main/java/ch/virt/smartphonemouse/transmission/DebugTransmitter.java @@ -0,0 +1,396 @@ +package ch.virt.smartphonemouse.transmission; + +import android.content.SharedPreferences; +import android.os.Build; +import android.util.Log; +import ch.virt.smartphonemouse.mouse.Processing; +import ch.virt.smartphonemouse.mouse.math.Vec2f; +import ch.virt.smartphonemouse.mouse.math.Vec3f; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingDeque; + +/** + * This class handles transmission to a SensorServer. It is used with the following workflow: + *

First, the transmission should be connected to a SensorServer (see DebugTransmitter#connect()). During this process, the transmitter logs in and transmits its columns.

+ *

After a successful connection, the transmitter may start a transmission (see DebugTransmitter#startTransmission()).

+ *

If a transmission is open, the transmitter can operate. To transmit data, a client has to stage data in the order, he has registered the columns earlier. If all data for a data row is staged, data has to be committed (see DebugTransmitter#commmit()).

+ *

After a transmission, the transmission can be ended (see DebugTransmitter#endTransmission()), and the connection closed if required (see DebugTransmitter#disconnect())

+ */ +public class DebugTransmitter { + + private static final String TAG = "DebugTransmitter"; + + public static final byte ID_LOGIN = 0x01; + public static final byte ID_DATA_REGISTER = 0x02; + public static final byte ID_TRANSMISSION_STATE = 0x03; + public static final byte ID_DATA = 0x04; + + public static final byte TYPE_BOOL = 0x01; + public static final byte TYPE_I32 = 0x02; + public static final byte TYPE_I64 = 0x03; + public static final byte TYPE_F32 = 0x04; + public static final byte TYPE_F64 = 0x05; + + private final SharedPreferences preferences; + + // A change requires a restart of the app + private final boolean enabled; + private final String host; + private final int port; + + private boolean connected; + private String connectionFailure; + private final BlockingQueue pendingPackets; + private Thread thread; + + private static class Column { + String name; + byte type; + + public Column(String name, byte type) { + this.name = name; + this.type = type; + } + } + private final List columns; + private int size; + + private boolean transmitting; + private ByteBuffer currentData; + + /** + * Creates a transmitter + * @param preferences preferences to read enabled, host and port from + */ + public DebugTransmitter(SharedPreferences preferences) { + this.preferences = preferences; + + this.enabled = preferences.getBoolean("debugEnabled", false); + this.host = this.preferences.getString("debugHost", "undefined"); + this.port = this.preferences.getInt("debugPort", 55555); + + this.connected = false; + this.pendingPackets = new LinkedBlockingDeque<>(); + this.columns = new ArrayList<>(); + } + + /** + * Returns whether debugging is currently enabled + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Returns whether the transmitter is currently connected + */ + public boolean isConnected() { + return connected; + } + + /** + * Returns a string combination of the server hostname and port + */ + public String getServerString() { + return host + ":" + port; + } + + /** + * Connects to the set SensorServer + */ + public void connect() { + if (!enabled || connected) return; + + Log.i(TAG, "connect: Connecting to debug host on " + host + ":" + port); + connected = false; + if (thread != null) thread.interrupt(); + + thread = new Thread(() -> { + while (true) { + try { + + Socket socket = new Socket(host, port); + OutputStream stream = socket.getOutputStream(); + Log.i(TAG, "connect: Successfully connected to debug host"); + + pendingPackets.clear(); + transmitLogin(); + reloadColumns(); + transmitColumns(); + + connected = true; + + try { + while (socket.isConnected()) { + ByteBuffer packet = pendingPackets.take(); + + stream.write(packet.array()); + stream.flush(); + } + + if (socket.isConnected()) socket.close(); + + connected = false; + } catch (InterruptedException e) { + Log.w(TAG, "Thread was interrupted, disconnecting"); + + socket.close(); + connected = false; + break; + } + + + } catch (IOException e) { + connected = false; + connectionFailure = e.getMessage(); + Log.i(TAG, "connect: Failed to connect to debug host: " + connectionFailure, e); + + try { + Thread.sleep(10000); // Wait 10 seconds until attempting reconnection + } catch (InterruptedException ignored) { break; } + } + } + }); + + thread.start(); + } + + /** + * Disconnects from the server + */ + public void disconnect() { + thread.interrupt(); + connected = false; + } + + /** + * Registers a column for transmission + * @param name column name + * @param type data type of column + */ + public void registerColumn(String name, Class type) { + if (!enabled) return; + + // Add columns + if (Vec2f.class.equals(type)) { + columns.add(new Column(name + "-x", TYPE_F32)); + columns.add(new Column(name + "-y", TYPE_F32)); + } else if (Vec3f.class.equals(type)) { + columns.add(new Column(name + "-x", TYPE_F32)); + columns.add(new Column(name + "-y", TYPE_F32)); + columns.add(new Column(name + "-z", TYPE_F32)); + } else if (Float.class.equals(type)) columns.add(new Column(name, TYPE_F32)); + else if (Double.class.equals(type)) { + columns.add(new Column(name, TYPE_F64)); + } else if (Integer.class.equals(type)) { + columns.add(new Column(name, TYPE_I32)); + } else if (Long.class.equals(type)) { + columns.add(new Column(name, TYPE_I64)); + } else if (Boolean.class.equals(type)) { + columns.add(new Column(name, TYPE_BOOL)); + } + + // Update size + int size = 0; + for (Column c : columns) { + switch (c.type) { + case TYPE_BOOL: + size += 1; + break; + case TYPE_I32: + case TYPE_F32: + size += 4; + break; + case TYPE_I64: + case TYPE_F64: + size += 8; + break; + } + } + + this.size = size; + } + + /** + * Starts a new transmission + */ + public void startTransmission() { + if (!enabled || !connected) return; // Allow when not connected, to instantly start transmission + pendingPackets.clear(); // remove packets from previous transmission if present + + transmitTransmission(true); + + transmitting = true; + currentData = ByteBuffer.allocate(size); + } + + /** + * Ends the current transmission + */ + public void endTransmission() { + if (!enabled || !connected || !transmitting) return; + transmitTransmission(false); + + transmitting = false; + } + + /** + * Stages a 3d float vector + */ + public void stageVec3f(Vec3f data) { + if (!enabled || !connected || !transmitting) return; + if (currentData.remaining() < 3 * 4) return; + + currentData.putFloat(data.x); + currentData.putFloat(data.y); + currentData.putFloat(data.z); + } + + /** + * Stages a 2d float vector + */ + public void stageVec2f(Vec2f data) { + if (!enabled || !connected || !transmitting) return; + if (currentData.remaining() < 2 * 4) return; + + currentData.putFloat(data.x); + currentData.putFloat(data.y); + } + + /** + * Stages a float + */ + public void stageFloat(float data) { + if (!enabled || !connected || !transmitting) return; + if (currentData.remaining() < 4) return; + + currentData.putFloat(data); + } + + /** + * Stages a double + */ + public void stageDouble(double data) { + if (!enabled || !connected || !transmitting) return; + if (currentData.remaining() < 8) return; + + currentData.putDouble(data); + } + + /** + * Stages an integer + */ + public void stageInteger(int data) { + if (!enabled || !connected || !transmitting) return; + if (currentData.remaining() < 4) return; + + currentData.putInt(data); + } + + /** + * Stages a long + */ + public void stageLong(long data) { + if (!enabled || !connected || !transmitting) return; + if (currentData.remaining() < 8) return; + + currentData.putLong(data); + } + + /** + * Stages a boolean + */ + public void stageBoolean(boolean data) { + if (!enabled || !connected || !transmitting) return; + if (currentData.remaining() < 1) return; + + currentData.put((byte) (data ? 0x01 : 0x00)); + } + + /** + * Commits current staged data and transmits it to the server + */ + public void commit() { + if (!enabled || !connected || !transmitting) return; + + transmitData(currentData); + currentData.clear(); + } + + /** + * Transmits a login packet to the server + */ + private void transmitLogin() { + // null-terminate string here --v + byte[] model = (Build.MODEL + '\0').getBytes(StandardCharsets.US_ASCII); + + ByteBuffer buffer = ByteBuffer.allocate(1 + model.length); // Packet ID + Model Name + + buffer.put(ID_LOGIN); + buffer.put(model); + + pendingPackets.add(buffer); + } + + /** + * Reloads required columns from the Processing class + */ + private void reloadColumns() { + columns.clear(); + Processing.registerDebugColumns(this); + } + + /** + * Transmits all registered columns to the server + */ + private void transmitColumns() { + for (Column column : columns) { + // null-terminate string here v + byte[] name = (column.name + '\0').getBytes(StandardCharsets.US_ASCII); + + ByteBuffer buffer = ByteBuffer.allocate(1 + 1 + name.length); // Packet ID + Type + Column Name + + buffer.put(ID_DATA_REGISTER); + buffer.put(column.type); + buffer.put(name); + + pendingPackets.add(buffer); + } + } + + /** + * Transmits the beginning or end of a transmission to the server + * @param start is beginning + */ + private void transmitTransmission(boolean start) { + ByteBuffer buffer = ByteBuffer.allocate(1 + 1); // Packet ID + Start or End + + buffer.put(ID_TRANSMISSION_STATE); + buffer.put((byte) (start ? 0x01 : 0x00)); + + pendingPackets.add(buffer); + } + + /** + * Transmits processing data to the server + * @param data buffer to transmit + */ + private void transmitData(ByteBuffer data) { + ByteBuffer buffer = ByteBuffer.allocate(1 + data.capacity()); // Packet ID + Data + data.position(0); + + buffer.put(ID_DATA); + buffer.put(data); + + pendingPackets.add(buffer); + } + +} diff --git a/app/src/main/java/ch/virt/smartphonemouse/transmission/hid/HidDevice.java b/app/src/main/java/ch/virt/smartphonemouse/transmission/hid/HidDevice.java index 4773f64..4f28074 100644 --- a/app/src/main/java/ch/virt/smartphonemouse/transmission/hid/HidDevice.java +++ b/app/src/main/java/ch/virt/smartphonemouse/transmission/hid/HidDevice.java @@ -155,12 +155,12 @@ public void sendReport(boolean left, boolean middle, boolean right, int wheel, i byte[] report = new byte[4]; - report[0] = (byte) ((left ? 1 : 0) | (middle ? 4 : 0) | (right ? 2 : 0)); // First bit left, second middle, third right and the rest padding + report[0] = (byte) ((left ? 1 : 0) | (middle ? 4 : 0) | (right ? 2 : 0)); // First bit left, second right, third middle and the rest padding report[1] = (byte) x; report[2] = (byte) y; report[3] = (byte) wheel; - service.sendReport(device, 1, report); // Id 1 because of the descriptor + service.sendReport(device, 1, report); // id 1 because of the descriptor } /** diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/DebugFragment.java b/app/src/main/java/ch/virt/smartphonemouse/ui/DebugFragment.java deleted file mode 100644 index af8282e..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/ui/DebugFragment.java +++ /dev/null @@ -1,138 +0,0 @@ -package ch.virt.smartphonemouse.ui; - -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.hardware.SensorManager; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.preference.PreferenceManager; - -import com.jjoe64.graphview.GraphView; - -import java.io.IOException; - -import ch.virt.smartphonemouse.R; -import ch.virt.smartphonemouse.ui.debug.DebugChartSheet; -import ch.virt.smartphonemouse.ui.debug.handling.DebugChartHandler; -import ch.virt.smartphonemouse.ui.debug.handling.DebugCsvExporter; -import ch.virt.smartphonemouse.ui.debug.handling.DebugDataHandler; - -/** - * This fragment represents the debug page, which can be used to analyse the algorithms behaviour more closely. - */ -public class DebugFragment extends Fragment { - - private GraphView chart; - - private ImageView buttonPlay, buttonClear, buttonRenew, buttonSeries, buttonExport; - - private DebugChartHandler chartHandler; - private DebugDataHandler dataHandler; - - /** - * Creates the fragment. - */ - public DebugFragment() { - super(R.layout.fragment_debug); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - - // Sets the app in landscape mode - if(getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - if(getActivity().getWindow().getDecorView().getSystemUiVisibility() != View.SYSTEM_UI_FLAG_FULLSCREEN) getActivity().getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN); - - return super.onCreateView(inflater, container, savedInstanceState); - } - - @Override - public void onDestroyView() { - - // Reverts the app back to portrait mode - if(getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - if(getActivity().getWindow().getDecorView().getSystemUiVisibility() != View.SYSTEM_UI_FLAG_VISIBLE) getActivity().getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); - - super.onDestroyView(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - chart = view.findViewById(R.id.debug_chart); - - buttonPlay = view.findViewById(R.id.debug_button_play); - buttonClear = view.findViewById(R.id.debug_button_clear); - buttonRenew = view.findViewById(R.id.debug_button_renew); - buttonSeries = view.findViewById(R.id.debug_button_series); - buttonExport = view.findViewById(R.id.debug_button_export); - - initialize(); - } - - /** - * Initializes everything. - */ - private void initialize(){ - - // Initializes chart - chartHandler = new DebugChartHandler(chart, PreferenceManager.getDefaultSharedPreferences(getContext())); - dataHandler = new DebugDataHandler((SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE), chartHandler, PreferenceManager.getDefaultSharedPreferences(getContext())); - - chartHandler.addSeries("Unfiltered Acceleration", 0); - chartHandler.addSeries("Filtered Gravitation", 1); - chartHandler.addSeries("Frozen Gravitation", 2); - chartHandler.addSeries("Subtracted Acceleration", 3); - chartHandler.addSeries("Noise Cancelled Acceleration", 4); - chartHandler.addSeries("Unfiltered Velocity", 5); - chartHandler.addSeries("Cached Velocity", 6); - chartHandler.addSeries("Noise Cancelled Velocity", 7); - chartHandler.addSeries("Scaled Velocity", 8); - chartHandler.addSeries("Unfiltered Distance", 9); - chartHandler.addSeries("Cached Distance", 10); - chartHandler.addSeries("Final Distance", 11); - - // Initializes buttons - buttonSeries.setOnClickListener(v -> { - DebugChartSheet chartSheet = new DebugChartSheet(chartHandler, dataHandler); - chartSheet.show(getParentFragmentManager(), "debugChartSheet"); - }); - - buttonPlay.setOnClickListener(v -> { - - if(dataHandler.isRegistered()) { - dataHandler.unregister(); - buttonPlay.setImageResource(R.drawable.debug_play); - } - else { - dataHandler.register(); - buttonPlay.setImageResource(R.drawable.debug_pause); - } - - }); - - buttonClear.setOnClickListener(v -> chartHandler.clear()); - - buttonRenew.setOnClickListener(v -> dataHandler.renew()); - - buttonExport.setOnClickListener(v -> { - DebugCsvExporter exporter = new DebugCsvExporter(chartHandler, getContext()); - try { - exporter.exportCsv(); - } catch (IOException e) { - e.printStackTrace(); - Toast.makeText(getContext(), "Failed exporting to CSV", Toast.LENGTH_SHORT).show(); - } - }); - } -} diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/HomeFragment.java b/app/src/main/java/ch/virt/smartphonemouse/ui/HomeFragment.java index 4593ab7..7a98b36 100644 --- a/app/src/main/java/ch/virt/smartphonemouse/ui/HomeFragment.java +++ b/app/src/main/java/ch/virt/smartphonemouse/ui/HomeFragment.java @@ -14,6 +14,7 @@ import ch.virt.smartphonemouse.MainActivity; import ch.virt.smartphonemouse.R; import ch.virt.smartphonemouse.transmission.BluetoothHandler; +import ch.virt.smartphonemouse.transmission.DebugTransmitter; import ch.virt.smartphonemouse.ui.home.HomeConnectedSubfragment; import ch.virt.smartphonemouse.ui.home.HomeDisabledSubfragment; import ch.virt.smartphonemouse.ui.home.HomeDisconnectedSubfragment; @@ -25,9 +26,11 @@ public class HomeFragment extends Fragment { private BluetoothHandler bluetooth; + private DebugTransmitter debug; private ImageView status; private TextView statusText; + private TextView debugStatus; private Button button; @@ -36,10 +39,11 @@ public class HomeFragment extends Fragment { * * @param bluetooth bluetooth handler to use */ - public HomeFragment(BluetoothHandler bluetooth) { + public HomeFragment(BluetoothHandler bluetooth, DebugTransmitter debug) { super(R.layout.fragment_home); this.bluetooth = bluetooth; + this.debug = debug; } @Override @@ -50,6 +54,9 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat statusText = view.findViewById(R.id.home_status_text); button = view.findViewById(R.id.home_button); + debugStatus = view.findViewById(R.id.home_debug_status); + debugStatus.setOnClickListener(v -> update()); + update(); } @@ -70,6 +77,13 @@ else if (bluetooth.isConnected()) else setStatus(R.color.status_disconnected, R.string.home_status_disconnected, R.string.home_button_disconnected, v -> ((MainActivity) getActivity()).navigate(R.id.drawer_connect), new HomeDisconnectedSubfragment()); + + if (debug.isEnabled()) { + debugStatus.setVisibility(View.VISIBLE); + + if (debug.isConnected()) debugStatus.setText(String.format(getContext().getText(R.string.home_debug_connected).toString(), debug.getServerString())); + else debugStatus.setText(String.format(getContext().getText(R.string.home_debug_disconnected).toString(), debug.getServerString())); + } } /** diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/SettingsFragment.java b/app/src/main/java/ch/virt/smartphonemouse/ui/SettingsFragment.java index d14865b..1cc1e56 100644 --- a/app/src/main/java/ch/virt/smartphonemouse/ui/SettingsFragment.java +++ b/app/src/main/java/ch/virt/smartphonemouse/ui/SettingsFragment.java @@ -8,6 +8,7 @@ import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceManager; +import androidx.preference.SwitchPreference; import com.google.android.material.snackbar.Snackbar; import ch.virt.smartphonemouse.R; @@ -27,6 +28,17 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { resetSettings(); return true; }); + + checkAdvanced(PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("advanced", false)); + findPreference("advanced").setOnPreferenceChangeListener((preference, newValue) -> { + checkAdvanced((Boolean) newValue); + return true; + }); + } + + public void checkAdvanced(boolean advanced) { + findPreference("reset").setVisible(advanced); + findPreference("debugging").setVisible(advanced); } /** @@ -42,6 +54,9 @@ private void resetSettings() { Snackbar.make(getView(), getResources().getString(R.string.settings_reset_confirmation), Snackbar.LENGTH_SHORT).show(); + boolean advanced = PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("advanced", false); + checkAdvanced(advanced); + ((SwitchPreference)findPreference("advanced")).setChecked(advanced); }) .setNegativeButton(R.string.settings_reset_dialog_cancel, (dialog, id) -> { }); diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/connect/dialog/AddDialog.java b/app/src/main/java/ch/virt/smartphonemouse/ui/connect/dialog/AddDialog.java index 9939273..a145f27 100644 --- a/app/src/main/java/ch/virt/smartphonemouse/ui/connect/dialog/AddDialog.java +++ b/app/src/main/java/ch/virt/smartphonemouse/ui/connect/dialog/AddDialog.java @@ -276,7 +276,7 @@ public void onNext(){ if (((AddManualSubdialog) currentFragment).check()) selected(((AddManualSubdialog) currentFragment).createDevice()); break; case BONDED_STATE: - if (((AddBondedSubdialog) currentFragment).check()) finished(); + finished(); break; case SUCCESS_STATE: case ALREADY_STATE: diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/debug/DebugChartSheet.java b/app/src/main/java/ch/virt/smartphonemouse/ui/debug/DebugChartSheet.java deleted file mode 100644 index 2ed7043..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/ui/debug/DebugChartSheet.java +++ /dev/null @@ -1,155 +0,0 @@ -package ch.virt.smartphonemouse.ui.debug; - -import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; -import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT; -import static android.view.inputmethod.EditorInfo.IME_ACTION_PREVIOUS; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.graphics.drawable.GradientDrawable; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.RadioGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import ch.virt.smartphonemouse.R; -import ch.virt.smartphonemouse.ui.debug.handling.DebugChartHandler; -import ch.virt.smartphonemouse.ui.debug.handling.DebugDataHandler; - -/** - * This class is the pseudo side sheet that is used to configure the chart in the debugging page. - */ -public class DebugChartSheet extends SideDialogFragment { - - private Dialog dialog; - private RecyclerView seriesView; - private RadioGroup axis; - private EditText averageEdit; - - private final DebugChartHandler chartHandler; - private final DebugDataHandler dataHandler; - - /** - * Creates the chart sheet. - * - * @param chartHandler chart to manipulate - * @param dataHandler data handler to manipulate - */ - public DebugChartSheet(DebugChartHandler chartHandler, DebugDataHandler dataHandler) { - this.chartHandler = chartHandler; - this.dataHandler = dataHandler; - } - - @NonNull - @Override - public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - View view = requireActivity().getLayoutInflater().inflate(R.layout.sheet_debug_chart, null); - builder.setView(view) - .setNegativeButton(R.string.dialog_debug_sheet_done, null); - - dialog = builder.create(); - - seriesView = view.findViewById(R.id.debug_chart_series); - axis = view.findViewById(R.id.debug_chart_axis_group); - averageEdit = view.findViewById(R.id.sheet_debug_chart_average_edit); - - seriesView.setAdapter(new ListAdapter(chartHandler)); - seriesView.setLayoutManager(new LinearLayoutManager(getContext())); - - axis.check(dataHandler.getAxis() == 0 ? R.id.debug_chart_axis_x : dataHandler.getAxis() == 1 ? R.id.debug_chart_axis_y : R.id.debug_chart_axis_z); - axis.setOnCheckedChangeListener((group, checkedId) -> dataHandler.setAxis(checkedId == R.id.debug_chart_axis_x ? 0 : checkedId == R.id.debug_chart_axis_y ? 1 : 2)); - - averageEdit.setText("" + chartHandler.getAverageAmount()); - averageEdit.setOnEditorActionListener((v, actionId, event) -> { - if (actionId == IME_ACTION_DONE || actionId == IME_ACTION_NEXT || actionId == IME_ACTION_PREVIOUS) - chartHandler.setAverageAmount(Integer.parseInt(v.getText().toString())); - return false; - }); - - return dialog; - } - - /** - * This list adapter is for the recycler view that is used to show the different series. - */ - private static class ListAdapter extends RecyclerView.Adapter { - - DebugChartHandler handler; - - /** - * Creates the list adapter. - * - * @param series handler to get series from that are displayed. - */ - public ListAdapter(DebugChartHandler series) { - this.handler = series; - } - - @NonNull - @Override - public ListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_debug_chart_axis, parent, false); - - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull ListAdapter.ViewHolder holder, int position) { - holder.bind(handler, position); - } - - @Override - public int getItemCount() { - return handler.getSeries().size(); - } - - /** - * This view holder does hold one entry for one series in the recycler view. - */ - public static class ViewHolder extends RecyclerView.ViewHolder { - - private final ImageView color; - private final CheckBox checkbox; - - /** - * Creates the view holder. - * - * @param view view to hold - */ - public ViewHolder(View view) { - super(view); - - checkbox = view.findViewById(R.id.debug_chart_series_item_checkbox); - color = view.findViewById(R.id.debug_chart_series_item_color); - } - - /** - * Binds the view holder to a particular series. - * - * @param handler chart to get series from - * @param index index of the series - */ - public void bind(DebugChartHandler handler, int index) { - ((GradientDrawable) color.getBackground()).setColor(handler.getSeries().get(index).getColor() | 0xFF000000); // Add alpha values - - checkbox.setText(handler.getSeries().get(index).getName()); - checkbox.setChecked(handler.getSeries().get(index).isVisible()); - checkbox.setOnCheckedChangeListener((buttonView, isChecked) -> handler.setSeriesVisibility(handler.getSeries().get(index), isChecked)); - } - } - - - } -} diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/debug/OnDoubleClickListener.java b/app/src/main/java/ch/virt/smartphonemouse/ui/debug/OnDoubleClickListener.java deleted file mode 100644 index 4d845ab..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/ui/debug/OnDoubleClickListener.java +++ /dev/null @@ -1,30 +0,0 @@ -package ch.virt.smartphonemouse.ui.debug; - -import android.view.View; - -/** - * This class is a further abstraction of the on click listener that listens to double clicks. - */ -public abstract class OnDoubleClickListener implements View.OnClickListener { - - public static final long DOUBLE_CLICK_DELAY = 200; - - private long lastClickTime = 0; - - @Override - public void onClick(View v) { - long currentClickTime = System.currentTimeMillis(); - - if (currentClickTime - lastClickTime < DOUBLE_CLICK_DELAY) onDoubleClick(v); - - lastClickTime = currentClickTime; - - } - - /** - * Gets called when a double click is performed. - * - * @param v view it was performed on - */ - public abstract void onDoubleClick(View v); -} diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/debug/SideDialogFragment.java b/app/src/main/java/ch/virt/smartphonemouse/ui/debug/SideDialogFragment.java deleted file mode 100644 index 1091423..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/ui/debug/SideDialogFragment.java +++ /dev/null @@ -1,34 +0,0 @@ -package ch.virt.smartphonemouse.ui.debug; - -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; - -import android.util.DisplayMetrics; -import android.view.Gravity; -import android.view.WindowManager; - -import androidx.fragment.app.DialogFragment; - -/** - * This class configures a dialog fragment as a dialog that appears on the side of the screen. - * This dialog is similar to a side sheet from material design, but since it is not implemented by their library, this is used. - */ -public class SideDialogFragment extends DialogFragment { - - @Override - public void onResume() { - super.onResume(); - - DisplayMetrics metrics = new DisplayMetrics(); - getDialog().getWindow().getWindowManager().getDefaultDisplay().getMetrics(metrics); - - WindowManager.LayoutParams params = getDialog().getWindow().getAttributes(); - params.gravity = Gravity.END; - params.width = (int) (metrics.widthPixels * 0.4f); - params.height = MATCH_PARENT; - params.horizontalMargin = 0; - params.verticalMargin = 0; - getDialog().getWindow().setAttributes(params); - - } - -} diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/debug/handling/DebugChartHandler.java b/app/src/main/java/ch/virt/smartphonemouse/ui/debug/handling/DebugChartHandler.java deleted file mode 100644 index 1ce74d5..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/ui/debug/handling/DebugChartHandler.java +++ /dev/null @@ -1,200 +0,0 @@ -package ch.virt.smartphonemouse.ui.debug.handling; - -import android.content.SharedPreferences; -import android.view.View; - -import com.jjoe64.graphview.GraphView; - -import java.util.ArrayList; -import java.util.List; - -import ch.virt.smartphonemouse.mouse.Pipeline; -import ch.virt.smartphonemouse.ui.debug.OnDoubleClickListener; - -/** - * This class handles the chart on the debug fragment. - */ -public class DebugChartHandler { - - private static final int[] COLORS = {0xf44336, 0x9c27b0, 0x673ab7, 0x3f51b5, 0x2196f3, 0x009688, 0x4caf50, 0x8bc34a, 0xcddc39, 0xffeb3b, 0xffc107, 0xff9800, 0xff5722, 0x795548, 0x9e9e9e, 0x607d8b, 0x03a9f4, 0x00bcd4, 0xe91e63}; - - private final GraphView chart; - private final List series; - private final SharedPreferences preferences; - - private int highestIndex; - private int colorIndex; - - private int averageAmount; - private int currentAverageAmount; - - private List timeStamps; - - /** - * Creates the chart handler. - * - * @param chart chart to handle - * @param preferences preferences to read certain config entries from - */ - public DebugChartHandler(GraphView chart, SharedPreferences preferences) { - timeStamps = new ArrayList<>(); - series = new ArrayList<>(); - this.preferences = preferences; - - this.chart = chart; - - averageAmount = preferences.getInt("debugChartAverageAmount", 20); - - styleChart(); - } - - /** - * Styles the chart in order to look good. - */ - private void styleChart() { - chart.getViewport().setYAxisBoundsManual(true); - chart.getViewport().setMinY(-10); - chart.getViewport().setMaxY(10); - - chart.getViewport().setXAxisBoundsManual(true); - chart.getViewport().setMinX(0); - chart.getViewport().setMaxX(100); - - chart.getViewport().setScalable(true); - chart.getViewport().setScalableY(true); - - chart.setOnClickListener(new OnDoubleClickListener() { - @Override - public void onDoubleClick(View v) { - chart.getViewport().setYAxisBoundsManual(true); - chart.getViewport().setMinY(-10); - chart.getViewport().setMaxY(10); - - chart.getViewport().setXAxisBoundsManual(true); - chart.getViewport().setMinX(0); - chart.getViewport().setMaxX(100); - } - }); - } - - /** - * Adds a series to the graph. - * - * @param name name of that series - * @param index source index of that data - */ - public void addSeries(String name, int index) { - if (index > highestIndex) highestIndex = index; - - DebugSeries series = new DebugSeries(selectColor(), name, averageAmount); - chart.addSeries(series.getDataSet()); - this.series.add(index, series); - } - - /** - * Adds a new line of samples to the graph. - * - * @param timestamp timestamp of the samples - * @param data debugging data from the pipeline - * @see Pipeline#getDebuggingValues() - */ - public void newData(long timestamp, float[] data) { - timeStamps.add(timestamp); - - for (int i = 0; i < data.length; i++) { - if (i >= series.size()) break; - DebugSeries series = this.series.get(i); - if (series != null) series.newData(data[i]); - } - - currentAverageAmount++; - if (currentAverageAmount == averageAmount) { - currentAverageAmount = 0; - } - } - - /** - * Selects a colour for the next series. The colours are selected from the material colors. - * - * @return selected color as an integer - * @see DebugChartHandler#COLORS - */ - private int selectColor() { - if (colorIndex == COLORS.length) colorIndex = 0; - return COLORS[colorIndex++]; - } - - /** - * Sets the amount of samples needed to form one point on the chart. - * - * @param amount amount of samples - */ - public void setAverageAmount(int amount) { - if (amount == averageAmount) return; - this.averageAmount = amount; - - currentAverageAmount = 0; - timeStamps.clear(); - - for (DebugSeries series : this.series) { - series.setAverageAmount(averageAmount); - } - - preferences.edit().putInt("debugChartAverageAmount", amount).apply(); - } - - /** - * Clears the chart. - */ - public void clear() { - timeStamps.clear(); - - currentAverageAmount = 0; - - for (DebugSeries series : this.series) { - series.clear(); - } - } - - /** - * Sets the visibility of a series. - * - * @param series series to set visibility for - * @param visibility visibility of that series - */ - public void setSeriesVisibility(DebugSeries series, boolean visibility) { - if (series.isVisible() != visibility) { - if (visibility) chart.addSeries(series.getDataSet()); - else chart.removeSeries(series.getDataSet()); - - series.setVisible(visibility); - } - } - - /** - * Returns the list of timestamps present on the diagram. This method is used for exporting the shown data. - * - * @return list of timestamps - */ - public List getTimeStamps() { - return timeStamps; - } - - /** - * Returns all series of the diagram. - * - * @return list of series - */ - public List getSeries() { - return series; - } - - /** - * Returns how many samples are used for one point on the chart. - * - * @return amount - */ - public int getAverageAmount() { - return averageAmount; - } -} diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/debug/handling/DebugCsvExporter.java b/app/src/main/java/ch/virt/smartphonemouse/ui/debug/handling/DebugCsvExporter.java deleted file mode 100644 index 9ce3829..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/ui/debug/handling/DebugCsvExporter.java +++ /dev/null @@ -1,105 +0,0 @@ -package ch.virt.smartphonemouse.ui.debug.handling; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; - -import androidx.core.content.FileProvider; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; - -/** - * This class is used to export the content of the chart as a csv file. - */ -public class DebugCsvExporter { - - private final DebugChartHandler chart; - private final Context context; - - /** - * Creates the exporter. - * - * @param chart chart to export - * @param context context to share with - */ - public DebugCsvExporter(DebugChartHandler chart, Context context) { - this.chart = chart; - this.context = context; - } - - /** - * Exports the chart as a csv file. - * - * @throws IOException thrown if failed to write file to disk for cache - */ - public void exportCsv() throws IOException { - String csv = createCsv(); - File file = cacheCsv(csv); - shareCsv(file); - } - - /** - * Creates a string with the diagram data in csv format. - * - * @return string with csv data - */ - public String createCsv() { - StringBuilder s = new StringBuilder(); - - // Write Header - s.append("Timestamp").append(';'); - for (DebugSeries series : chart.getSeries()) { - s.append(series.getName()).append(';'); - } - s.append('\n'); - - // Write Body - int length = chart.getTimeStamps().size(); - for (int i = 0; i < length; i++) { - s.append(chart.getTimeStamps().get(i)).append(';'); - for (DebugSeries series : chart.getSeries()) { - s.append(series.getSamples().get(i)).append(';'); - } - s.append('\n'); - } - - return s.toString(); - } - - /** - * Writes the csv data to a cache file so it can be shared. - * - * @param s string with csv data - * @return file that it was written to - * @throws IOException thrown if failed to write file - */ - public File cacheCsv(String s) throws IOException { - new File(context.getExternalFilesDir(null), "exports").mkdir(); - File file = new File(context.getExternalFilesDir(null), "exports/export.csv"); - - FileWriter writer = new FileWriter(file); - writer.write(s); - writer.flush(); - writer.close(); - - return file; - } - - /** - * Shares the csv file through the android share dialog. - * - * @param f cache file to share - */ - public void shareCsv(File f) { - Uri u = FileProvider.getUriForFile(context, "ch.virt.fileprovider", f); - - Intent intent = new Intent(Intent.ACTION_SEND); - intent.setType("text/*"); - intent.putExtra(Intent.EXTRA_STREAM, u); - intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - context.startActivity(Intent.createChooser(intent, "Export file to ")); - } - -} diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/debug/handling/DebugDataHandler.java b/app/src/main/java/ch/virt/smartphonemouse/ui/debug/handling/DebugDataHandler.java deleted file mode 100644 index 5d35ca3..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/ui/debug/handling/DebugDataHandler.java +++ /dev/null @@ -1,144 +0,0 @@ -package ch.virt.smartphonemouse.ui.debug.handling; - -import android.content.SharedPreferences; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; - -import ch.virt.smartphonemouse.mouse.MovementHandler; -import ch.virt.smartphonemouse.mouse.Pipeline; -import ch.virt.smartphonemouse.mouse.PipelineConfig; - -/** - * This class does handle the data from the accelerometer and does provide it to the chart. - */ -public class DebugDataHandler implements SensorEventListener { - - private static final float NANO_FULL_FACTOR = 1e-9f; - - private final DebugChartHandler chart; - private final SharedPreferences preferences; - - private long lastSample = 0; - private boolean registered; - private Pipeline pipeline; - - private final SensorManager manager; - private Sensor sensor; - - private int axis; - - /** - * Create a debug data handler. - * - * @param manager sensor manager to get the sensor from - * @param chart chart to provide data to - * @param preferences preferences to fetch specific config - */ - public DebugDataHandler(SensorManager manager, DebugChartHandler chart, SharedPreferences preferences) { - this.manager = manager; - this.chart = chart; - this.preferences = preferences; - - axis = preferences.getInt("debugChartAxis", 0); - - create(preferences); - fetchSensor(); - } - - /** - * Creates the pipeline used for processing. - * - * @param preferences preferences to get pipeline config from - */ - public void create(SharedPreferences preferences) { - pipeline = new Pipeline(preferences.getInt("communicationTransmissionRate", 200), new PipelineConfig(preferences)); - pipeline.enableDebugging(); - } - - /** - * Fetches the sensor from the sensor manager. - */ - private void fetchSensor() { - sensor = manager.getDefaultSensor(MovementHandler.SENSOR_TYPE); - } - - /** - * Registers this handler as a handler for the accelerometer. - */ - public void register() { - if (registered) return; - manager.registerListener(this, sensor, MovementHandler.SAMPLING_RATE); - - lastSample = 0; - registered = true; - } - - /** - * Unregisters this handler as a handler for the accelerometer. - */ - public void unregister() { - if (!registered) return; - manager.unregisterListener(this, sensor); - - registered = false; - } - - @Override - public void onSensorChanged(SensorEvent event) { - if (!registered) return; // Ignore Samples when the listener is not registered - if (lastSample == 0) { // Ignore First sample, because there is no delta - lastSample = event.timestamp; - return; - } - - float delta = (event.timestamp - lastSample) * NANO_FULL_FACTOR; // Delta in Seconds - - pipeline.nextForDistance(delta, event.values[axis]); - chart.newData(event.timestamp, pipeline.getDebuggingValues()); - - lastSample = event.timestamp; - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - - } - - /** - * Returns whether the handler is currently registered. - * - * @return is currently registered - */ - public boolean isRegistered() { - return registered; - } - - /** - * Returns the current axis data is used from. - * - * @return current axis, 0 = x, 1 = y, 2 = z - */ - public int getAxis() { - return axis; - } - - /** - * Sets the axis data is used form. - * - * @param axis current axis, 0 = x, 1 = y, 2 = z - */ - public void setAxis(int axis) { - this.axis = axis; - - preferences.edit().putInt("debugChartAxis", axis).apply(); - } - - /** - * Resets the algorithm to its initial state. - */ - public void renew() { - pipeline.reset(); - } -} diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/debug/handling/DebugSeries.java b/app/src/main/java/ch/virt/smartphonemouse/ui/debug/handling/DebugSeries.java deleted file mode 100644 index bfe5ef2..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/ui/debug/handling/DebugSeries.java +++ /dev/null @@ -1,138 +0,0 @@ -package ch.virt.smartphonemouse.ui.debug.handling; - -import com.jjoe64.graphview.series.DataPoint; -import com.jjoe64.graphview.series.LineGraphSeries; - -import java.util.ArrayList; -import java.util.List; - -/** - * This class holds a single series in the debugging chart. - */ -public class DebugSeries { - - private final int color; - private final String name; - - private int averageAmount; - private boolean visible; - - private int currentAverageIndex; - private float currentAverage; - - private List samples; - private LineGraphSeries dataSet; - private int entryIndex; - - /** - * Creates a series. - * - * @param color color of that series - * @param name name of that series - * @param averageAmount amount of samples required for one point on the diagram - */ - public DebugSeries(int color, String name, int averageAmount) { - this.color = color; - this.name = name; - this.averageAmount = averageAmount; - this.visible = true; - - samples = new ArrayList<>(); - dataSet = new LineGraphSeries<>(); - - dataSet.setColor(color | 0xff000000); // Add alpha values - } - - /** - * Adds a new sample to the series. - * - * @param sample new sample - */ - void newData(float sample) { - samples.add(sample); - - currentAverage += sample; - currentAverageIndex++; - if (currentAverageIndex == averageAmount) { - dataSet.appendData(new DataPoint(entryIndex++, currentAverage / averageAmount), true, 10000); - currentAverageIndex = 0; - currentAverage = 0; - } - } - - /** - * Clears the series. - */ - void clear() { - samples.clear(); - dataSet.resetData(new DataPoint[0]); - - entryIndex = 0; - currentAverageIndex = 0; - currentAverage = 0; - } - - /** - * Sets how many samples are required for one datapoint on the chart. - * - * @param amount amount of samples - */ - void setAverageAmount(int amount) { - averageAmount = amount; - clear(); - } - - /** - * Returns the data set, one can add to a chart. - * - * @return dataset to be added - */ - public LineGraphSeries getDataSet() { - return dataSet; - } - - /** - * Returns all samples shown for this series. - * - * @return list of samples - */ - public List getSamples() { - return samples; - } - - /** - * Returns the color of this series. - * - * @return color as an integer - */ - public int getColor() { - return color; - } - - /** - * Returns the name of this series. - * - * @return name of the series - */ - public String getName() { - return name; - } - - /** - * Returns whether the series is currently visible on the chart. - * - * @return is visible - */ - public boolean isVisible() { - return visible; - } - - /** - * Sets whether the series should be visible. - * - * @param visible should be visible - */ - void setVisible(boolean visible) { - this.visible = visible; - } -} diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/mouse/MouseCalibrateDialog.java b/app/src/main/java/ch/virt/smartphonemouse/ui/mouse/MouseCalibrateDialog.java index 4957918..3ddf487 100644 --- a/app/src/main/java/ch/virt/smartphonemouse/ui/mouse/MouseCalibrateDialog.java +++ b/app/src/main/java/ch/virt/smartphonemouse/ui/mouse/MouseCalibrateDialog.java @@ -6,14 +6,11 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.Button; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; - import ch.virt.smartphonemouse.R; -import ch.virt.smartphonemouse.ui.settings.dialog.SamplingRateSubdialog; /** * This class is the dialog that is shown upon mouse calibration @@ -55,7 +52,7 @@ private void next() { introduction = false; - setFragment(new SamplingRateSubdialog((r) -> positiveButton.post(this::finished))); +// setFragment(new CalibrationHappeningSubdialog((r) -> positiveButton.post(this::finished))); } else dismiss(); } diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/settings/SettingsDebuggingSubfragment.java b/app/src/main/java/ch/virt/smartphonemouse/ui/settings/SettingsDebuggingSubfragment.java new file mode 100644 index 0000000..daae52f --- /dev/null +++ b/app/src/main/java/ch/virt/smartphonemouse/ui/settings/SettingsDebuggingSubfragment.java @@ -0,0 +1,30 @@ +package ch.virt.smartphonemouse.ui.settings; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; +import ch.virt.smartphonemouse.R; +import ch.virt.smartphonemouse.ui.settings.custom.EditFloatPreference; +import ch.virt.smartphonemouse.ui.settings.custom.SeekFloatPreference; +import ch.virt.smartphonemouse.ui.settings.dialog.CalibrateDialog; + +/** + * This fragment is the settings page, where the user can configure everything regarding the movement. + */ +public class SettingsDebuggingSubfragment extends PreferenceFragmentCompat { + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferencesFromResource(R.xml.settings_debugging, null); + + findPreference("debugDownload").setOnPreferenceClickListener(preference -> { + + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(getContext().getText(R.string.settings_debug_server_download_url).toString())); + startActivity(browserIntent); + + return false; + }); + } +} diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/settings/SettingsInterfaceSubfragment.java b/app/src/main/java/ch/virt/smartphonemouse/ui/settings/SettingsInterfaceSubfragment.java index 95d5470..187e21d 100644 --- a/app/src/main/java/ch/virt/smartphonemouse/ui/settings/SettingsInterfaceSubfragment.java +++ b/app/src/main/java/ch/virt/smartphonemouse/ui/settings/SettingsInterfaceSubfragment.java @@ -4,6 +4,7 @@ import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; import ch.virt.smartphonemouse.R; import ch.virt.smartphonemouse.ui.settings.custom.SeekFloatPreference; import ch.virt.smartphonemouse.ui.settings.custom.SeekIntegerPreference; @@ -52,5 +53,21 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { interfaceVisualsIntensity.setMinimum(0.0f); interfaceVisualsIntensity.setSteps(100); interfaceVisualsIntensity.update(); + + checkAdvanced(); + } + + public void checkAdvanced() { + boolean advanced = PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("advanced", false); + findPreference("interfaceBehaviour").setVisible(advanced); + findPreference("interfaceVisualsStrokeWeight").setVisible(advanced); + findPreference("interfaceVisualsIntensity").setVisible(advanced); + findPreference("interfaceVibrationsButtonIntensity").setVisible(advanced); + findPreference("interfaceVibrationsButtonLength").setVisible(advanced); + findPreference("interfaceVibrationsScrollIntensity").setVisible(advanced); + findPreference("interfaceVibrationsScrollLength").setVisible(advanced); + findPreference("interfaceVibrationsSpecialIntensity").setVisible(advanced); + findPreference("interfaceVibrationsSpecialLength").setVisible(advanced); + findPreference("interfaceLayout").setVisible(advanced); } } diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/settings/SettingsMovementSubfragment.java b/app/src/main/java/ch/virt/smartphonemouse/ui/settings/SettingsMovementSubfragment.java index e0bcd2e..c3de9f7 100644 --- a/app/src/main/java/ch/virt/smartphonemouse/ui/settings/SettingsMovementSubfragment.java +++ b/app/src/main/java/ch/virt/smartphonemouse/ui/settings/SettingsMovementSubfragment.java @@ -1,12 +1,11 @@ package ch.virt.smartphonemouse.ui.settings; import android.os.Bundle; - import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; - +import androidx.preference.PreferenceManager; import ch.virt.smartphonemouse.R; -import ch.virt.smartphonemouse.ui.settings.custom.EditIntegerPreference; +import ch.virt.smartphonemouse.ui.settings.custom.EditFloatPreference; import ch.virt.smartphonemouse.ui.settings.custom.SeekFloatPreference; import ch.virt.smartphonemouse.ui.settings.dialog.CalibrateDialog; @@ -20,21 +19,49 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { setPreferencesFromResource(R.xml.settings_movement, null); SeekFloatPreference movementSensitivity = findPreference("movementSensitivity"); - movementSensitivity.setMaximum(16f); - movementSensitivity.setMinimum(-2.0f); - movementSensitivity.setSteps(180); + movementSensitivity.setMaximum(20000f); + movementSensitivity.setMinimum(10000f); + movementSensitivity.setSteps(200); movementSensitivity.update(); - EditIntegerPreference movementSamplingRealRate = findPreference("movementSamplingRealRate"); + EditFloatPreference rotThreshold = findPreference("movementThresholdRotation"); + EditFloatPreference accThreshold = findPreference("movementThresholdAcceleration"); + EditFloatPreference samplingRate = findPreference("movementSampling"); - Preference movementSamplingCalibrate = findPreference("movementSamplingCalibrate"); + Preference movementSamplingCalibrate = findPreference("movementRecalibrate"); movementSamplingCalibrate.setOnPreferenceClickListener(preference -> { CalibrateDialog dialog = new CalibrateDialog(); - dialog.setFinishedListener((v) -> movementSamplingRealRate.update()); + dialog.setFinishedListener(dialog1 -> { + // Update changed values after calibration + rotThreshold.update(); + accThreshold.update(); + samplingRate.update(); + }); + dialog.show(SettingsMovementSubfragment.this.getParentFragmentManager(), null); return true; }); + + checkAdvanced(); + } + + public void checkAdvanced() { + boolean advanced = PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("advanced", false); + findPreference("movementEnableGravityRotation").setVisible(advanced); + findPreference("movementCalibrationSampling").setVisible(advanced); + findPreference("movementCalibrationNoise").setVisible(advanced); + findPreference("movementNoiseRatioAcceleration").setVisible(advanced); + findPreference("movementNoiseFactorAcceleration").setVisible(advanced); + findPreference("movementNoiseRatioRotation").setVisible(advanced); + findPreference("movementNoiseFactorRotation").setVisible(advanced); + findPreference("movementThresholdAcceleration").setVisible(advanced); + findPreference("movementThresholdRotation").setVisible(advanced); + findPreference("movementSampling").setVisible(advanced); + findPreference("movementDurationWindowGravity").setVisible(advanced); + findPreference("movementDurationWindowNoise").setVisible(advanced); + findPreference("movementDurationThreshold").setVisible(advanced); + findPreference("movementDurationGravity").setVisible(advanced); } } diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/settings/custom/EditIntegerPreference.java b/app/src/main/java/ch/virt/smartphonemouse/ui/settings/custom/EditIntegerPreference.java index b9f7817..070fd5b 100644 --- a/app/src/main/java/ch/virt/smartphonemouse/ui/settings/custom/EditIntegerPreference.java +++ b/app/src/main/java/ch/virt/smartphonemouse/ui/settings/custom/EditIntegerPreference.java @@ -17,7 +17,7 @@ public class EditIntegerPreference extends EditTextPreference { private boolean showValueAsDescription; private String valueUnit; private int minimumValue = 0; - private int maximumValue = 1000; + private int maximumValue = 100000; private int value; diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/settings/custom/SeekFloatPreference.java b/app/src/main/java/ch/virt/smartphonemouse/ui/settings/custom/SeekFloatPreference.java index a5ebdd3..289c9c0 100644 --- a/app/src/main/java/ch/virt/smartphonemouse/ui/settings/custom/SeekFloatPreference.java +++ b/app/src/main/java/ch/virt/smartphonemouse/ui/settings/custom/SeekFloatPreference.java @@ -2,9 +2,7 @@ import android.content.Context; import android.util.AttributeSet; - import androidx.preference.SeekBarPreference; - import ch.virt.smartphonemouse.R; /** @@ -27,9 +25,11 @@ public class SeekFloatPreference extends SeekBarPreference { public SeekFloatPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - setSteps(20000); - setMaximum(100); - setMinimum(-100); + // Set a ludicrous min, max and amount of steps, because all data will be fitted to it for some reason + // FIXME: Investigate this and fix it + setSteps(10000000); + setMaximum(50000); + setMinimum(-50000); } @@ -54,6 +54,8 @@ public SeekFloatPreference(Context context, AttributeSet attrs) { this(context, attrs, R.attr.seekBarPreferenceStyle); } + + /** * Creates a preference. * diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/CalibrateBeginSubdialog.java b/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/CalibrateBeginSubdialog.java new file mode 100644 index 0000000..e83b298 --- /dev/null +++ b/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/CalibrateBeginSubdialog.java @@ -0,0 +1,27 @@ +package ch.virt.smartphonemouse.ui.settings.dialog; + +import android.os.Bundle; +import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import ch.virt.smartphonemouse.R; + +/** + * This class is a sub fragment for the calibrate dialog, that is shown when the calibration process has finished. + */ +public class CalibrateBeginSubdialog extends Fragment { + + + /** + * Creates the sub dialog. + */ + public CalibrateBeginSubdialog() { + super(R.layout.subdialog_calibrate_begin); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + } +} diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/CalibrateDialog.java b/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/CalibrateDialog.java index b874bc6..560bb5c 100644 --- a/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/CalibrateDialog.java +++ b/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/CalibrateDialog.java @@ -6,12 +6,10 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.widget.Button; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; - import ch.virt.smartphonemouse.R; /** @@ -23,6 +21,8 @@ public class CalibrateDialog extends DialogFragment { Button positiveButton; + boolean calibrating = false; + private DialogInterface.OnDismissListener finishedListener; /** @@ -38,12 +38,20 @@ public void setFinishedListener(DialogInterface.OnDismissListener finishedListen * Is called when the dialog is created. */ private void created() { - dialog.setTitle(R.string.dialog_calibrate_samplingrate_title); + dialog.setTitle(R.string.dialog_calibrate_title); - positiveButton.setEnabled(false); dialog.setCanceledOnTouchOutside(false); - setFragment(new SamplingRateSubdialog((r) -> positiveButton.post(this::finished))); + setFragment(new CalibrateBeginSubdialog()); + } + + private void calibrate() { + calibrating = true; + + positiveButton.setEnabled(false); + positiveButton.setText(R.string.dialog_calibrate_done); + + setFragment(new CalibrationHappeningSubdialog(() -> positiveButton.post(this::finished))); } /** @@ -73,7 +81,7 @@ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { LayoutInflater inflater = requireActivity().getLayoutInflater(); builder.setView(inflater.inflate(R.layout.dialog_calibrate, null)) - .setPositiveButton(R.string.dialog_calibrate_done, null); + .setPositiveButton(R.string.dialog_calibrate_next, null); dialog = builder.create(); @@ -81,6 +89,11 @@ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { dialog.setOnShowListener(dialogInterface -> { positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE); + positiveButton.setOnClickListener((b) -> { + if (!calibrating) calibrate(); + else dismiss(); + }); + created(); }); @@ -91,6 +104,6 @@ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { public void onDismiss(@NonNull DialogInterface dialog) { super.onDismiss(dialog); - finishedListener.onDismiss(dialog); + if (finishedListener != null) finishedListener.onDismiss(dialog); } } diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/CalibrateFinishedSubdialog.java b/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/CalibrateFinishedSubdialog.java index b2d887c..e7c6506 100644 --- a/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/CalibrateFinishedSubdialog.java +++ b/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/CalibrateFinishedSubdialog.java @@ -2,13 +2,9 @@ import android.os.Bundle; import android.view.View; -import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import androidx.preference.PreferenceManager; - import ch.virt.smartphonemouse.R; /** @@ -16,7 +12,6 @@ */ public class CalibrateFinishedSubdialog extends Fragment { - private TextView rate; /** * Creates the sub dialog. @@ -28,8 +23,5 @@ public CalibrateFinishedSubdialog() { @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - - rate = view.findViewById(R.id.calibrate_finished_rate); - rate.setText(getResources().getString(R.string.dialog_calibrate_finished_rate, PreferenceManager.getDefaultSharedPreferences(getContext()).getInt("movementSamplingRealRate", 0))); } } diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/CalibrationHappeningSubdialog.java b/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/CalibrationHappeningSubdialog.java new file mode 100644 index 0000000..3144a63 --- /dev/null +++ b/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/CalibrationHappeningSubdialog.java @@ -0,0 +1,53 @@ +package ch.virt.smartphonemouse.ui.settings.dialog; + +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import ch.virt.smartphonemouse.R; +import ch.virt.smartphonemouse.customization.CalibrationHandler; +import ch.virt.smartphonemouse.mouse.Calibration; + +/** + * This fragment for a dialog is used to handle the calibration of the sampling rate. + */ +public class CalibrationHappeningSubdialog extends Fragment { + + private TextView time; + + private CalibrationHandler calibrator; + private final DoneListener doneListener; + + /** + * Creates the sub dialog fragment. + * + * @param doneListener listener that gets called when the calibration process is finished. + */ + public CalibrationHappeningSubdialog(DoneListener doneListener) { + super(R.layout.subdialog_calibrate_happening); + + this.doneListener = doneListener; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + calibrator = new CalibrationHandler(getContext()); + + time = view.findViewById(R.id.calibrate_samplingrate_time); + time.setText(getResources().getString(R.string.dialog_calibrate_happening_init)); + + calibrator.calibrate(state -> time.post(() -> { + if (state == Calibration.STATE_SAMPLING) time.setText(getResources().getString(R.string.dialog_calibrate_happening_sampling)); + else if (state == Calibration.STATE_NOISE) time.setText(getResources().getString(R.string.dialog_calibrate_happening_noise)); + else if (state == Calibration.STATE_END) doneListener.done(); + })); + } + + public interface DoneListener { + void done(); + } +} diff --git a/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/SamplingRateSubdialog.java b/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/SamplingRateSubdialog.java deleted file mode 100644 index 6136e16..0000000 --- a/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/SamplingRateSubdialog.java +++ /dev/null @@ -1,46 +0,0 @@ -package ch.virt.smartphonemouse.ui.settings.dialog; - -import android.os.Bundle; -import android.view.View; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; - -import ch.virt.smartphonemouse.R; -import ch.virt.smartphonemouse.customization.SamplingRateCalibrator; - -/** - * This fragment for a dialog is used to handle the calibration of the sampling rate. - */ -public class SamplingRateSubdialog extends Fragment { - - private TextView time; - - private SamplingRateCalibrator calibrator; - private final SamplingRateCalibrator.DoneListener doneListener; - - /** - * Creates the sub dialog fragment. - * - * @param doneListener listener that gets called when the calibration process is finished. - */ - public SamplingRateSubdialog(SamplingRateCalibrator.DoneListener doneListener) { - super(R.layout.subdialog_calibrate_samplingrate); - - this.doneListener = doneListener; - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - calibrator = new SamplingRateCalibrator(getContext()); - - time = view.findViewById(R.id.calibrate_samplingrate_time); - time.setText(getResources().getString(R.string.dialog_calibrate_samplingrate_time, calibrator.getTestLength() / 1000)); - - calibrator.calibrate(doneListener); - } -} diff --git a/app/src/main/res/drawable/debug_chart_color.xml b/app/src/main/res/drawable/debug_chart_color.xml deleted file mode 100644 index 59c2326..0000000 --- a/app/src/main/res/drawable/debug_chart_color.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/debug_clear.xml b/app/src/main/res/drawable/debug_clear.xml deleted file mode 100644 index 16d6d37..0000000 --- a/app/src/main/res/drawable/debug_clear.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/debug_export.xml b/app/src/main/res/drawable/debug_export.xml deleted file mode 100644 index f3e5683..0000000 --- a/app/src/main/res/drawable/debug_export.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/debug_pause.xml b/app/src/main/res/drawable/debug_pause.xml deleted file mode 100644 index 13d6d2e..0000000 --- a/app/src/main/res/drawable/debug_pause.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/debug_play.xml b/app/src/main/res/drawable/debug_play.xml deleted file mode 100644 index 13c137a..0000000 --- a/app/src/main/res/drawable/debug_play.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/debug_renew.xml b/app/src/main/res/drawable/debug_renew.xml deleted file mode 100644 index 1772ed5..0000000 --- a/app/src/main/res/drawable/debug_renew.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/debug_series.xml b/app/src/main/res/drawable/debug_series.xml deleted file mode 100644 index 01cdf71..0000000 --- a/app/src/main/res/drawable/debug_series.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/device_connect_settings.xml b/app/src/main/res/drawable/device_connect_settings.xml deleted file mode 100644 index 41a82ed..0000000 --- a/app/src/main/res/drawable/device_connect_settings.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml index 6784b50..fc03357 100644 --- a/app/src/main/res/layout/fragment_about.xml +++ b/app/src/main/res/layout/fragment_about.xml @@ -49,13 +49,7 @@ android:layout_marginTop="24dp" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/about_feasibility"/> - - + android:text="@string/about_explanation"/> - - - + android:layout_height="0dp" + android:layout_marginTop="24dp" + android:layout_weight="1" + android:orientation="vertical" + android:gravity="bottom"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAlignment="center" + android:text="@string/about_copyright"/> + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_debug.xml b/app/src/main/res/layout/fragment_debug.xml deleted file mode 100644 index d7a896b..0000000 --- a/app/src/main/res/layout/fragment_debug.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 000dea7..69573e8 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -21,6 +21,14 @@ android:text="@string/home_status_init" android:textAppearance="@style/TextAppearance.AppCompat.Large" /> + + - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_debug_chart.xml b/app/src/main/res/layout/sheet_debug_chart.xml deleted file mode 100644 index df400c4..0000000 --- a/app/src/main/res/layout/sheet_debug_chart.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/subdialog_add_bonded.xml b/app/src/main/res/layout/subdialog_add_bonded.xml index a94c654..d595f72 100644 --- a/app/src/main/res/layout/subdialog_add_bonded.xml +++ b/app/src/main/res/layout/subdialog_add_bonded.xml @@ -7,7 +7,7 @@ @@ -18,13 +18,6 @@ android:layout_marginBottom="10dp" android:layout_marginHorizontal="10dp"/> - - + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/subdialog_calibrate_finished.xml b/app/src/main/res/layout/subdialog_calibrate_finished.xml index 742a217..4ec34b7 100644 --- a/app/src/main/res/layout/subdialog_calibrate_finished.xml +++ b/app/src/main/res/layout/subdialog_calibrate_finished.xml @@ -12,9 +12,4 @@ android:text="@string/dialog_calibrate_finished_message" android:layout_marginBottom="10dp"/> - - \ No newline at end of file diff --git a/app/src/main/res/layout/subdialog_calibrate_samplingrate.xml b/app/src/main/res/layout/subdialog_calibrate_happening.xml similarity index 76% rename from app/src/main/res/layout/subdialog_calibrate_samplingrate.xml rename to app/src/main/res/layout/subdialog_calibrate_happening.xml index 9d94cdd..56e8079 100644 --- a/app/src/main/res/layout/subdialog_calibrate_samplingrate.xml +++ b/app/src/main/res/layout/subdialog_calibrate_happening.xml @@ -9,7 +9,13 @@ + android:text="@string/dialog_calibrate_happening_message"/> + + - + - SmartMouse Home Verbinden Maus @@ -15,19 +14,19 @@ Ihr Gerät scheint das Bluetooth HID Profil nicht zu unterstützen, das für diese App benötigt wird.
Nicht alle Geräte unterstützen dieses Profil. Manchmal kann durch ein Firmware Update unterstüztung für dieses Profil erreicht werden.
Im Play Store öffnen Nicht Verbunden - Zum Verbinden - Sie sind verbunden mit: + Verbinden + Verbunden mit: Verbunden Zur Maus %s vergangen - Damit diese App richtig funktionieren kann, muss Bluetooth angeschaltet sein, da Verbindungen zum Zielgerät über Bluetooth hergestellt werden. + Verbindungen zum Zielgerät werden über Bluetooth hergestellt. Schalte deshalb Bluetooth ein, um diese App zu benutzen. Neu Laden Deaktiviert - Bluetooth aktivieren + Bluetooth Aktivieren Verbunden Nicht Verbunden - Wählen sie ein Gerät: - Neues Gerät + Wählen Sie ein Gerät: + Gerät Hinzufügen Weiter Abbrechen Manuell Hinzufügen @@ -39,30 +38,28 @@ Stellen sie sicher dass ihr Gerät sichtbar ist, um es hier zu sehen. Manchmal muss dies extra angeschaltet werden. Name des Gerätes MAC Adresse des Gerätes - Bitte geben sie einen Namen an. - Bitte geben sie eine richtige MAC Adresse an. + Bitte geben sie einen Namen an + Bitte geben sie eine korrekte MAC Adresse an "Manuell " - Bitte entfernen sie das Zielgerät in den Bluetooth Einstellungen von diesem Gerät UND entfernen sie dieses Gerät in den Bluetooth Einstellungen des Zielgeräts. - Damit diese App verbinden kann, sollte das Smartphone nicht mit dem Zielgerät gekoppelt sein. - Wenn das Smartphone mit dem Zielgerät schon gekoppelt ist kann dies zu Verbindungsproblemen führen. - Entkopplen sie das Zielgerät. - Ihr gerät ist immer noch mit dem Zielgerät gekoppelt. - Sie haben erfolgreich hinzugefügt: + Falls sie in der Zukunft Verbindungsprobleme mit diesem Gerät haben, versuchen sie das Zielgerät von diesem Gerät UND dieses Gerät vom Zielgerät in den jeweiligen Bluetooth-Einstellungen zu entfernen. + Ihr Gerät ist im moment bereits mit dem Zielgerät verbunden. Bei manchen Geräten kann dies zu Verbindungsproblemen führen. + Bereits Gekoppelt + Ihr Gerät ist immer noch mit dem Zielgerät gekoppelt. + Erfolgreich hinzugefügt: Erfolgreich Fertig - Sie haben dieses gerät bereits hinzugefügt. - Falls sie ihr gerät unter den bekannten geräten finden, könnte es unter einem anderen Namen registriert sein. + Sie haben dieses Gerät bereits hinzugefügt. + Falls sie ihr Gerät nicht unter den bekannten Geräten finden, könnte es unter einem anderen Namen gespeichert sein. Fertig - Bereits hinzugefügt + Bereits Hinzugefügt Unbekannt Zuletzt genutzt: %s Name MAC Adresse Zuletzt genutzt am - Bitte geben sie die Daten ihres Zielgerätes ein: Fertig Gerät Entfernen - Geräte Information + Geräteinformationen dd.MM.yy Nie Noch nie @@ -70,7 +67,6 @@ Verbindung wird hergestellt… Zur Maus Verbindung trennen - "Zeit verstrichen: " Verbunden mit: %s vergangen Verbinden… @@ -78,22 +74,20 @@ Verbindung fehlgeschlagen mit: "Stelle sicher dass … " … das Zielgerät in der Nähe ist. - … Bluetooth auf dem Zielgerät angeschaltet ist. - … ihr Zielgerät HID Geräte unterstützt und verbindungen mit dem Android Bluetooth HID Profile eingehen kann. + … Bluetooth auf dem Zielgerät eingeschaltet ist. + … das Zielgerät HID Geräte unterstützt und verbindungen mit dem Android Bluetooth HID Profile eingehen kann. Mögliche Lösungen zu anderen Problemen könnten sein … - "… dieses Gerät vom Zielgerät und umgekehrt zu entkoppeln in den jeweiligen Bluetooth Einstellungen. " + "… dieses Gerät vom Zielgerät und umgekehrt zu entkoppeln in den jeweiligen Bluetooth-Einstellungen. " … diesem Gerät auf dem Zielgerät zu vertrauen, damit dieses von alleine Verbindungen aufbauen kann. Zurück Dieses Gerät entfernen Einstellungen - Bitte warten sie, während die App die Sampling Rate ihres Beschleunigungssensors analysiert. - Sampling Rate wird analysiert + Bitte warten sie, während die App ihre Sensoren analysiert und interne werte kalibriert. Dieser Prozess kann bis zu einer Minute dauern. Fertig - Dieser Prozess wird ca. %s Sekunden dauern. - "Die Sampling Rate ihres gerätes wurde erfolgreich analysiert. Ihre Beschleunigungssensor Sampling Rate beträgt: " - %d Samples pro Sekunde - Analyse abgeschlossen - Wollen sie ihre Einstellungen auf die standart Einstellungen zurücksetzen? + Initialisierung… + Erfolgreich Kalibration abgeschlossen. Sie können die App nun nutzen. + Kalibration abgeschlossen + Wollen sie ihre geänderten Einstellungen zurücksetzen? Zurücksetzen Abbrechen Ihre gespeicherten Geräte wurden erfolgreich entfernt. @@ -102,119 +96,126 @@ Abbrechen Ihre Einstellungen wurden erfolgreich zurückgesetzt. Weiter - Damit diese App gut als eine Maus funktioneren kann, muss die Konfiguration an ihr Gerät angepasst werden. Sie können die App nach dem Analysierprozess verwenden. + Damit diese App gut als eine Maus funktioneren kann, muss die Konfiguration an ihr Gerät angepasst werden. Sie können die App nach dem Kalibrierprozess verwenden. Abbrechen Fertig Sampling Rate wird analysiert Einrichten Abschliessen Einrichten Abgeschlossen - Die Analyse wurde erfolgreich abgeschlossen. Sie können die Maus jetzt verwenden. - Stellen sie sicher dass sie ihr Smartphone auf eine ebene Oberfläche platzieren, damit die Maubewegung optimal funktioniert. + Die Kalibration wurde erfolgreich abgeschlossen. Sie können die Maus jetzt verwenden. + Platzieren Sie ihr Smartphone auf einer grossen, ebenen Oberfläche, damit die Maubewegung optimal funktioniert. Um den Mauszeiger zu verschieben, können sie das Smartphone wie eine normale Computer Maus bewegen. - Nutzen sie die Tasten auf dem Bilschirm um Klicks durchzuführen. Wischen sie auf dem mittleren Button um zu scrollen, halten sie ihn gedrückt, um die mittlere Maustaste zu drücken. - Um die Mausoberfläche zu verlassen können sie von Links hineinwischen, um die Navigation einzublenden. + Nutzen sie die Tasten auf dem Bildschirm um Klicks durchzuführen. Wischen sie auf dem mittleren Button um zu scrollen, halten sie ihn gedrückt, um die mittlere Maustaste zu drücken. + Um die Mausoberfläche zu verlassen, können sie von Links hineinwischen, um die Navigation einzublenden. Weiter Benutzung Zeige diese Anleitung nicht mehr. Sie können die Maus jetzt verwenden. - Damit eine App Bluetooth Geräte in der Nähe sehen kann, muss sie Positionsberechtigungen haben. Wenn sie Bluetooth Geräte aus ihrer Nähe hinzufügen wollen, sollten sie der App diese Rechte geben. Falls sie diese Rechte nicht geben wollen, können sie ihr Gerät immer noch manuel hinzufügen. - Sie sollten der App Positionsberechtigungen geben, um weiter zu gehen. - Positionsrechte - Sie sollten Position anschalten, um weiter zu gehen. + Damit eine App Bluetooth Geräte in der Nähe sehen kann, muss sie Standortberechtigungen haben. Wenn sie Bluetooth Geräte aus ihrer Nähe hinzufügen wollen, sollten sie der App diese Rechte geben. Falls sie diese Rechte nicht geben wollen, können sie ihr Gerät immer noch manuel hinzufügen. + Sie sollten der App Standortberechtigungen geben, um weiter zu gehen. + Standortsrechte + Sie müssen den Standort anschalten, um weiter zu gehen. Damit sie Geräte aus ihrer Nähe hinzufügen können, sollten sie Position anschalten. Falls sie dies nicht anschalten können, können sie ihr Gerät manuell hinzufügen. Aktiviere Position Über Version:  - Zuletzt geupdated am:  - Diese App ist eine Machbarkeitststudie darüber, ob es möglich ist, ein Smartphone als normale Computer Maus zu verwenden. Deshalb sollte diese App nicht als ein fertiges Produkt behandelt werden. - Diese App ist auch teil einer Maturaarbeit von Joshua Stalder. - Entwickelt von Joshua Stalder - Der Quellcode für diese App ist öffentlich auf GitHub. - Debugger - Debugger - Fertig - X Achse - Z Achse - Y Achse - Aktuelle Achse - Angezeigte Serien - Andere Optionen - Anzahl Samples für ein Datenpunkt + Zuletzt aktualisiert am:  + Diese app versucht, ihr Smartphone als normale Computer Maus nutzbar zu machen. Sie ermittelt die Position des Smartphones mithilfe der Daten vom Beschleunigungs- und Gyrosensors, welche in einem speziellen Sensor-Fusions-Algorithmus verarbeitet werden. Leider ist diese ermittlung nicht immer korrekt, was zu verschiedenen Artefakten und falschen Bewegungen des Mauszeigers führen. Aufgrund dessen ist diese App noch lange nicht fertig, und sollte nicht als reifes Produkt behandelt werden. + Diese App ist opensource und kann auf GitHub gefunden werden. Falls dieser Prozess zu lange dauert, könnten sie die App neu starten und ein anderes Gerät auswählen. Bluetooth Übertragungs Rate - Wie oft pro sekunde werden Daten an den Host übermittelt. + Wie oft werden daten übermittelt (Hz) Alle bekannten Geräte entfernen - Entfernt alle Geräte die zufor bei der App hinzugefügt wurden. + Entferne alle bereits hinzugefügten Geräte von der App entfernen Einstellungen Zurücksetzen - Setzt alle Einstellungen auf ihre Standarteinstellung zurück. - Aktiviere Debugger - Aktiviert die Debugger Seite für fortgeschrittene anwendungen. + Setze alle Einstellungen auf ihre Standarteinstellung zurück. + Debugging + Aktiviere und konfiguriere debugging funktionen Bewegung - Bestimmt wie die Bewegung der Maus berechnet wird. + Bestimme wie die Bewegung der Maus berechnet wird Kommunikation - Bestimmt wie die App mit dem Host kommuniziert. + Bestimme wie die App mit dem Host kommuniziert Oberfläche - Bestimmt wie die Mausoberfläche aussieht. + Bestimme wie die Mausoberfläche aussieht Theme - Setzt das generelle Aussehen der Oberfläche. - Wie viel wisch-distanz einer scroll einheit entspricht. + Setze das generelle Aussehen der Oberfläche. + Wie viel wisch-distanz enspricht einer scroll einheit Scroll Schrittgrösse - "Sets wie lange in Millisekunden der mittlere Mausbutton gedrückt werden muss, um einen Mittelklick auszulösen. " + Wie lange muss die mittlere Maustaste gedrückt werden, um einen Klick auszulösen (ms) Mittelklick Verzögerung - Aktiviere Visuals - Setzt die dicke der Linien der Buttons. + Aktiviere Visuelles + Dicke der Linien der Buttons Strichdicke - Setzt die intensität der Visuals. + Intensität der visuellen Elemente Intensität - Aktiviert Vibrationen - Setzt die Vibrationsintensität der Maustasten. - Setzt die Vibrationsintensität des Scrollrades. - Setzt die Vibrationsintensität von sekundären Ereignissen wie der mittleren Maustaste. + Aktiviere Vibrationen + Vibrationsintensität der Maustasten + Vibrationsintensität des Scrollrades + Vibrationsintensität von sekundären Ereignissen wie der mittleren Maustaste Tasten Intensität - Setzt die Vibrationslänge der Maustasten in Millisekunden. - Setzt die Vibrationslänge des Mausrades in Millisekunden. - Setzt die Vibrationslänge von sekundären Ereignissen wie der mittleren Maustaste in Millisekunden. + Vibrationslänge der Maustasten (ms) + Vibrationslänge eines Schrittes des Mausrades (ms) + Vibrationslänge von sekundären Ereignissen wie der mittleren Maustaste (ms) Tasten Länge Mausrad Intensität Mausrad Länge Sekundäre Intensität Sekundäre Länge - Setzt die Länge der Maustasten. - Länge + Setzt die Höhe der Maustasten + Höhe Breite mittlere Maustaste - Setzt die Breite der mittleren Maustaste. + Breite der mittleren Maustaste. Maus Sensitivität - Setzt wie schnell sich die Maus bewegt. - Geschwindigkeit Skalieren - Setzt ob die App die Geschwindigkeit des Mauszeigers skalieren soll. - Sampling Rate analysieren - Evaluiert automatisch die Sampling Rate ihres Sensors. - Reale Sampling Rate - Setzt die reale Rate mit der der Sensor Samples liefert. - Ordnung - Trennfrequenz - Einfrier Grenzwert - Entfrier Grenzwert - Entfrier Samples - Grenzwert - Reset Samples - Minimale Cache Dauer - Minimale Cache Dauer - Grenzwert um Aufzulösen - Exponent - Trenngeschwindigkeit - Samples - Samples nacheinander + Wie schnell bewegt sich der Mauszeiger + Erneut Kalibrieren + Kalibriert den algorithmus nach der Sampling Rate und dem Noise ihrer Sensoren Samples pro Sekunde - Beschleunigungs Freezer - Tiefpass Filter - Sampling Rate - Noise Cancelling - Geschwindigkeits Vorzeichen Cache - Skalierung + Gemessene Sampling Rate + Schwellenwert Kalibration Verhalten - Visuals + Visuelles Vibrationen Layout Um diese App als eine Maus zu benutzen, sollten sie zuerst mit einem Zielgerät verbinden. Dies kann auf der Verbindungsseite erledigt werden. + Algorithmus Kalibrieren + Weiter + Analysiere Sampling Rate… + Analysiere Noise höhe… + Berühren oder bewegen Sie ihr Gerät nicht, bis die Kalibration abgeschlossen ist. + Damit diese App korrekt funktionieren kann, muss sie sich zuerst für die Sensoren auf ihrem Gerät kalibrieren. Während der Kalibration muss das Gerät speziell behandelt werden. + Also, platzieren Sie ihr Gerät auf einer flachen, stabilen Oberfläche, welche sich nicht bewegt. Tippe \"Weiter\" und berühren sie ihr Gerät nicht, bis die Kalibration abeschlossen ist. + Sampling Rate Analyse + Sekunden + Noise Höhe Analyse + Kalibrationsdauer + Verhältniss für Beschleunigungswahl + Verhältniss für Rotationswahl + Faktor für Beschleunigung + Faktor für Rotation + Algorithmus Zeiten + Schwellwert Endzeit + Aktivierung Gravitation Fenster + Aktivierung Noise Fenster + Gravitation Durchschittsfenster + Aktivierungsschwellenwerte + Absolute Beschleunigung + Absolute Winkelgeschwindigkeit + Algorithmus Funktionalität + Rotiere Gravitation + SensorServer Hostname + Aktiviere Debug Übertragung + Aktiviere Sensor- und Algorithmusdaten an einen computer + SensorServer Port + Debugging ist deaktiviert + Verbunden mit %s + Versuche Verbindung mit %s herzustellen + Den Gravitationswert zu rotieren ist experimentell und könnte falsch funktioneren + Sampling Rate + "Zeige erweiterte Einstellungen an, welche teilweise schwierig zu verstehen sind " + Erweiterte Einstellungen + Öffne die GitHub seite um SensorServer herunterzuladen + Download SensorServer + Server Konfiguration + Übertrage daten an einen SensorServer + Damit diese Einstellungen angewandt werden, muss die App neugestartet werden.
\ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index ea71bed..9405349 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -5,7 +5,6 @@ #0f763e #151B3F #2A377E - #3D4EAC #6474CA @@ -20,9 +19,6 @@ #707070 - #000000 - #919191 - #6A666666 #868686 #242424 #6A666666 @@ -30,6 +26,4 @@ #000000 #B1B1B1 - - #808080 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3216e76..147eb3f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ - SmartMouse + SmartMouse Home Connect Mouse @@ -14,12 +14,12 @@ Your device does not seem to support the bluetooth HID profile that is required to run this app.
Not all devices do support this profile. Sometimes, support for this profile may be added in a firmware update.
Open in Play Store Disconnected - To Connect - Your are connected to: + Connect + Connected to: Connected To Mouse %s elapsed - This app requires bluetooth to work properly, since connections to the target device are established via bluetooth. + Connections to the target device are established via bluetooth. Please enable bluetooth to use this app. Refresh Disabled Enable Bluetooth @@ -37,36 +37,33 @@ Make sure your target device is discoverable in order to see it here. In some cases this must be enabled explicitly. Device Name Device MAC Address - Please enter a Name for your device. - Please enter a valid MAC address. - Please make sure to remove the target device from your phone AND your phone from the target device in the respective bluetooth settings. - Being paired with the target device will lead to connection problems in the future. - Unpair the target device. + Please enter a Name + Please enter a valid MAC address + Your device is currently already paired to the target device. On some devices, this might lead to connection issues in the future. + So if you experience connection issues with this target device, try to remove the target device from this device AND this device from the target device in the respective bluetooth settings. Your device is still paired to the target device. - You have successfully added: + Successfully added device: Success Done You have already added this device. - If you do not see your device under the added devices, it may be registered under an old name. + If you do not see your device under the added devices, it may be stored under an old name. Done - Already added + Already Added Unknown Last used: %s Name MAC Address Last used at - Please enter the details of your target device: Done Remove device Device Information - dd.MM.yy + dd/MM/yy Never Not yet - dd.MM.yy + dd/MM/yy Establishing connection… To Mouse Disconnect - Time Elapsed: Connected to: %s elapsed Connecting… @@ -75,21 +72,23 @@ Make sure that … … the target device is in range. … bluetooth is enabled on the target device. - … your host device supports HID devices and connections with the Android bluetooth HID profile. + … the target device supports HID devices and connections with the Android Bluetooth HID Profile. Possible fixes to other problems may be to … … remove this device from your target device and the host from this device in the respective bluetooth settings. … trust this device on the target device so that this device can connect to it on its own. Back Remove this device Settings - Please wait while the app is analyzing the sampling rate of your Accelerometer. - Analyzing Sampling Rate + Please wait while the app is analyzing your sensors and calibrating its values. This process can take up to one minute. + Calibrating Algorithm + Next Done - This process will take about %d seconds. - The sampling rate for this app was analyzed successfully. Your Accelerometer sampling rate for this device is: - %d Samples per Second - Analyzing Finished - Do you want to reset your settings to the default settings? + Initializing… + Analyzing sampling rate… + Analyzing noise levels… + Successfully completed the calibration procedure. You can proceed to use the App now. + Calibration finished + Do you want to reset your changed settings? Reset Cancel All your saved devices have been removed successfully. @@ -98,14 +97,14 @@ Cancel Your settings have been reset successfully. Next - In order for this app to work properly as a mouse, its configuration first has to be acustomed to your device. You can use the app after the analyzing process. + In order for this app to work properly as a mouse, its configuration first has to be acustomed to your device. You can use the app after the calibration process. Cancel Done Analyzing sampling rate Finish setup Setup finished - The analyzing has ended successfully. You may now use the mouse. - In order for the mouse movement to function properly, make sure to place your smartphone on a large, even and flat surface. + The calibration has completed successfully. You may now use the mouse. + In order for the mouse movement to function ideally, make sure to place your smartphone on a large, even and flat surface. To move the mouse cursor, move the smartphone as if it was a normal computer mouse. Use the buttons on screen to perform clicks. Swipe on the middle button to scroll, hold to make a middle click. In order to leave the mouse interface, swipe in from the left to reveal the navigation drawer. @@ -113,113 +112,116 @@ Usage Do not show these instructions again. You may now use the mouse. - In order to see nearby devices, an app is required to have location permissions. If you want to see and add your nearby devices, please go on and grant the location permissons. If you do not want to give location permissoins to this app, you can still add your device manually. - You need to grant the app location permissions, to continue. + In order to see nearby bluetooth devices, an app is required to have location permissions. If you want to see and add your nearby devices, please go on and grant the location permissons. If you do not want to give location permissoins to this app, you can still add your device manually. + You need to grant the app location permissions to continue. Grant Location You need to enable location to continue. In order to see nearby devices, you must have location turn on. If you don\'t want to enable location, you can still add a device manually. Enable Location About - 1.3.0 + 1.4.0 Version:  - Last Updated at:  - 21.09.21 - This app is a feasibility study about whether it is possible to use a smartphone as a normal computer mouse. As a result, the app should not be treated as a finished product. - This app is also part of a Matura Paper by Joshua Stalder. - Developed by Joshua Stalder - © 2021 - The source code for this app is available on GitHub. - Debugger - Debugger - Done - X Axis - Z Axis - Y Axis - Current Axis - Visible Series - Other Settings - Amount of samples for one datapoint + Last updated at:  + 31.03.23 + This app tries to make your smartphone usable as a normal computer mouse. It estimates its the position of the Smartphone by processing accelerometer and gyroscope data in its custom sensor-fusion algorithm. However, this estimation is not always correct, which leads to different artifacts and wrong movements of the cursor. Because of that, this app is far from finished and should not be treated as a finished product. + This app is open source and may be found on GitHub. If this process is taking too long, you may restart the app and select another device. Bluetooth Transmission Rate - How many times per second is data sent to the host. + How frequently is data transmitted (Hz) Remove all known devices - Removes all devices you have previously added to the app. + Remove all added devices from the app Reset Settings - Resets all settings to their default. - Enable Debugger - Enables the debugger page for advanced usage. + Reset all settings to their default + Debugging + Enable and configure debugging features Movement - Controls how the movement of the mouse is calculated. + Control how the movement of the mouse is calculated Communication - Controls how the app communicates with the host. + Control how the app communicates with the host Interface - Controls the way the mouse interface looks. + Control the way the mouse interface looks Theme - Sets the general look of the interface. - How much swipe distance equals one scroll step. + Set the general look of the interface + How much swipe distance equals one scroll step Scroll Step Size - Sets how long the middle button has to be pressed for a click in milliseconds. + How long the has the middle button to be pressed for a click (ms) Middle Click Delay Enable Visuals - Sets the weight of the button outline. + Stroke weight of the button outline Stroke Weight - Sets the intensity of the visuals. Intensity Enable Vibrations - Controls the vibration intensity of the buttons. + Vibration intensity of the buttons Button Intensity - Sets the length of the vibration of the button in milliseconds. + Length of the vibration of the buttons (ms) Button Length - Controls the vibration intensity of the scroll wheel. + Vibration intensity of the scroll wheel Scroll Intensity - Sets the length of the vibration of one scroll wheel step in milliseconds. + Length of the vibration of one scroll wheel step (ms) Scroll Length - Controls the vibration intensity of secondary events, like the middle button. + Vibration intensity of secondary events, like the middle button Secondary Intensity - Sets the length of the vibration of a secondary event. + Length of the vibration of a secondary event (ms) Secondary Length - Controls the Height of the Mouse Buttons. + Controls the Height of the Mouse Buttons Height - Controls how wide the middle mouse button is. + How wide the middle mouse button is Middle Button Width Mouse Sensitivity - Controls how fast the mouse moves. - Scale Speed - Sets whether the app should scale the mouse movement speed. - Analyze Sampling Rate - Automatically evaluate the sampling rate of your sensor. - Real Sampling Rate - Sets the real rate the sensor can deliver samples. - Order - Cutoff Frequency - Freezing Threshold - Unfreezing Threshold - Unfreezing Samples - Threshold - Reset Samples - Minimal Cache Duration - Maximal Cache Duration - Threshold to Release - Power - Split Velocity - m/s + How fast does the mousecursor move + Calibrate Again + Calibrate the algorithm to the sampling rate and noise of your sensors m/s² - Samples - Samples in a Row - Hz Samples per Second Connected Manual - The target device should be paired with this smartphone so that this app can connect to it. - Acceleration Freezer - Low Pass Filter - Sampling Rate - Noise Cancelling - Velocity Sign Cache - Scaler + Measured Sampling Rate + Threshold Calibration Behaviour Visuals Vibrations Layout To use this app as a mouse, you should connect to your target device first. You can do so on the connection page. + Do not touch or move your device until the calibration is finished. + In order to function correctly, this app first needs to calibrate itself for the sensors present in your device. During the calibration, the device should be treated correctly. + Now, place your phone on a flat, stable surface that is not moving. Click \"Next\" and don\'t touch your phone until the calibration is finished. + Sampling Rate Analysis + Seconds + Noise Level Analysis + Calibration Duration + Ratio for Acceleration Choice + Ratio for Rotation Choice + Factor for Acceleration + Factor for Rotation + Algorithm Durations + Threshold Dropoff + Activation Gravity Window + Activation Noise Window + Gravity Average Length + Activation Thresholds + Absolute Acceleration + Absolute Angular Velocity + rad/s + Algorithm Functionality + Do Gravity Rotation + SensorServer Hostname + Enable Debug Transmission + Transmit sensor and algorithm data to a computer + SensorServer Port + Debugging is disabled + Connected to %s + Attempting connection to %s + Rotating the gravity value is experimental and might not work correctly + Sampling Rate + Enables the display of advanced settings which might be difficult to understand + Advanced Settings + Open the GitHub page to download SensorServer + Download SensorServer + Server Configuration + Transmit data to a SensorServer + For any of these settings to take effect, this app must be restarted. + https://github.com/VirtCode/SensorServer + "© 2023 VirtCode" + Already Paired + Intensity of the visual elements
\ No newline at end of file diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml index 8e63745..7e7b5d4 100644 --- a/app/src/main/res/xml/settings.xml +++ b/app/src/main/res/xml/settings.xml @@ -19,13 +19,23 @@ app:summary="@string/settings_main_movement_description" app:title="@string/settings_main_movement_title" /> + + + app:key="advanced" + app:summary="@string/settings_main_advanced_description" + app:title="@string/settings_main_advanced" /> diff --git a/app/src/main/res/xml/settings_communication.xml b/app/src/main/res/xml/settings_communication.xml index c46f348..f621448 100644 --- a/app/src/main/res/xml/settings_communication.xml +++ b/app/src/main/res/xml/settings_communication.xml @@ -1,5 +1,6 @@ - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/settings_interface.xml b/app/src/main/res/xml/settings_interface.xml index 18a84a9..5c9bb75 100644 --- a/app/src/main/res/xml/settings_interface.xml +++ b/app/src/main/res/xml/settings_interface.xml @@ -2,97 +2,117 @@ + app:entries="@array/themes" + app:entryValues="@array/themeValues" + app:key="interfaceTheme" + app:summary="@string/settings_interface_theme_description" + app:title="@string/settings_interface_theme_title"/> - + + app:key="interfaceBehaviourScrollStep" + app:summary="@string/settings_interface_behaviour_scroll_description" + app:title="@string/settings_interface_behaviour_scroll_title"/> + app:key="interfaceBehaviourSpecialWait" + app:summary="@string/settings_interface_behaviour_middle_description" + app:title="@string/settings_interface_behaviour_middle_title"/> + app:initialExpandedChildrenCount="1" + app:title="@string/settings_interface_visuals"> + app:key="interfaceVisualsEnable" + app:title="@string/settings_interface_visuals_enable_title"/> + app:isPreferenceVisible="false" + app:key="interfaceVisualsStrokeWeight" + app:summary="@string/settings_interface_visuals_stroke_description" + app:title="@string/settings_interface_visuals_stroke_title" + app:dependency="interfaceVisualsEnable"/> + app:isPreferenceVisible="false" + app:key="interfaceVisualsIntensity" + app:summary="@string/settings_interface_visuals_intensity_description" + app:title="@string/settings_interface_visuals_intensity_title" + app:dependency="interfaceVisualsEnable"/> + app:initialExpandedChildrenCount="1" + app:title="@string/settings_interface_vibrations"> + app:key="interfaceVibrationsEnable" + app:title="@string/settings_interface_vibrations_enable_title"/> + app:isPreferenceVisible="false" + app:key="interfaceVibrationsButtonIntensity" + app:summary="@string/settings_interface_vibrations_button_intensity_description" + app:title="@string/settings_interface_vibrations_button_intensity_title" + app:dependency="interfaceVibrationsEnable"/> + app:isPreferenceVisible="false" + app:key="interfaceVibrationsButtonLength" + app:summary="@string/settings_interface_vibrations_button_length_description" + app:title="@string/settings_interface_vibrations_button_length_title" + app:dependency="interfaceVibrationsEnable"/> + app:isPreferenceVisible="false" + app:key="interfaceVibrationsScrollIntensity" + app:summary="@string/settings_interface_vibrations_wheel_intensity_description" + app:title="@string/settings_interface_vibrations_wheel_intensity_title" + app:dependency="interfaceVibrationsEnable"/> + app:isPreferenceVisible="false" + app:key="interfaceVibrationsScrollLength" + app:summary="@string/settings_interface_vibrations_wheel_length_description" + app:title="@string/settings_interface_vibrations_wheel_length_title" + app:dependency="interfaceVibrationsEnable"/> + app:isPreferenceVisible="false" + app:key="interfaceVibrationsSpecialIntensity" + app:summary="@string/settings_interface_vibrations_special_intensity_description" + app:title="@string/settings_interface_vibrations_special_intensity_title" + app:dependency="interfaceVibrationsEnable"/> + app:isPreferenceVisible="false" + app:key="interfaceVibrationsSpecialLength" + app:summary="@string/settings_interface_vibrations_special_length_description" + app:title="@string/settings_interface_vibrations_special_length_title" + app:dependency="interfaceVibrationsEnable"/> - + + app:key="interfaceLayoutHeight" + app:summary="@string/settings_interface_layout_height_description" + app:title="@string/settings_interface_layout_height_title"/> + app:key="interfaceLayoutMiddleWidth" + app:summary="@string/settings_interface_layout_middle_description" + app:title="@string/settings_interface_layout_middle_title"/> diff --git a/app/src/main/res/xml/settings_movement.xml b/app/src/main/res/xml/settings_movement.xml index ee1217a..42be358 100644 --- a/app/src/main/res/xml/settings_movement.xml +++ b/app/src/main/res/xml/settings_movement.xml @@ -1,121 +1,134 @@ + app:initialExpandedChildrenCount="2"> - - + app:key="movementSensitivity" + app:summary="@string/settings_movement_sensitivity_description" + app:title="@string/settings_movement_sensitivity_title"/> + app:key="movementRecalibrate" + app:summary="@string/settings_movement_calibrate_description" + app:title="@string/settings_movement_calibrate_title"/> - + - + - + - + + app:isPreferenceVisible="false" + app:key="movementCalibrationNoise" + app:showValueAsSummary="true" + app:title="@string/settings_movement_calibration_duration_noise" + app:valueUnit="@string/settings_movement_unit_second"/> - + + app:isPreferenceVisible="false" + app:key="movementNoiseRatioAcceleration" + app:showValueAsSummary="true" + app:title="@string/settings_movement_noise_ratio_acceleration"/> + app:isPreferenceVisible="false" + app:key="movementNoiseFactorAcceleration" + app:showValueAsSummary="true" + app:title="@string/settings_movement_noise_factor_acceleration"/> - + + + - + + app:isPreferenceVisible="false" + app:key="movementThresholdAcceleration" + app:showValueAsSummary="true" + app:title="@string/settings_movement_threshold_acceleration" + app:valueUnit="@string/settings_movement_unit_acceleration"/> - + - - - - - + + app:isPreferenceVisible="false" + app:key="movementSampling" + app:showValueAsSummary="true" + app:title="@string/settings_movement_sampling_rate" + app:valueUnit="@string/settings_movement_unit_samplesecond"/> - - + + + + + + + + app:isPreferenceVisible="false" + app:key="movementDurationGravity" + app:showValueAsSummary="true" + app:title="@string/settings_movement_duration_gravity" + app:valueUnit="@string/settings_movement_unit_second"/> + \ No newline at end of file diff --git a/brand/title-dark.svg b/brand/title-dark.svg new file mode 100644 index 0000000..7f72915 --- /dev/null +++ b/brand/title-dark.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/brand/title-light.svg b/brand/title-light.svg new file mode 100644 index 0000000..a87456e --- /dev/null +++ b/brand/title-light.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + +