├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── xml
│ │ │ │ ├── paths.xml
│ │ │ │ ├── settings_communication.xml
│ │ │ │ ├── settings_debugging.xml
│ │ │ │ └── settings.xml
│ │ │ ├── drawable
│ │ │ │ ├── connect_status.xml
│ │ │ │ ├── home_status.xml
│ │ │ │ ├── icon_background.xml
│ │ │ │ ├── drawer_home.xml
│ │ │ │ ├── bar_menu.xml
│ │ │ │ ├── settings_movement.xml
│ │ │ │ ├── drawer_about.xml
│ │ │ │ ├── device_connect_info.xml
│ │ │ │ ├── device_add_smartphone.xml
│ │ │ │ ├── drawer_mouse.xml
│ │ │ │ ├── device_add_computer.xml
│ │ │ │ ├── device_connect.xml
│ │ │ │ ├── drawer_connect.xml
│ │ │ │ ├── settings_communication.xml
│ │ │ │ ├── settings_interface.xml
│ │ │ │ ├── device_add_other.xml
│ │ │ │ ├── drawer_debug.xml
│ │ │ │ ├── drawer_settings.xml
│ │ │ │ └── icon_foreground.xml
│ │ │ ├── layout
│ │ │ │ ├── fragment_mouse.xml
│ │ │ │ ├── subfragment_home_disconnected.xml
│ │ │ │ ├── subdialog_mouse_message.xml
│ │ │ │ ├── dialog_add.xml
│ │ │ │ ├── dialog_mouse.xml
│ │ │ │ ├── subdialog_calibrate_finished.xml
│ │ │ │ ├── dialog_calibrate.xml
│ │ │ │ ├── subdialog_add_already.xml
│ │ │ │ ├── subdialog_calibrate_begin.xml
│ │ │ │ ├── subdialog_add_success.xml
│ │ │ │ ├── subdialog_mouse_usage_finished.xml
│ │ │ │ ├── subdialog_add_requestsetting.xml
│ │ │ │ ├── subdialog_add_requestpermission.xml
│ │ │ │ ├── subfragment_home_unsupported.xml
│ │ │ │ ├── subfragment_home_disabled.xml
│ │ │ │ ├── subfragment_connect_select.xml
│ │ │ │ ├── navigation_drawer_header.xml
│ │ │ │ ├── subdialog_add_select.xml
│ │ │ │ ├── item_add_select.xml
│ │ │ │ ├── subdialog_add_bonded.xml
│ │ │ │ ├── subdialog_calibrate_happening.xml
│ │ │ │ ├── subfragment_connect_connecting.xml
│ │ │ │ ├── subdialog_add_manual.xml
│ │ │ │ ├── subfragment_home_connected.xml
│ │ │ │ ├── fragment_connect.xml
│ │ │ │ ├── subfragment_connect_connected.xml
│ │ │ │ ├── dialog_info.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── fragment_home.xml
│ │ │ │ ├── item_connect_select.xml
│ │ │ │ ├── fragment_about.xml
│ │ │ │ └── subfragment_connect_failed.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ └── icon.xml
│ │ │ ├── values
│ │ │ │ ├── arrays.xml
│ │ │ │ ├── attrs.xml
│ │ │ │ ├── themes.xml
│ │ │ │ └── colors.xml
│ │ │ ├── values-night
│ │ │ │ └── themes.xml
│ │ │ └── menu
│ │ │ │ └── navigation_drawer.xml
│ │ ├── java
│ │ │ └── ch
│ │ │ │ └── virt
│ │ │ │ └── smartphonemouse
│ │ │ │ ├── ui
│ │ │ │ ├── connect
│ │ │ │ │ ├── dialog
│ │ │ │ │ │ ├── AddAlreadySubdialog.java
│ │ │ │ │ │ ├── AddRequestPermissionSubdialog.java
│ │ │ │ │ │ ├── AddRequestSettingSubdialog.java
│ │ │ │ │ │ ├── AddSuccessSubdialog.java
│ │ │ │ │ │ ├── AddBondedSubdialog.java
│ │ │ │ │ │ ├── AddManualSubdialog.java
│ │ │ │ │ │ └── InfoDialog.java
│ │ │ │ │ ├── ConnectConnectingSubfragment.java
│ │ │ │ │ ├── ConnectConnectedSubfragment.java
│ │ │ │ │ └── ConnectFailedSubfragment.java
│ │ │ │ ├── home
│ │ │ │ │ ├── HomeDisconnectedSubfragment.java
│ │ │ │ │ ├── HomeUnsupportedSubfragment.java
│ │ │ │ │ ├── HomeDisabledSubfragment.java
│ │ │ │ │ └── HomeConnectedSubfragment.java
│ │ │ │ ├── settings
│ │ │ │ │ ├── dialog
│ │ │ │ │ │ ├── CalibrateBeginSubdialog.java
│ │ │ │ │ │ ├── CalibrateFinishedSubdialog.java
│ │ │ │ │ │ ├── CalibrationHappeningSubdialog.java
│ │ │ │ │ │ └── CalibrateDialog.java
│ │ │ │ │ ├── SettingsDebuggingSubfragment.java
│ │ │ │ │ ├── SettingsCommunicationSubfragment.java
│ │ │ │ │ ├── SettingsMovementSubfragment.java
│ │ │ │ │ ├── SettingsInterfaceSubfragment.java
│ │ │ │ │ └── custom
│ │ │ │ │ │ ├── SeekIntegerPreference.java
│ │ │ │ │ │ └── SeekFloatPreference.java
│ │ │ │ ├── AboutFragment.java
│ │ │ │ ├── mouse
│ │ │ │ │ ├── MouseMessageSubdialog.java
│ │ │ │ │ ├── MouseUsageFinishedSubdialog.java
│ │ │ │ │ ├── MouseCalibrateDialog.java
│ │ │ │ │ └── MouseUsageDialog.java
│ │ │ │ ├── SettingsFragment.java
│ │ │ │ ├── ConnectFragment.java
│ │ │ │ └── HomeFragment.java
│ │ │ │ ├── mouse
│ │ │ │ ├── components
│ │ │ │ │ ├── Trapezoid2f.java
│ │ │ │ │ ├── Trapezoid3f.java
│ │ │ │ │ ├── WindowAverage.java
│ │ │ │ │ ├── WindowAverage3f.java
│ │ │ │ │ └── OrThreshold.java
│ │ │ │ ├── math
│ │ │ │ │ ├── Vec2f.java
│ │ │ │ │ └── Vec3f.java
│ │ │ │ ├── Calibration.java
│ │ │ │ ├── MouseInputs.java
│ │ │ │ └── MovementHandler.java
│ │ │ │ ├── transmission
│ │ │ │ ├── HostDevice.java
│ │ │ │ ├── hid
│ │ │ │ │ └── HidDescriptor.java
│ │ │ │ └── DeviceStorage.java
│ │ │ │ └── customization
│ │ │ │ ├── DefaultSettings.java
│ │ │ │ └── CalibrationHandler.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── ch
│ │ │ └── virt
│ │ │ └── smartphonemouse
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── ch
│ │ └── virt
│ │ └── smartphonemouse
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── brand
├── screenshots
│ ├── mouse.jpg
│ ├── device-selection.jpg
│ └── movement-settings.jpg
├── icon.svg
├── badge.svg
├── background.svg
└── banner.svg
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── .github
└── workflows
│ └── release.yml
├── gradlew.bat
└── README.md
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | rootProject.name = "SmartMouse"
--------------------------------------------------------------------------------
/brand/screenshots/mouse.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VirtCode/SmartMouse/HEAD/brand/screenshots/mouse.jpg
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VirtCode/SmartMouse/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/brand/screenshots/device-selection.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VirtCode/SmartMouse/HEAD/brand/screenshots/device-selection.jpg
--------------------------------------------------------------------------------
/brand/screenshots/movement-settings.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VirtCode/SmartMouse/HEAD/brand/screenshots/movement-settings.jpg
--------------------------------------------------------------------------------
/app/src/main/res/xml/paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | .cxx
10 | local.properties
11 | /app/release
12 | /secret
13 | app/keystore.jks
14 | .env
--------------------------------------------------------------------------------
/app/src/main/res/drawable/connect_status.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat May 22 12:39:29 CEST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_mouse.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/home_status.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Light
5 | Dark
6 |
7 |
8 |
9 | light
10 | dark
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/drawer_home.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bar_menu.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/settings_movement.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/drawer_about.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/device_connect_info.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/subfragment_home_disconnected.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/ch/virt/smartphonemouse/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/device_add_smartphone.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/drawer_mouse.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/device_add_computer.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/subdialog_mouse_message.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/connect/dialog/AddAlreadySubdialog.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.connect.dialog;
2 |
3 | import androidx.fragment.app.Fragment;
4 |
5 | import ch.virt.smartphonemouse.R;
6 |
7 | /**
8 | * This class displays the sub page of the add dialog when a device has already been added.
9 | */
10 | public class AddAlreadySubdialog extends Fragment {
11 | /**
12 | * Initializes the sub dialog.
13 | */
14 | public AddAlreadySubdialog() {
15 | super(R.layout.subdialog_add_already);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/home/HomeDisconnectedSubfragment.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.home;
2 |
3 | import androidx.fragment.app.Fragment;
4 |
5 | import ch.virt.smartphonemouse.R;
6 |
7 | /**
8 | * This sub fragment of the home page is shown when the app is not connected.
9 | */
10 | public class HomeDisconnectedSubfragment extends Fragment {
11 |
12 | /**
13 | * Creates this sub fragment.
14 | */
15 | public HomeDisconnectedSubfragment() {
16 | super(R.layout.subfragment_home_disconnected);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/device_connect.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_add.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_mouse.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/drawer_connect.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/subdialog_calibrate_finished.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/connect/ConnectConnectingSubfragment.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.connect;
2 |
3 | import androidx.fragment.app.Fragment;
4 |
5 | import ch.virt.smartphonemouse.R;
6 |
7 | /**
8 | * This is a sub fragment for the connect page.
9 | * This fragment gets displayed when the app is currently connecting to a device.
10 | */
11 | public class ConnectConnectingSubfragment extends Fragment {
12 |
13 | /**
14 | * Creates this sub fragment.
15 | */
16 | public ConnectConnectingSubfragment() {
17 | super(R.layout.subfragment_connect_connecting);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_calibrate.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/settings_communication.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/settings_interface.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/settings_communication.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
9 |
10 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/device_add_other.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/subdialog_add_already.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/drawer_debug.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/subdialog_calibrate_begin.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
19 |
20 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/res/layout/subdialog_add_success.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/mouse/components/Trapezoid2f.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.mouse.components;
2 |
3 | import ch.virt.smartphonemouse.mouse.math.Vec2f;
4 |
5 | /**
6 | * This class is used to calculate a number of following trapezoids. Here with a Vec2 as a value
7 | */
8 | public class Trapezoid2f {
9 |
10 | private Vec2f last = new Vec2f();
11 |
12 | /**
13 | * Calculates the current trapezoid
14 | * @param delta delta time to the last value
15 | * @param next current value
16 | * @return trapezoid
17 | */
18 | public Vec2f trapezoid(float delta, Vec2f next) {
19 | Vec2f result = last.mean(next).multiply(delta);
20 | last = next;
21 | return result;
22 | }
23 |
24 | /**
25 | * Resets the last value
26 | */
27 | public void reset() {
28 | last = new Vec2f();
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/mouse/components/Trapezoid3f.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.mouse.components;
2 |
3 | import ch.virt.smartphonemouse.mouse.math.Vec3f;
4 |
5 | /**
6 | * This class is used to calculate a number of following trapezoids. Here with a Vec3 as a value
7 | */
8 | public class Trapezoid3f {
9 |
10 | private Vec3f last = new Vec3f();
11 |
12 | /**
13 | * Calculates the current trapezoid
14 | * @param delta delta time to the last value
15 | * @param next current value
16 | * @return trapezoid
17 | */
18 | public Vec3f trapezoid(float delta, Vec3f next) {
19 | Vec3f result = last.mean(next).multiply(delta);
20 | last = next;
21 | return result;
22 | }
23 |
24 | /**
25 | * Resets the last value
26 | */
27 | public void reset() {
28 | last = new Vec3f();
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/subdialog_mouse_usage_finished.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/ch/virt/smartphonemouse/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 | assertEquals("ch.virt.smartphonemouse", appContext.getPackageName());
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/CalibrateBeginSubdialog.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.settings.dialog;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 | import androidx.annotation.NonNull;
6 | import androidx.annotation.Nullable;
7 | import androidx.fragment.app.Fragment;
8 | import ch.virt.smartphonemouse.R;
9 |
10 | /**
11 | * This class is a sub fragment for the calibrate dialog, that is shown when the calibration process has finished.
12 | */
13 | public class CalibrateBeginSubdialog extends Fragment {
14 |
15 |
16 | /**
17 | * Creates the sub dialog.
18 | */
19 | public CalibrateBeginSubdialog() {
20 | super(R.layout.subdialog_calibrate_begin);
21 | }
22 |
23 | @Override
24 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
25 | super.onViewCreated(view, savedInstanceState);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/CalibrateFinishedSubdialog.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.settings.dialog;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 | import androidx.annotation.NonNull;
6 | import androidx.annotation.Nullable;
7 | import androidx.fragment.app.Fragment;
8 | import ch.virt.smartphonemouse.R;
9 |
10 | /**
11 | * This class is a sub fragment for the calibrate dialog, that is shown when the calibration process has finished.
12 | */
13 | public class CalibrateFinishedSubdialog extends Fragment {
14 |
15 |
16 | /**
17 | * Creates the sub dialog.
18 | */
19 | public CalibrateFinishedSubdialog() {
20 | super(R.layout.subdialog_calibrate_finished);
21 | }
22 |
23 | @Override
24 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
25 | super.onViewCreated(view, savedInstanceState);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/subdialog_add_requestsetting.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/subdialog_add_requestpermission.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/subfragment_home_unsupported.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
16 |
17 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/AboutFragment.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui;
2 |
3 | import android.os.Bundle;
4 | import android.text.method.LinkMovementMethod;
5 | import android.view.View;
6 | import android.widget.TextView;
7 |
8 | import androidx.annotation.NonNull;
9 | import androidx.annotation.Nullable;
10 | import androidx.fragment.app.Fragment;
11 |
12 | import ch.virt.smartphonemouse.R;
13 |
14 | /**
15 | * This fragment shows information about the app in general.
16 | */
17 | public class AboutFragment extends Fragment {
18 |
19 | private TextView aboutGithub;
20 |
21 | /**
22 | * Creates the fragment.
23 | */
24 | public AboutFragment() {
25 | super(R.layout.fragment_about);
26 | }
27 |
28 | @Override
29 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
30 | super.onViewCreated(view, savedInstanceState);
31 |
32 | aboutGithub = view.findViewById(R.id.about_github);
33 | aboutGithub.setMovementMethod(LinkMovementMethod.getInstance());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/subfragment_home_disabled.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
16 |
17 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/subfragment_connect_select.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
18 |
19 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/brand/icon.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | android.nonTransitiveRClass=false
19 | android.nonFinalResIds=false
--------------------------------------------------------------------------------
/app/src/main/res/layout/navigation_drawer_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
14 |
15 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/subdialog_add_select.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
13 |
14 |
19 |
20 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #219954
5 | #0f763e
6 | #151B3F
7 | #2A377E
8 | #6474CA
9 |
10 |
11 | #FF000000
12 | #FFFFFFFF
13 |
14 |
15 | #2196F3
16 | #4CAF50
17 | #FF9800
18 | #F44336
19 | #707070
20 |
21 |
22 | #868686
23 | #242424
24 | #6A666666
25 | #6A303030
26 | #000000
27 | #B1B1B1
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/mouse/MouseMessageSubdialog.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.mouse;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 | import android.widget.TextView;
6 |
7 | import androidx.annotation.NonNull;
8 | import androidx.annotation.Nullable;
9 | import androidx.fragment.app.Fragment;
10 |
11 | import ch.virt.smartphonemouse.R;
12 |
13 | /**
14 | * This sub dialog shows some basic information as text to the user.
15 | */
16 | public class MouseMessageSubdialog extends Fragment {
17 |
18 | private String message;
19 | private TextView messageView;
20 |
21 | /**
22 | * Creates the message sub dialog.
23 | *
24 | * @param message message that gets shown
25 | */
26 | public MouseMessageSubdialog(String message) {
27 | super(R.layout.subdialog_mouse_message);
28 |
29 | this.message = message;
30 | }
31 |
32 | @Override
33 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
34 | super.onViewCreated(view, savedInstanceState);
35 |
36 | messageView = view.findViewById(R.id.mouse_message_message);
37 |
38 | messageView.setText(message);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_add_select.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
20 |
21 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/home/HomeUnsupportedSubfragment.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.home;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 | import android.widget.Button;
6 | import android.widget.Toast;
7 |
8 | import androidx.annotation.NonNull;
9 | import androidx.annotation.Nullable;
10 | import androidx.fragment.app.Fragment;
11 |
12 | import ch.virt.smartphonemouse.R;
13 |
14 | /**
15 | * This sub fragment of the home page is shown when the smartphone does not support the bluetooth id profile.
16 | */
17 | public class HomeUnsupportedSubfragment extends Fragment {
18 |
19 | private Button playstoreLink;
20 |
21 | /**
22 | * Creates this sub fragment.
23 | */
24 | public HomeUnsupportedSubfragment() {
25 | super(R.layout.subfragment_home_unsupported);
26 | }
27 |
28 | @Override
29 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
30 | super.onViewCreated(view, savedInstanceState);
31 |
32 | playstoreLink = view.findViewById(R.id.home_unsupported_playstore);
33 | playstoreLink.setOnClickListener(v -> Toast.makeText(view.getContext(), "Currently not published", Toast.LENGTH_SHORT).show());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/drawer_settings.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/connect/dialog/AddRequestPermissionSubdialog.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.connect.dialog;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 | import android.widget.TextView;
6 |
7 | import androidx.annotation.NonNull;
8 | import androidx.annotation.Nullable;
9 | import androidx.fragment.app.Fragment;
10 |
11 | import ch.virt.smartphonemouse.R;
12 |
13 | /**
14 | * This class holds a sub page for the add dialog when the user must grant the app permissions to discover new devices.
15 | */
16 | public class AddRequestPermissionSubdialog extends Fragment {
17 |
18 | private TextView error;
19 |
20 | /**
21 | * Creates the sub dialog.
22 | */
23 | public AddRequestPermissionSubdialog() {
24 | super(R.layout.subdialog_add_requestpermission);
25 | }
26 |
27 | /**
28 | * Displays the error on the screen.
29 | */
30 | public void showError() {
31 | error.post(() -> error.setVisibility(View.VISIBLE));
32 | }
33 |
34 | @Override
35 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
36 | super.onViewCreated(view, savedInstanceState);
37 |
38 | error = view.findViewById(R.id.add_request_permission_error);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/connect/dialog/AddRequestSettingSubdialog.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.connect.dialog;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 | import android.widget.TextView;
6 |
7 | import androidx.annotation.NonNull;
8 | import androidx.annotation.Nullable;
9 | import androidx.fragment.app.Fragment;
10 |
11 | import ch.virt.smartphonemouse.R;
12 |
13 | /**
14 | * This class holds the sub page of the add dialog that is displayed when the user should enable location in order to be able to discover near devices.
15 | */
16 | public class AddRequestSettingSubdialog extends Fragment {
17 |
18 | private TextView error;
19 |
20 | /**
21 | * Creates the sub dialog.
22 | */
23 | public AddRequestSettingSubdialog() {
24 | super(R.layout.subdialog_add_requestsetting);
25 | }
26 |
27 | /**
28 | * Shows the error on the screen.
29 | */
30 | public void showError(){
31 | error.post(() -> error.setVisibility(View.VISIBLE));
32 | }
33 |
34 | @Override
35 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
36 | super.onViewCreated(view, savedInstanceState);
37 |
38 | error = view.findViewById(R.id.add_request_setting_error);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/mouse/components/WindowAverage.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.mouse.components;
2 |
3 | /**
4 | * This class represents an average which operates in a window, remembering a given value of past samples to do the averaging with.
5 | */
6 | public class WindowAverage {
7 |
8 | private int index;
9 | private float[] elements;
10 |
11 | /**
12 | * @param length length of the window in values
13 | */
14 | public WindowAverage(int length) {
15 | this.elements = new float[length];
16 | index = 0;
17 | }
18 |
19 | /**
20 | * Takes the next sample and calculates the current average
21 | * @param next next sample
22 | * @return current average, including next sample
23 | */
24 | public float avg(float next) {
25 | elements[index % elements.length] = next;
26 | index++;
27 |
28 | float total = 0f;
29 | int amount = Math.min(elements.length, index);
30 | for (int i = 0; i < amount; i++) {
31 | total += elements[i];
32 | }
33 |
34 | return total / amount;
35 | }
36 |
37 | /**
38 | * Resets the average
39 | */
40 | public void reset() {
41 | elements = new float[elements.length];
42 | index = 0;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/settings/SettingsDebuggingSubfragment.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.settings;
2 |
3 | import android.content.Intent;
4 | import android.net.Uri;
5 | import android.os.Bundle;
6 | import androidx.preference.Preference;
7 | import androidx.preference.PreferenceFragmentCompat;
8 | import ch.virt.smartphonemouse.R;
9 | import ch.virt.smartphonemouse.ui.settings.custom.EditFloatPreference;
10 | import ch.virt.smartphonemouse.ui.settings.custom.SeekFloatPreference;
11 | import ch.virt.smartphonemouse.ui.settings.dialog.CalibrateDialog;
12 |
13 | /**
14 | * This fragment is the settings page, where the user can configure everything regarding the movement.
15 | */
16 | public class SettingsDebuggingSubfragment extends PreferenceFragmentCompat {
17 |
18 | @Override
19 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
20 | setPreferencesFromResource(R.xml.settings_debugging, null);
21 |
22 | findPreference("debugDownload").setOnPreferenceClickListener(preference -> {
23 |
24 | Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(getContext().getText(R.string.settings_debug_server_download_url).toString()));
25 | startActivity(browserIntent);
26 |
27 | return false;
28 | });
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/settings_debugging.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
9 |
10 |
14 |
15 |
16 |
20 |
21 |
24 |
25 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/subdialog_add_bonded.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
20 |
21 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/navigation_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/home/HomeDisabledSubfragment.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.home;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 | import android.widget.Button;
6 |
7 | import androidx.annotation.NonNull;
8 | import androidx.annotation.Nullable;
9 | import androidx.fragment.app.Fragment;
10 |
11 | import ch.virt.smartphonemouse.R;
12 | import ch.virt.smartphonemouse.transmission.BluetoothHandler;
13 |
14 | /**
15 | * This sub fragment on the home page gets shown when bluetooth happens to be disabled.
16 | */
17 | public class HomeDisabledSubfragment extends Fragment {
18 |
19 | private final BluetoothHandler handler;
20 |
21 | private Button refresh;
22 |
23 | /**
24 | * Creates the sub fragment.
25 | *
26 | * @param handler bluetooth handler to use to refresh
27 | */
28 | public HomeDisabledSubfragment(BluetoothHandler handler) {
29 | super(R.layout.subfragment_home_disabled);
30 | this.handler = handler;
31 | }
32 |
33 | @Override
34 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
35 | super.onViewCreated(view, savedInstanceState);
36 |
37 | refresh = view.findViewById(R.id.home_disabled_recheck);
38 |
39 | refresh.setOnClickListener(v -> handler.reInit());
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/connect/dialog/AddSuccessSubdialog.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.connect.dialog;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 | import android.widget.TextView;
6 |
7 | import androidx.annotation.NonNull;
8 | import androidx.annotation.Nullable;
9 | import androidx.fragment.app.Fragment;
10 |
11 | import ch.virt.smartphonemouse.R;
12 | import ch.virt.smartphonemouse.transmission.BluetoothDiscoverer;
13 |
14 | /**
15 | * This class holds the sub page for the add dialog that informs the user that they have successfully added a device.
16 | */
17 | public class AddSuccessSubdialog extends Fragment {
18 |
19 | private TextView name;
20 |
21 | private final BluetoothDiscoverer.DiscoveredDevice target;
22 |
23 | /**
24 | * Creates the sub dialog.
25 | *
26 | * @param target device which was added, used to display its name
27 | */
28 | public AddSuccessSubdialog(BluetoothDiscoverer.DiscoveredDevice target) {
29 | super(R.layout.subdialog_add_success);
30 |
31 | this.target = target;
32 | }
33 |
34 | @Override
35 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
36 | super.onViewCreated(view, savedInstanceState);
37 |
38 | name = view.findViewById(R.id.add_success_name);
39 | name.setText(target.getName());
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/subdialog_calibrate_happening.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
19 |
20 |
26 |
27 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/mouse/components/WindowAverage3f.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.mouse.components;
2 |
3 | import ch.virt.smartphonemouse.mouse.math.Vec3f;
4 |
5 | /**
6 | * This class represents an average which operates in a window, remembering a given value of past samples to do the averaging with.
7 | * Here with a Vec3f as a value.
8 | */
9 | public class WindowAverage3f {
10 |
11 | private int index;
12 | private Vec3f[] elements;
13 |
14 | /**
15 | * @param length length of the window in values
16 | */
17 | public WindowAverage3f(int length) {
18 | this.elements = new Vec3f[length];
19 | index = 0;
20 | }
21 |
22 | /**
23 | * Takes the next sample and calculates the current average
24 | * @param next next sample
25 | * @return current average, including next sample
26 | */
27 | public Vec3f avg(Vec3f next) {
28 |
29 | elements[index % elements.length] = next;
30 | index++;
31 |
32 |
33 | Vec3f total = new Vec3f();
34 | int amount = Math.min(elements.length, index);
35 | for (int i = 0; i < amount; i++) {
36 | total.add(elements[i]);
37 | }
38 |
39 | return total.divide(amount);
40 | }
41 |
42 | /**
43 | * Resets the average
44 | */
45 | public void reset() {
46 | elements = new Vec3f[elements.length];
47 | index = 0;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/mouse/MouseUsageFinishedSubdialog.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.mouse;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 | import android.widget.CheckBox;
6 |
7 | import androidx.annotation.NonNull;
8 | import androidx.annotation.Nullable;
9 | import androidx.fragment.app.Fragment;
10 | import androidx.preference.PreferenceManager;
11 |
12 | import ch.virt.smartphonemouse.R;
13 |
14 | /**
15 | * This sub dialog is the last page of the usage dialog. It gives the user the option to disable every further usage dialog.
16 | */
17 | public class MouseUsageFinishedSubdialog extends Fragment {
18 |
19 | private CheckBox notAgain;
20 |
21 | /**
22 | * Creates the sub dialog.
23 | */
24 | public MouseUsageFinishedSubdialog() {
25 | super(R.layout.subdialog_mouse_usage_finished);
26 | }
27 |
28 | @Override
29 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
30 | super.onViewCreated(view, savedInstanceState);
31 |
32 | notAgain = view.findViewById(R.id.mouse_usage_finished_notagain);
33 |
34 |
35 | notAgain.setChecked(!PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("showUsage", true));
36 |
37 | notAgain.setOnCheckedChangeListener((buttonView, isChecked) -> PreferenceManager.getDefaultSharedPreferences(getContext()).edit().putBoolean("showUsage", !isChecked).apply());
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/subfragment_connect_connecting.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
20 |
21 |
26 |
27 |
28 |
29 |
30 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/subdialog_add_manual.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
19 |
20 |
21 |
22 |
27 |
28 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/mouse/components/OrThreshold.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.mouse.components;
2 |
3 | /**
4 | * This class represents a threshold which operates based on two values.
5 | * It uses the values in an OR fashion, activating when one or both values surpass their threshold.
6 | * It also takes a time to drop off, after the last active value pair.
7 | */
8 | public class OrThreshold {
9 |
10 | private final int dropoff;
11 | private final float firstThreshold, secondThreshold;
12 |
13 | private int lastActive;
14 |
15 | /**
16 | * @param dropoff amount of samples it takes from the last active sample to be inactive
17 | * @param firstThreshold threshold the first value has to surpass
18 | * @param secondThreshold threshold the second value has to surpass
19 | */
20 | public OrThreshold(int dropoff, float firstThreshold, float secondThreshold) {
21 | this.dropoff = dropoff;
22 | this.firstThreshold = firstThreshold;
23 | this.secondThreshold = secondThreshold;
24 |
25 | this.lastActive = dropoff;
26 | }
27 |
28 | /**
29 | * Takes two new values and determines whether the threshold is activated
30 | * @param first first value
31 | * @param second second value
32 | * @return whether the threshold is active
33 | */
34 | public boolean active(float first, float second) {
35 | if (first > firstThreshold || second > secondThreshold) lastActive = 0;
36 | else lastActive++;
37 |
38 | return lastActive <= dropoff;
39 | }
40 |
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/transmission/HostDevice.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.transmission;
2 |
3 | /**
4 | * This class represents a known host device.
5 | */
6 | public class HostDevice {
7 |
8 | private String address;
9 | private String name;
10 | private long lastConnected;
11 |
12 | /**
13 | * Creates a host device.
14 | *
15 | * @param address bluetooth mac address of the device
16 | * @param name name of the device
17 | */
18 | public HostDevice(String address, String name) {
19 | this.address = address;
20 | this.name = name;
21 | this.lastConnected = -1;
22 | }
23 |
24 | /**
25 | * Sets when the device has last connected.
26 | *
27 | * @param lastConnected unix timestamp when last connected
28 | */
29 | public void setLastConnected(long lastConnected) {
30 | this.lastConnected = lastConnected;
31 | }
32 |
33 | /**
34 | * Returns the bluetooth mac address of the device.
35 | *
36 | * @return bluetooth mac address
37 | */
38 | public String getAddress() {
39 | return address;
40 | }
41 |
42 | /**
43 | * Returns the name of the device.
44 | *
45 | * @return name
46 | */
47 | public String getName() {
48 | return name;
49 | }
50 |
51 | /**
52 | * Returns when the device was last connected to.
53 | *
54 | * @return unix timestamp of when it was last connected
55 | */
56 | public long getLastConnected() {
57 | return lastConnected;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
13 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/subfragment_home_connected.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
19 |
20 |
26 |
27 |
31 |
32 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_connect.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
22 |
23 |
30 |
31 |
32 |
33 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
35 |
36 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/subfragment_connect_connected.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
19 |
20 |
25 |
26 |
30 |
31 |
37 |
38 |
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
22 |
23 |
28 |
29 |
36 |
37 |
42 |
43 |
50 |
51 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Build and Sign Release
2 |
3 | permissions:
4 | contents: write
5 |
6 | on:
7 | workflow_dispatch:
8 | release:
9 | types: [published]
10 |
11 | jobs:
12 | build-and-sign:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout Source
16 | uses: actions/checkout@v4
17 | with:
18 | ref: ${{ github.ref_name }}
19 |
20 | - name: Setup JDK
21 | uses: actions/setup-java@v4
22 | with:
23 | # if you change this - remember to announce that the build sdk has changed. see #20
24 | java-version: '21'
25 | distribution: 'temurin'
26 |
27 | - name: Setup Gradle
28 | uses: gradle/actions/setup-gradle@v3
29 |
30 | - name: Create Keystore File
31 | shell: bash
32 | env:
33 | KEYSTORE_DATA: ${{ secrets.KEYSTORE_DATA }}
34 | run: |
35 | echo "$KEYSTORE_DATA" | base64 -d > app/keystore.jks
36 |
37 | - name: Build and Sign Release
38 | env:
39 | KEYSTORE_FILE: keystore.jks
40 | KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
41 | KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
42 | KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
43 | run: ./gradlew assembleRelease
44 |
45 | - name: Store build Artifact
46 | uses: actions/upload-artifact@v4
47 | with:
48 | name: release-${{ github.ref_name }}
49 | path: app/build/outputs/apk/release/app-release.apk
50 |
51 | - name: Rename Build
52 | shell: bash
53 | run: |
54 | mv app/build/outputs/apk/release/app-release.apk SmartMouse-${{ github.ref_name }}.apk
55 |
56 | - name: Upload to Release
57 | env:
58 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
59 | run: |
60 | gh release upload ${{ github.ref_name }} SmartMouse-${{ github.ref_name }}.apk
61 |
62 |
63 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
15 |
16 |
21 |
22 |
30 |
31 |
35 |
36 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
19 |
20 |
26 |
27 |
28 |
29 |
33 |
34 |
35 |
36 |
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/home/HomeConnectedSubfragment.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.home;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 | import android.widget.Button;
6 | import android.widget.Chronometer;
7 | import android.widget.TextView;
8 |
9 | import androidx.annotation.NonNull;
10 | import androidx.annotation.Nullable;
11 | import androidx.fragment.app.Fragment;
12 |
13 | import ch.virt.smartphonemouse.MainActivity;
14 | import ch.virt.smartphonemouse.R;
15 | import ch.virt.smartphonemouse.transmission.BluetoothHandler;
16 |
17 | /**
18 | * This sub fragment gets shown on the home screen when a device is currently connected.
19 | */
20 | public class HomeConnectedSubfragment extends Fragment {
21 |
22 | private final BluetoothHandler handler;
23 |
24 | private Chronometer chronometer;
25 | private TextView device;
26 | private Button more;
27 |
28 | /**
29 | * Creates the sub fragment.
30 | *
31 | * @param handler bluetooth handler to get connected information from
32 | */
33 | public HomeConnectedSubfragment(BluetoothHandler handler) {
34 | super(R.layout.subfragment_home_connected);
35 | this.handler = handler;
36 | }
37 |
38 | @Override
39 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
40 | super.onViewCreated(view, savedInstanceState);
41 |
42 | chronometer = view.findViewById(R.id.home_connected_device_elapsed);
43 | device = view.findViewById(R.id.home_connected_device_name);
44 | more = view.findViewById(R.id.home_connected_more);
45 |
46 |
47 | chronometer.setBase(handler.getHost().getConnectedSince());
48 | chronometer.setFormat(getResources().getString(R.string.home_connected_elapsed));
49 | chronometer.start();
50 |
51 | device.setText(handler.getHost().getDevice().getName());
52 |
53 | more.setOnClickListener(v -> ((MainActivity) getActivity()).navigate(R.id.drawer_connect));
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/connect/dialog/AddBondedSubdialog.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.connect.dialog;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 | import android.widget.TextView;
6 |
7 | import androidx.annotation.NonNull;
8 | import androidx.annotation.Nullable;
9 | import androidx.fragment.app.Fragment;
10 |
11 | import ch.virt.smartphonemouse.R;
12 | import ch.virt.smartphonemouse.transmission.BluetoothDiscoverer;
13 | import ch.virt.smartphonemouse.transmission.BluetoothHandler;
14 |
15 | /**
16 | * This class contains the sub page of the add dialog, which is displayed when a device is bonded with the smartphone.
17 | */
18 | public class AddBondedSubdialog extends Fragment {
19 |
20 | private BluetoothHandler handler;
21 | private TextView error;
22 |
23 | private BluetoothDiscoverer.DiscoveredDevice target;
24 |
25 | /**
26 | * Creates the sub dialog.
27 | *
28 | * @param handler handler to check whether still bonded
29 | * @param target target device to check for
30 | */
31 | public AddBondedSubdialog(BluetoothHandler handler, BluetoothDiscoverer.DiscoveredDevice target) {
32 | super(R.layout.subdialog_add_bonded);
33 |
34 | this.handler = handler;
35 | this.target = target;
36 | }
37 |
38 | /**
39 | * Checks if the device is still bonded.
40 | * If it is still bonded, an error message will be displayed.
41 | *
42 | * @return whether it is NOT bonded
43 | */
44 | public boolean check() {
45 | if (handler.isBonded(target.getAddress())) {
46 | error.setVisibility(View.VISIBLE);
47 | return false;
48 | } else {
49 | error.setVisibility(View.GONE);
50 | return true;
51 | }
52 | }
53 |
54 | @Override
55 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
56 | super.onViewCreated(view, savedInstanceState);
57 |
58 | error = view.findViewById(R.id.add_bonded_error);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
23 |
24 |
31 |
32 |
40 |
41 |
48 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | }
4 |
5 | android {
6 | namespace 'ch.virt.smartphonemouse'
7 |
8 | defaultConfig {
9 | applicationId "ch.virt.smartphonemouse"
10 |
11 | // TODO: Upgrade targetSdk version to 32 (turns out to be a ton of work)
12 | //noinspection ExpiredTargetSdkVersion
13 | targetSdk 29
14 | compileSdk 30
15 | minSdk 28
16 |
17 | versionCode 6
18 | versionName "1.4.2"
19 |
20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
21 | }
22 |
23 | signingConfigs {
24 | release {
25 | storeFile file(System.getenv("KEYSTORE_FILE") ?: "keystore.jks")
26 | storePassword System.getenv("KEYSTORE_PASSWORD")
27 | keyAlias System.getenv("KEY_ALIAS")
28 | keyPassword System.getenv("KEY_PASSWORD")
29 | }
30 | }
31 |
32 | buildTypes {
33 | release {
34 | signingConfig signingConfigs.release
35 | minifyEnabled false
36 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
37 | }
38 | }
39 |
40 | dependenciesInfo {
41 | // not useful when not distributing via google play
42 | includeInApk = false
43 | includeInBundle = false
44 | }
45 | }
46 |
47 | repositories {
48 | maven { url 'https://jitpack.io' }
49 | }
50 |
51 | dependencies {
52 |
53 | // Json Serialization
54 | implementation 'com.google.code.gson:gson:2.8.7'
55 |
56 | // Android Libraries
57 | implementation 'androidx.appcompat:appcompat:1.3.0'
58 | implementation "androidx.navigation:navigation-ui:2.3.5"
59 | implementation "androidx.preference:preference:1.1.1"
60 |
61 | // Material Design
62 | implementation 'com.google.android.material:material:1.3.0'
63 |
64 | // Testing
65 | testImplementation 'junit:junit:4.13.2'
66 | androidTestImplementation 'androidx.test.ext:junit:1.1.2'
67 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
68 |
69 | }
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/CalibrationHappeningSubdialog.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.settings.dialog;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 | import android.widget.TextView;
6 | import androidx.annotation.NonNull;
7 | import androidx.annotation.Nullable;
8 | import androidx.fragment.app.Fragment;
9 | import ch.virt.smartphonemouse.R;
10 | import ch.virt.smartphonemouse.customization.CalibrationHandler;
11 | import ch.virt.smartphonemouse.mouse.Calibration;
12 |
13 | /**
14 | * This fragment for a dialog is used to handle the calibration of the sampling rate.
15 | */
16 | public class CalibrationHappeningSubdialog extends Fragment {
17 |
18 | private TextView time;
19 |
20 | private CalibrationHandler calibrator;
21 | private final DoneListener doneListener;
22 |
23 | /**
24 | * Creates the sub dialog fragment.
25 | *
26 | * @param doneListener listener that gets called when the calibration process is finished.
27 | */
28 | public CalibrationHappeningSubdialog(DoneListener doneListener) {
29 | super(R.layout.subdialog_calibrate_happening);
30 |
31 | this.doneListener = doneListener;
32 | }
33 |
34 | @Override
35 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
36 | super.onViewCreated(view, savedInstanceState);
37 |
38 | calibrator = new CalibrationHandler(getContext());
39 |
40 | time = view.findViewById(R.id.calibrate_samplingrate_time);
41 | time.setText(getResources().getString(R.string.dialog_calibrate_happening_init));
42 |
43 | calibrator.calibrate(state -> time.post(() -> {
44 | if (state == Calibration.STATE_SAMPLING) time.setText(getResources().getString(R.string.dialog_calibrate_happening_sampling));
45 | else if (state == Calibration.STATE_NOISE) time.setText(getResources().getString(R.string.dialog_calibrate_happening_noise));
46 | else if (state == Calibration.STATE_END) doneListener.done();
47 | }));
48 | }
49 |
50 | public interface DoneListener {
51 | void done();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/mouse/math/Vec2f.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.mouse.math;
2 |
3 | /**
4 | * This class represents a vector consisting of two floats.
5 | */
6 | public class Vec2f {
7 | public float x, y;
8 |
9 | public Vec2f() {}
10 | public Vec2f(float x, float y) {
11 | this.x = x;
12 | this.y = y;
13 | }
14 |
15 | /**
16 | * Creates a copy of the vector
17 | */
18 | public Vec2f copy() {
19 | return new Vec2f(x, y);
20 | }
21 |
22 | /**
23 | * Returns the absolute value, aka. length of the vector
24 | */
25 | public float abs() {
26 | return (float) Math.sqrt(x*x + y*y);
27 | }
28 |
29 | /**
30 | * Adds another vector to this one
31 | */
32 | public Vec2f add(Vec2f other) {
33 | x += other.x;
34 | y += other.y;
35 |
36 | return this;
37 | }
38 |
39 | /**
40 | * Adds another vector to this one
41 | */
42 | public Vec2f subtract(Vec2f other) {
43 | x -= other.x;
44 | y -= other.y;
45 |
46 | return this;
47 | }
48 |
49 | /**
50 | * Multiplies or scales this vector by an amount
51 | */
52 | public Vec2f multiply(float factor) {
53 | x *= factor;
54 | y *= factor;
55 |
56 | return this;
57 | }
58 |
59 | /**
60 | * Divides this vector by a given amount
61 | */
62 | public Vec2f divide(float divisor) {
63 | x /= divisor;
64 | y /= divisor;
65 |
66 | return this;
67 | }
68 |
69 | /**
70 | * Calculates the average between this and a given other vector. Not affecting this instance.
71 | */
72 | public Vec2f mean(Vec2f second) {
73 | return this.copy().add(second).divide(2);
74 | }
75 |
76 | /**
77 | * Rotates this vector by a given rotation
78 | * @param rotation rotation in radians
79 | */
80 | public Vec2f rotate(float rotation) {
81 | float c = (float) Math.cos(rotation);
82 | float s = (float) Math.sin(rotation);
83 |
84 | float newX = c * x + (-s) * y;
85 | float newY = s * x + c * y;
86 |
87 | this.x = newX;
88 | this.y = newY;
89 |
90 | return this;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_connect_select.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
19 |
20 |
27 |
28 |
34 |
35 |
42 |
43 |
44 |
45 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/connect/ConnectConnectedSubfragment.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.connect;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 | import android.widget.Button;
6 | import android.widget.Chronometer;
7 | import android.widget.TextView;
8 |
9 | import androidx.annotation.NonNull;
10 | import androidx.annotation.Nullable;
11 | import androidx.fragment.app.Fragment;
12 |
13 | import ch.virt.smartphonemouse.MainActivity;
14 | import ch.virt.smartphonemouse.R;
15 | import ch.virt.smartphonemouse.transmission.BluetoothHandler;
16 |
17 | /**
18 | * This class is a sub fragment for the connect page.
19 | * This fragment is displayed when the app has a successful connection.
20 | */
21 | public class ConnectConnectedSubfragment extends Fragment {
22 |
23 | private Chronometer elapsed;
24 | private TextView name;
25 |
26 | private Button disconnect;
27 | private Button mouse;
28 |
29 | private final BluetoothHandler bluetooth;
30 |
31 | /**
32 | * Creates this sub fragment.
33 | *
34 | * @param bluetooth bluetooth handler to read status from
35 | */
36 | public ConnectConnectedSubfragment(BluetoothHandler bluetooth) {
37 | super(R.layout.subfragment_connect_connected);
38 | this.bluetooth = bluetooth;
39 | }
40 |
41 | @Override
42 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
43 | super.onViewCreated(view, savedInstanceState);
44 |
45 | elapsed = view.findViewById(R.id.connect_connected_device_elapsed);
46 | name = view.findViewById(R.id.connect_connected_device_name);
47 | disconnect = view.findViewById(R.id.connect_connected_disconnect);
48 | mouse = view.findViewById(R.id.connect_connected_mouse);
49 |
50 | if (bluetooth.isConnected()) {
51 |
52 | name.setText(bluetooth.getHost().getDevice().getName());
53 |
54 | elapsed.setBase(bluetooth.getHost().getConnectedSince());
55 | elapsed.setFormat(getResources().getString(R.string.connect_connected_elapsed));
56 | elapsed.start();
57 |
58 | disconnect.setOnClickListener(v -> bluetooth.getHost().disconnect());
59 | mouse.setOnClickListener(v -> ((MainActivity) getActivity()).navigate(R.id.drawer_mouse));
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/settings/SettingsCommunicationSubfragment.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.settings;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.SharedPreferences;
6 | import android.os.Bundle;
7 |
8 | import androidx.preference.Preference;
9 | import androidx.preference.PreferenceFragmentCompat;
10 | import androidx.preference.PreferenceManager;
11 |
12 | import com.google.android.material.snackbar.Snackbar;
13 |
14 | import ch.virt.smartphonemouse.R;
15 | import ch.virt.smartphonemouse.transmission.DeviceStorage;
16 |
17 | /**
18 | * This fragment shows the settings that are used to configure the settings regarding communication.
19 | */
20 | public class SettingsCommunicationSubfragment extends PreferenceFragmentCompat {
21 |
22 | @Override
23 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
24 | setPreferencesFromResource(R.xml.settings_communication, null);
25 |
26 | Preference communicationRemoveDevices = findPreference("communicationRemoveDevices");
27 |
28 | communicationRemoveDevices.setOnPreferenceClickListener(preference -> {
29 | removeDevices();
30 | return true;
31 | });
32 | }
33 |
34 | /**
35 | * Shows the dialog where the user can choose to remove all known devices.
36 | */
37 | private void removeDevices() {
38 |
39 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
40 | builder.setMessage(R.string.settings_communication_removeall_dialog_message)
41 | .setPositiveButton(R.string.settings_communication_removeall_dialog_remove, (dialog, id) -> {
42 |
43 | SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(getContext()).edit();
44 |
45 | editor.putString(DeviceStorage.DEVICES_KEY, "[]"); // Reset to an empty json array
46 |
47 | editor.apply();
48 |
49 | Snackbar.make(getView(), getResources().getString(R.string.settings_communication_removeall_confirmation), Snackbar.LENGTH_SHORT).show();
50 |
51 | })
52 | .setNegativeButton(R.string.settings_communication_removeall_dialog_cancel, (dialog, id) -> { });
53 |
54 | Dialog dialog = builder.create();
55 | dialog.show();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/connect/ConnectFailedSubfragment.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.connect;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 | import android.widget.Button;
6 | import android.widget.TextView;
7 |
8 | import androidx.annotation.NonNull;
9 | import androidx.annotation.Nullable;
10 | import androidx.fragment.app.Fragment;
11 |
12 | import ch.virt.smartphonemouse.R;
13 | import ch.virt.smartphonemouse.transmission.BluetoothHandler;
14 |
15 | /**
16 | * This is a sub fragment for the connect page.
17 | * This fragment is shown when a recent connection failed.
18 | */
19 | public class ConnectFailedSubfragment extends Fragment {
20 |
21 | private final BluetoothHandler bluetooth;
22 |
23 | private TextView device;
24 | private Button back;
25 | private Button remove;
26 |
27 | private ReturnListener listener;
28 |
29 | /**
30 | * Creates the sub fragment.
31 | * @param bluetooth bluetooth handler to query information from
32 | */
33 | public ConnectFailedSubfragment(BluetoothHandler bluetooth) {
34 | super(R.layout.subfragment_connect_failed);
35 | this.bluetooth = bluetooth;
36 | }
37 |
38 | @Override
39 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
40 | super.onViewCreated(view, savedInstanceState);
41 |
42 | device = view.findViewById(R.id.connect_failed_device);
43 |
44 | back = view.findViewById(R.id.connect_failed_back);
45 | remove = view.findViewById(R.id.connect_failed_remove);
46 |
47 |
48 | device.setText(bluetooth.getHost().getDevice().getName());
49 |
50 | remove.setOnClickListener(v -> {
51 | bluetooth.getDevices().removeDevice(bluetooth.getHost().getDevice().getAddress());
52 | bluetooth.getHost().markFailedAsRead();
53 | listener.returned();
54 | });
55 | back.setOnClickListener(v -> {
56 | bluetooth.getHost().markFailedAsRead();
57 | listener.returned();
58 | });
59 | }
60 |
61 | /**
62 | * Sets the return listener that is called when the user wants to return from this fragment
63 | * @param listener return listener
64 | */
65 | public void setReturnListener(ReturnListener listener) {
66 | this.listener = listener;
67 | }
68 |
69 | /**
70 | * This interface is a basic listener that is called when the user returns from the failure message.
71 | */
72 | public interface ReturnListener {
73 |
74 | /**
75 | * Called when the user returns.
76 | */
77 | void returned();
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/SettingsFragment.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.os.Bundle;
6 |
7 | import androidx.preference.Preference;
8 | import androidx.preference.PreferenceFragmentCompat;
9 | import androidx.preference.PreferenceManager;
10 |
11 | import androidx.preference.SwitchPreference;
12 | import com.google.android.material.snackbar.Snackbar;
13 |
14 | import ch.virt.smartphonemouse.R;
15 | import ch.virt.smartphonemouse.customization.DefaultSettings;
16 |
17 | /**
18 | * This fragment contains the settings for the app.
19 | */
20 | public class SettingsFragment extends PreferenceFragmentCompat {
21 |
22 | @Override
23 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
24 | setPreferencesFromResource(R.xml.settings, null);
25 |
26 | Preference reset = findPreference("reset");
27 | reset.setOnPreferenceClickListener(preference -> {
28 | resetSettings();
29 | return true;
30 | });
31 |
32 | checkAdvanced(PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("advanced", false));
33 | findPreference("advanced").setOnPreferenceChangeListener((preference, newValue) -> {
34 | checkAdvanced((Boolean) newValue);
35 | return true;
36 | });
37 | }
38 |
39 | public void checkAdvanced(boolean advanced) {
40 | findPreference("reset").setVisible(advanced);
41 | findPreference("debugging").setVisible(advanced);
42 | }
43 |
44 | /**
45 | * Shows the settings reset dialog, where the user can choose to restore their settings.
46 | */
47 | private void resetSettings() {
48 |
49 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
50 | builder.setMessage(R.string.settings_reset_dialog_message)
51 | .setPositiveButton(R.string.settings_reset_dialog_reset, (dialog, id) -> {
52 |
53 | DefaultSettings.set(PreferenceManager.getDefaultSharedPreferences(getContext()));
54 |
55 | Snackbar.make(getView(), getResources().getString(R.string.settings_reset_confirmation), Snackbar.LENGTH_SHORT).show();
56 |
57 | boolean advanced = PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("advanced", false);
58 | checkAdvanced(advanced);
59 | ((SwitchPreference)findPreference("advanced")).setChecked(advanced);
60 | })
61 | .setNegativeButton(R.string.settings_reset_dialog_cancel, (dialog, id) -> { });
62 |
63 | Dialog dialog = builder.create();
64 | dialog.show();
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_about.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
18 |
19 |
23 |
24 |
28 |
29 |
30 |
31 |
35 |
36 |
40 |
41 |
45 |
46 |
47 |
48 |
53 |
54 |
60 |
61 |
68 |
69 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/connect/dialog/AddManualSubdialog.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.connect.dialog;
2 |
3 | import android.bluetooth.BluetoothClass;
4 | import android.os.Bundle;
5 | import android.view.View;
6 |
7 | import androidx.annotation.NonNull;
8 | import androidx.annotation.Nullable;
9 | import androidx.fragment.app.Fragment;
10 |
11 | import com.google.android.material.textfield.TextInputLayout;
12 |
13 | import ch.virt.smartphonemouse.R;
14 | import ch.virt.smartphonemouse.transmission.BluetoothDiscoverer;
15 |
16 | /**
17 | * This class contains the sub page of the add dialog that lets the user add their own custom device by their bluetooth mac address.
18 | */
19 | public class AddManualSubdialog extends Fragment {
20 |
21 | TextInputLayout nameLayout, macLayout;
22 |
23 | /**
24 | * Creates the sub dialog.
25 | */
26 | public AddManualSubdialog() {
27 | super(R.layout.subdialog_add_manual);
28 | }
29 |
30 | /**
31 | * Checks whether the current entered name and mac bluetooth address are correct.
32 | *
33 | * @return whether the inputs are correct
34 | */
35 | public boolean check() {
36 | boolean valid = true;
37 |
38 | if (nameLayout.getEditText().getText() == null || nameLayout.getEditText().getText().toString().equals("")) {
39 | nameLayout.setErrorEnabled(true);
40 | nameLayout.setError(getString(R.string.dialog_add_manual_name_error));
41 | valid = false;
42 | } else {
43 | nameLayout.setErrorEnabled(false);
44 | }
45 |
46 | if (macLayout.getEditText().getText() == null || !macLayout.getEditText().getText().toString().matches("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$")) {
47 | macLayout.setErrorEnabled(true);
48 | macLayout.setError(getString(R.string.dialog_add_manual_mac_error));
49 | valid = false;
50 | } else {
51 | macLayout.setErrorEnabled(false);
52 | }
53 |
54 |
55 | return valid;
56 | }
57 |
58 | /**
59 | * Returns a discovered device, created from the entered details.
60 | *
61 | * @return discovered device
62 | */
63 | public BluetoothDiscoverer.DiscoveredDevice createDevice() {
64 | return new BluetoothDiscoverer.DiscoveredDevice(
65 | nameLayout.getEditText().getText().toString(),
66 | macLayout.getEditText().getText().toString().toUpperCase().replace('-', ':'), // format address as expected in BluetoothAdapter#getRemoteDevice(String)
67 | BluetoothClass.Device.Major.UNCATEGORIZED);
68 | }
69 |
70 | @Override
71 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
72 | super.onViewCreated(view, savedInstanceState);
73 |
74 | nameLayout = view.findViewById(R.id.add_manual_name);
75 | macLayout = view.findViewById(R.id.add_manual_mac);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/transmission/hid/HidDescriptor.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.transmission.hid;
2 |
3 | /**
4 | * This class holds the Hid Descriptor used for this app.
5 | */
6 | public class HidDescriptor {
7 |
8 | // Tag IDs
9 | private static final byte TAG_USAGE_PAGE = 0x05;
10 | private static final byte TAG_USAGE = 0x09;
11 | private static final byte TAG_COLLECTION = (byte) 0xA1;
12 | private static final byte TAG_USAGE_MIN = 0x19;
13 | private static final byte TAG_USAGE_MAX = 0x29;
14 | private static final byte TAG_LOGICAL_MIN = 0x15;
15 | private static final byte TAG_LOGICAL_MAX = 0x25;
16 | private static final byte TAG_REPORT_ID = (byte) 0x85;
17 | private static final byte TAG_REPORT_COUNT = (byte) 0x95;
18 | private static final byte TAG_REPORT_SIZE = 0x75;
19 | private static final byte TAG_INPUT = (byte) 0x81;
20 | private static final byte TAG_END_COLLECTION = (byte) 0xC0;
21 |
22 | // Usage Page IDs
23 | private static final byte UP_GENERIC_DESKTOP = 0x01;
24 | private static final byte UP_BUTTON = 0x09;
25 |
26 | // Usage IDs
27 | private static final byte U_MOUSE = 0x02;
28 | private static final byte U_POINTER = 0x01;
29 | private static final byte U_X = 0x30;
30 | private static final byte U_Y = 0x31;
31 | private static final byte U_WHEEL = 0x38;
32 |
33 | // Collection IDs
34 | private static final byte C_PHYSICAL = 0x00;
35 | private static final byte C_APPLICATION = 0x01;
36 |
37 | // Input IDs
38 | private static final byte I_DAT_VAR_ABS = 0x02;
39 | private static final byte I_CON = 0x01;
40 | private static final byte I_DAT_VAR_REL = 0x06;
41 |
42 | // Descriptor
43 | public static final byte[] DESCRIPTOR = new byte[] {
44 | TAG_USAGE_PAGE, UP_GENERIC_DESKTOP,
45 | TAG_USAGE, U_MOUSE,
46 | TAG_COLLECTION, C_APPLICATION,
47 |
48 | TAG_REPORT_ID, 1, // Record Id
49 | TAG_USAGE, U_POINTER,
50 | TAG_COLLECTION, C_PHYSICAL,
51 |
52 | TAG_USAGE_PAGE, UP_BUTTON,
53 | TAG_USAGE_MIN, 1, // First Button 1
54 | TAG_USAGE_MAX, 3, // Last Button 3
55 | TAG_LOGICAL_MIN, 0,
56 | TAG_LOGICAL_MAX, 1,
57 | TAG_REPORT_SIZE, 1,
58 | TAG_REPORT_COUNT, 3,
59 | TAG_INPUT, I_DAT_VAR_ABS,
60 |
61 | TAG_REPORT_SIZE, 5,
62 | TAG_REPORT_COUNT, 1,
63 | TAG_INPUT, I_CON,
64 |
65 | TAG_USAGE_PAGE, UP_GENERIC_DESKTOP,
66 | TAG_USAGE, U_X,
67 | TAG_USAGE, U_Y,
68 | TAG_USAGE, U_WHEEL,
69 | TAG_LOGICAL_MIN, (byte) 0x81, // -127
70 | TAG_LOGICAL_MAX, 0x7F, // 127
71 | TAG_REPORT_SIZE, 8,
72 | TAG_REPORT_COUNT, 3,
73 | TAG_INPUT, I_DAT_VAR_REL,
74 |
75 | TAG_END_COLLECTION,
76 |
77 | TAG_END_COLLECTION
78 | };
79 | }
80 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/subfragment_connect_failed.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
19 |
20 |
25 |
26 |
32 |
33 |
39 |
40 |
46 |
47 |
52 |
53 |
59 |
60 |
66 |
67 |
71 |
72 |
78 |
79 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/settings/SettingsMovementSubfragment.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.settings;
2 |
3 | import android.os.Bundle;
4 | import androidx.preference.Preference;
5 | import androidx.preference.PreferenceFragmentCompat;
6 | import androidx.preference.PreferenceManager;
7 | import ch.virt.smartphonemouse.R;
8 | import ch.virt.smartphonemouse.ui.settings.custom.EditFloatPreference;
9 | import ch.virt.smartphonemouse.ui.settings.custom.SeekFloatPreference;
10 | import ch.virt.smartphonemouse.ui.settings.dialog.CalibrateDialog;
11 |
12 | /**
13 | * This fragment is the settings page, where the user can configure everything regarding the movement.
14 | */
15 | public class SettingsMovementSubfragment extends PreferenceFragmentCompat {
16 |
17 | @Override
18 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
19 | setPreferencesFromResource(R.xml.settings_movement, null);
20 |
21 | SeekFloatPreference movementSensitivity = findPreference("movementSensitivity");
22 | movementSensitivity.setMaximum(20000f);
23 | movementSensitivity.setMinimum(10000f);
24 | movementSensitivity.setSteps(200);
25 | movementSensitivity.update();
26 |
27 | EditFloatPreference rotThreshold = findPreference("movementThresholdRotation");
28 | EditFloatPreference accThreshold = findPreference("movementThresholdAcceleration");
29 | EditFloatPreference samplingRate = findPreference("movementSampling");
30 |
31 | Preference movementSamplingCalibrate = findPreference("movementRecalibrate");
32 | movementSamplingCalibrate.setOnPreferenceClickListener(preference -> {
33 |
34 | CalibrateDialog dialog = new CalibrateDialog();
35 | dialog.setFinishedListener(dialog1 -> {
36 | // Update changed values after calibration
37 | rotThreshold.update();
38 | accThreshold.update();
39 | samplingRate.update();
40 | });
41 |
42 | dialog.show(SettingsMovementSubfragment.this.getParentFragmentManager(), null);
43 |
44 | return true;
45 | });
46 |
47 | checkAdvanced();
48 | }
49 |
50 | public void checkAdvanced() {
51 | boolean advanced = PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("advanced", false);
52 | findPreference("movementEnableGravityRotation").setVisible(advanced);
53 | findPreference("movementCalibrationSampling").setVisible(advanced);
54 | findPreference("movementCalibrationNoise").setVisible(advanced);
55 | findPreference("movementNoiseRatioAcceleration").setVisible(advanced);
56 | findPreference("movementNoiseFactorAcceleration").setVisible(advanced);
57 | findPreference("movementNoiseRatioRotation").setVisible(advanced);
58 | findPreference("movementNoiseFactorRotation").setVisible(advanced);
59 | findPreference("movementThresholdAcceleration").setVisible(advanced);
60 | findPreference("movementThresholdRotation").setVisible(advanced);
61 | findPreference("movementSampling").setVisible(advanced);
62 | findPreference("movementDurationWindowGravity").setVisible(advanced);
63 | findPreference("movementDurationWindowNoise").setVisible(advanced);
64 | findPreference("movementDurationThreshold").setVisible(advanced);
65 | findPreference("movementDurationGravity").setVisible(advanced);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/connect/dialog/InfoDialog.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.connect.dialog;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.os.Bundle;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.widget.TextView;
10 |
11 | import androidx.annotation.NonNull;
12 | import androidx.annotation.Nullable;
13 | import androidx.fragment.app.DialogFragment;
14 |
15 | import java.text.SimpleDateFormat;
16 | import java.util.Date;
17 |
18 | import ch.virt.smartphonemouse.R;
19 | import ch.virt.smartphonemouse.transmission.BluetoothHandler;
20 | import ch.virt.smartphonemouse.transmission.HostDevice;
21 |
22 | /**
23 | * This dialog is shown when the user wants to see more information about a device.
24 | */
25 | public class InfoDialog extends DialogFragment {
26 |
27 | private final BluetoothHandler bluetooth;
28 | private final HostDevice device;
29 |
30 | private DialogInterface.OnDismissListener dismissListener;
31 |
32 | /**
33 | * Creates the info dialog.
34 | *
35 | * @param bluetooth bluetooth handler to remove device from
36 | * @param device device that is viewed
37 | */
38 | public InfoDialog(BluetoothHandler bluetooth, HostDevice device) {
39 | this.bluetooth = bluetooth;
40 | this.device = device;
41 | }
42 |
43 | /**
44 | * Populates the text views on the view with the data of the device.
45 | *
46 | * @param view view to populate
47 | */
48 | private void populate(View view) {
49 | ((TextView) view.findViewById(R.id.info_address)).setText(device.getAddress());
50 | ((TextView) view.findViewById(R.id.info_name)).setText(device.getName());
51 | ((TextView) view.findViewById(R.id.info_last)).setText(device.getLastConnected() == -1 ? view.getResources().getString(R.string.dialog_info_last_never) : new SimpleDateFormat(view.getResources().getString(R.string.dialog_info_last_format)).format(new Date(device.getLastConnected())));
52 | }
53 |
54 | /**
55 | * Sets the dismiss listener.
56 | * The listener should be set before the dialog is shown.
57 | *
58 | * @param dismissListener dismiss listener that will be passed to the dialog
59 | * @see Dialog#setOnDismissListener(DialogInterface.OnDismissListener)
60 | */
61 | public void setOnDismissListener(DialogInterface.OnDismissListener dismissListener) {
62 | this.dismissListener = dismissListener;
63 | }
64 |
65 | @Override
66 | public void onDismiss(@NonNull DialogInterface dialog) {
67 | super.onDismiss(dialog);
68 |
69 | dismissListener.onDismiss(dialog);
70 | }
71 |
72 | @NonNull
73 | @Override
74 | public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
75 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
76 | LayoutInflater inflater = requireActivity().getLayoutInflater();
77 |
78 | View view = inflater.inflate(R.layout.dialog_info, null);
79 | populate(view);
80 |
81 | builder.setView(view)
82 | .setPositiveButton(R.string.dialog_info_positive, null)
83 | .setNeutralButton(R.string.dialog_info_delete, (dialog, which) -> bluetooth.getDevices().removeDevice(device.getAddress()));
84 |
85 |
86 | Dialog dialog = builder.create();
87 | dialog.setTitle(R.string.dialog_info_title);
88 |
89 | return dialog;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/customization/DefaultSettings.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.customization;
2 |
3 | import android.content.SharedPreferences;
4 | import ch.virt.smartphonemouse.mouse.Parameters;
5 |
6 | /**
7 | * This class is used for restoring the settings to their factory defaults.
8 | */
9 | public class DefaultSettings {
10 |
11 | /**
12 | * Checks whether the preferences have once been populated.
13 | * If not, they get initialized to the default settings.
14 | *
15 | * @param preferences preferences to check in
16 | */
17 | public static void check(SharedPreferences preferences) {
18 | if (!preferences.getBoolean("populated", false)) set(preferences);
19 | }
20 |
21 | /**
22 | * Overwrites the settings to the defaults
23 | *
24 | * @param preferences preferences to write in
25 | */
26 | public static void set(SharedPreferences preferences) {
27 | SharedPreferences.Editor edit = preferences.edit();
28 |
29 | edit.putBoolean("populated", true);
30 | edit.putBoolean("showUsage", true);
31 | edit.putBoolean("advanced", false);
32 |
33 | edit.apply();
34 |
35 | defaultDebug(preferences);
36 | defaultInterface(preferences);
37 | defaultMovement(preferences);
38 | defaultCommunication(preferences);
39 | }
40 |
41 | private static void defaultDebug(SharedPreferences preferences) {
42 | preferences.edit()
43 | .putBoolean("debugEnabled", false)
44 | .putString("debugHost", "undefined")
45 | .putInt("debugPort", 55555)
46 | .apply();
47 | }
48 |
49 | /**
50 | * Writes the default interface settings
51 | *
52 | * @param preferences preferences to write in
53 | */
54 | private static void defaultInterface(SharedPreferences preferences) {
55 | SharedPreferences.Editor edit = preferences.edit();
56 |
57 | edit.putString("interfaceTheme", "dark");
58 |
59 | edit.putInt("interfaceBehaviourScrollStep", 50);
60 | edit.putInt("interfaceBehaviourSpecialWait", 300);
61 |
62 | edit.putBoolean("interfaceVisualsEnable", true);
63 | edit.putInt("interfaceVisualsStrokeWeight", 4);
64 | edit.putFloat("interfaceVisualsIntensity", 0.5f);
65 |
66 | edit.putBoolean("interfaceVibrationsEnable", true);
67 | edit.putInt("interfaceVibrationsButtonIntensity", 50);
68 | edit.putInt("interfaceVibrationsButtonLength", 30);
69 | edit.putInt("interfaceVibrationsScrollIntensity", 25);
70 | edit.putInt("interfaceVibrationsScrollLength", 20);
71 | edit.putInt("interfaceVibrationsSpecialIntensity", 50);
72 | edit.putInt("interfaceVibrationsSpecialLength", 50);
73 |
74 | edit.putFloat("interfaceLayoutHeight", 0.3f);
75 | edit.putFloat("interfaceLayoutMiddleWidth", 0.2f);
76 |
77 | edit.apply();
78 | }
79 |
80 | /**
81 | * Writes the default movement settings
82 | *
83 | * @param preferences preferences to write in
84 | */
85 | private static void defaultMovement(SharedPreferences preferences) {
86 | new Parameters(preferences).reset();
87 | }
88 |
89 | /**
90 | * Writes the default communication settings
91 | *
92 | * @param preferences preferences to write in
93 | */
94 | private static void defaultCommunication(SharedPreferences preferences) {
95 | SharedPreferences.Editor edit = preferences.edit();
96 |
97 | edit.putInt("communicationTransmissionRate", 100);
98 |
99 | edit.apply();
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/mouse/math/Vec3f.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.mouse.math;
2 |
3 | /**
4 | * This class represents a three-dimensional vector, based on floats
5 | */
6 | public class Vec3f {
7 | public float x, y, z;
8 |
9 | public Vec3f() {}
10 | public Vec3f(float x, float y, float z) {
11 | this.x = x;
12 | this.y = y;
13 | this.z = z;
14 | }
15 |
16 | /**
17 | * Returns the x and y axes as a 2d vector
18 | */
19 | public Vec2f xy() {
20 | return new Vec2f(x, y);
21 | }
22 |
23 | /**
24 | * Returns the y and z axes as a 2d vector
25 | */
26 | public Vec2f yz() {
27 | return new Vec2f(y, z);
28 | }
29 |
30 | /**
31 | * Returns the x and z axes as a 2d vector
32 | */
33 | public Vec2f xz() {
34 | return new Vec2f(x, z);
35 | }
36 |
37 | /**
38 | * Returns the absolute value or length of the vector
39 | */
40 | public float abs() {
41 | return (float) Math.sqrt(x*x + y*y + z*z);
42 | }
43 |
44 | /**
45 | * Creates a copy of this vector
46 | */
47 | public Vec3f copy() {
48 | return new Vec3f(x, y, z);
49 | }
50 |
51 | /**
52 | * Adds another vector to this one
53 | */
54 | public Vec3f add(Vec3f other) {
55 | x += other.x;
56 | y += other.y;
57 | z += other.z;
58 |
59 | return this;
60 | }
61 |
62 | /**
63 | * Adds another vector to this one
64 | */
65 | public Vec3f subtract(Vec3f other) {
66 | x -= other.x;
67 | y -= other.y;
68 | z -= other.z;
69 |
70 | return this;
71 | }
72 |
73 | /**
74 | * Multiplies or scales this vector by an amount
75 | */
76 | public Vec3f multiply(float factor) {
77 | x *= factor;
78 | y *= factor;
79 | z *= factor;
80 |
81 | return this;
82 | }
83 |
84 | /**
85 | * Divides this vector by a given amount
86 | */
87 | public Vec3f divide(float divisor) {
88 | x /= divisor;
89 | y /= divisor;
90 | z /= divisor;
91 |
92 | return this;
93 | }
94 |
95 | /**
96 | * Negates this vector
97 | */
98 | public Vec3f negative() {
99 | return this.multiply(-1f);
100 | }
101 |
102 | /**
103 | * Calculates the average between this and a given other vector. Not affecting this instance.
104 | */
105 | public Vec3f mean(Vec3f second) {
106 | return this.copy().add(second).divide(2);
107 | }
108 |
109 | /**
110 | * Rotates this vector by all three axis by using euler angles in a xyz fashion
111 | * @param rotation vector containing the rotation for each axis as radians
112 | */
113 | public Vec3f rotate(Vec3f rotation) {
114 |
115 | // Calculate sines and cosines that are used (for optimization)
116 | float sa = (float) Math.sin(rotation.x);
117 | float ca = (float) Math.cos(rotation.x);
118 | float sb = (float) Math.sin(rotation.y);
119 | float cb = (float) Math.cos(rotation.y);
120 | float sc = (float) Math.sin(rotation.z);
121 | float cc = (float) Math.cos(rotation.z);
122 |
123 | // Apply the rotation (matrix used: xyz)
124 | float newX = cb * cc * x + cb * (-sc) * y + sb * z;
125 | float newY = ((-sa) * (-sb) * cb + ca * sc) * x + ((-sa) * (-sb) * (-sc) + ca * cc) * y + (-sa) * cb * z;
126 | float newZ = (ca * (-sb) * cc + sa * sc) * x + (ca * (-sb) * (-sc) + sa * cc) * y + ca * cb * z;
127 |
128 | this.x = newX;
129 | this.y = newY;
130 | this.z = newZ;
131 |
132 | return this;
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/ConnectFragment.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui;
2 |
3 | import android.graphics.drawable.GradientDrawable;
4 | import android.os.Bundle;
5 | import android.view.View;
6 | import android.widget.ImageView;
7 | import android.widget.TextView;
8 |
9 | import androidx.annotation.NonNull;
10 | import androidx.annotation.Nullable;
11 | import androidx.fragment.app.Fragment;
12 |
13 | import ch.virt.smartphonemouse.R;
14 | import ch.virt.smartphonemouse.transmission.BluetoothHandler;
15 | import ch.virt.smartphonemouse.ui.connect.ConnectConnectedSubfragment;
16 | import ch.virt.smartphonemouse.ui.connect.ConnectConnectingSubfragment;
17 | import ch.virt.smartphonemouse.ui.connect.ConnectFailedSubfragment;
18 | import ch.virt.smartphonemouse.ui.connect.ConnectSelectSubfragment;
19 |
20 | /**
21 | * This fragment allows the user to see the current connection status and to change it.
22 | */
23 | public class ConnectFragment extends Fragment {
24 |
25 | private ImageView status;
26 | private TextView statusText;
27 |
28 | private BluetoothHandler bluetooth;
29 |
30 | /**
31 | * Creates the fragment.
32 | *
33 | * @param bluetooth bluetooth handler used for bluetooth operations
34 | */
35 | public ConnectFragment(BluetoothHandler bluetooth) {
36 | super(R.layout.fragment_connect);
37 | this.bluetooth = bluetooth;
38 | }
39 |
40 | @Override
41 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
42 | super.onViewCreated(view, savedInstanceState);
43 |
44 | status = view.findViewById(R.id.connect_status);
45 | statusText = view.findViewById(R.id.connect_status_text);
46 |
47 | update();
48 | }
49 |
50 | /**
51 | * Updates the content on the page according to the current status.
52 | */
53 | public void update() {
54 | if (bluetooth.isInitialized()) {
55 |
56 | if (bluetooth.isConnecting()) {
57 |
58 | loadFragment(new ConnectConnectingSubfragment());
59 | setStatus(R.color.status_connecting, R.string.connect_status_connecting);
60 |
61 | } else if (!bluetooth.isConnected()) {
62 |
63 | if (bluetooth.getHost().hasFailed()) {
64 |
65 | ConnectFailedSubfragment fragment = new ConnectFailedSubfragment(bluetooth);
66 | fragment.setReturnListener(this::update); // Updates the fragment when going back
67 | loadFragment(fragment);
68 |
69 | } else loadFragment(new ConnectSelectSubfragment(bluetooth));
70 |
71 | setStatus(R.color.status_disconnected, R.string.connect_status_disconnected);
72 | } else {
73 |
74 | loadFragment(new ConnectConnectedSubfragment(bluetooth));
75 | setStatus(R.color.status_connected, R.string.connect_status_connected);
76 |
77 | }
78 | }
79 | }
80 |
81 | /**
82 | * Sets the status of the page.
83 | *
84 | * @param color color of the current status
85 | * @param text name of the current status
86 | */
87 | private void setStatus(int color, int text) {
88 | ((GradientDrawable) status.getBackground()).setColor(getResources().getColor(color, null));
89 | statusText.setText(text);
90 | }
91 |
92 | /**
93 | * Sets the inner content to the requested fragment.
94 | *
95 | * @param fragment fragment to set to
96 | */
97 | private void loadFragment(Fragment fragment) {
98 | getChildFragmentManager().beginTransaction().setReorderingAllowed(true).replace(R.id.connect_container, fragment).commit();
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/settings/dialog/CalibrateDialog.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.settings.dialog;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.os.Bundle;
7 | import android.view.LayoutInflater;
8 | import android.widget.Button;
9 | import androidx.annotation.NonNull;
10 | import androidx.annotation.Nullable;
11 | import androidx.fragment.app.DialogFragment;
12 | import androidx.fragment.app.Fragment;
13 | import ch.virt.smartphonemouse.R;
14 |
15 | /**
16 | * This dialog is used in the settings menu when the user requests to calibrate the sampling rate.
17 | */
18 | public class CalibrateDialog extends DialogFragment {
19 |
20 | AlertDialog dialog;
21 |
22 | Button positiveButton;
23 |
24 | boolean calibrating = false;
25 |
26 | private DialogInterface.OnDismissListener finishedListener;
27 |
28 | /**
29 | * Sets the listener that should be used to update the setting once the dialog is done.
30 | *
31 | * @param finishedListener dialog dismiss listener that is executed
32 | */
33 | public void setFinishedListener(DialogInterface.OnDismissListener finishedListener) {
34 | this.finishedListener = finishedListener;
35 | }
36 |
37 | /**
38 | * Is called when the dialog is created.
39 | */
40 | private void created() {
41 | dialog.setTitle(R.string.dialog_calibrate_title);
42 |
43 | dialog.setCanceledOnTouchOutside(false);
44 |
45 | setFragment(new CalibrateBeginSubdialog());
46 | }
47 |
48 | private void calibrate() {
49 | calibrating = true;
50 |
51 | positiveButton.setEnabled(false);
52 | positiveButton.setText(R.string.dialog_calibrate_done);
53 |
54 | setFragment(new CalibrationHappeningSubdialog(() -> positiveButton.post(this::finished)));
55 | }
56 |
57 | /**
58 | * Is called when the calibration process has finished.
59 | */
60 | private void finished() {
61 | dialog.setTitle(R.string.dialog_calibrate_finished_title);
62 |
63 | dialog.setCanceledOnTouchOutside(true);
64 | positiveButton.setEnabled(true);
65 |
66 | setFragment(new CalibrateFinishedSubdialog());
67 | }
68 |
69 | /**
70 | * Sets the fragment of the dialog.
71 | *
72 | * @param fragment fragment to set
73 | */
74 | private void setFragment(Fragment fragment) {
75 | getChildFragmentManager().beginTransaction().setReorderingAllowed(true).replace(R.id.calibrate_container, fragment).commit();
76 | }
77 |
78 | @Override
79 | public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
80 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
81 | LayoutInflater inflater = requireActivity().getLayoutInflater();
82 |
83 | builder.setView(inflater.inflate(R.layout.dialog_calibrate, null))
84 | .setPositiveButton(R.string.dialog_calibrate_next, null);
85 |
86 |
87 | dialog = builder.create();
88 | dialog.setTitle("-"); // Add default title so it is shown
89 | dialog.setOnShowListener(dialogInterface -> {
90 | positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
91 |
92 | positiveButton.setOnClickListener((b) -> {
93 | if (!calibrating) calibrate();
94 | else dismiss();
95 | });
96 |
97 | created();
98 | });
99 |
100 | return dialog;
101 | }
102 |
103 | @Override
104 | public void onDismiss(@NonNull DialogInterface dialog) {
105 | super.onDismiss(dialog);
106 |
107 | if (finishedListener != null) finishedListener.onDismiss(dialog);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/mouse/MouseCalibrateDialog.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.mouse;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.os.Bundle;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.widget.Button;
9 | import androidx.annotation.NonNull;
10 | import androidx.annotation.Nullable;
11 | import androidx.fragment.app.DialogFragment;
12 | import androidx.fragment.app.Fragment;
13 | import ch.virt.smartphonemouse.R;
14 |
15 | /**
16 | * This class is the dialog that is shown upon mouse calibration
17 | */
18 | public class MouseCalibrateDialog extends DialogFragment {
19 |
20 | private AlertDialog dialog;
21 | private Button positiveButton, negativeButton;
22 |
23 | private boolean introduction;
24 |
25 | /**
26 | * Is called when the dialog is created.
27 | */
28 | private void created() {
29 | dialog.setTitle(R.string.dialog_mouse_calibrate_title_intro);
30 |
31 | introduction = true;
32 |
33 | positiveButton.setEnabled(true);
34 | negativeButton.setVisibility(View.VISIBLE);
35 |
36 | setFragment(new MouseMessageSubdialog(getResources().getString(R.string.mouse_message_calibrate)));
37 | }
38 |
39 | /**
40 | * Is called when the user wants to go to the next page.
41 | */
42 | private void next() {
43 | dialog.setTitle(R.string.dialog_mouse_calibrate_title_calibrate);
44 |
45 | if (introduction) {
46 |
47 | dialog.setCanceledOnTouchOutside(false);
48 | negativeButton.setVisibility(View.GONE);
49 |
50 | positiveButton.setEnabled(false);
51 | positiveButton.setText(R.string.dialog_mouse_calibrate_done);
52 |
53 | introduction = false;
54 |
55 | // setFragment(new CalibrationHappeningSubdialog((r) -> positiveButton.post(this::finished)));
56 | } else dismiss();
57 |
58 | }
59 |
60 | /**
61 | * Is called when the calibration procedure is finished.
62 | */
63 | private void finished() {
64 | dialog.setTitle(R.string.dialog_mouse_calibrate_title_finished);
65 |
66 | dialog.setCanceledOnTouchOutside(true);
67 | positiveButton.setEnabled(true);
68 |
69 | setFragment(new MouseMessageSubdialog(getResources().getString(R.string.dialog_mouse_calibrate_finished)));
70 | }
71 |
72 | /**
73 | * Sets the current dialog fragment that should be displayed.
74 | *
75 | * @param fragment fragment to display
76 | */
77 | private void setFragment(Fragment fragment) {
78 | getChildFragmentManager().beginTransaction().setReorderingAllowed(true).replace(R.id.mouse_container, fragment).commit();
79 | }
80 |
81 | @NonNull
82 | @Override
83 | public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
84 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
85 | LayoutInflater inflater = requireActivity().getLayoutInflater();
86 |
87 | builder.setView(inflater.inflate(R.layout.dialog_mouse, null))
88 | .setPositiveButton(R.string.dialog_mouse_next, null)
89 | .setNegativeButton(R.string.dialog_mouse_calibrate_cancel, null);
90 |
91 |
92 | dialog = builder.create();
93 | dialog.setTitle("-"); // Add default title so it is shown
94 | dialog.setOnShowListener(dialogInterface -> {
95 | positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
96 | positiveButton.setOnClickListener(view -> next());
97 |
98 | negativeButton = dialog.getButton(AlertDialog.BUTTON_NEGATIVE);
99 |
100 | created();
101 | });
102 |
103 | return dialog;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/settings/SettingsInterfaceSubfragment.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.settings;
2 |
3 | import android.os.Bundle;
4 |
5 | import androidx.preference.PreferenceFragmentCompat;
6 |
7 | import androidx.preference.PreferenceManager;
8 | import ch.virt.smartphonemouse.R;
9 | import ch.virt.smartphonemouse.ui.settings.custom.SeekFloatPreference;
10 | import ch.virt.smartphonemouse.ui.settings.custom.SeekIntegerPreference;
11 |
12 | /**
13 | * This fragment is the settings page, where the user can configure things regarding the interface.
14 | */
15 | public class SettingsInterfaceSubfragment extends PreferenceFragmentCompat {
16 |
17 | @Override
18 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
19 | setPreferencesFromResource(R.xml.settings_interface, null);
20 |
21 | SeekFloatPreference interfaceLayoutMiddleWidth = findPreference("interfaceLayoutMiddleWidth");
22 | interfaceLayoutMiddleWidth.setMaximum(0.5f);
23 | interfaceLayoutMiddleWidth.setMinimum(0.0f);
24 | interfaceLayoutMiddleWidth.setSteps(100);
25 | interfaceLayoutMiddleWidth.update();
26 |
27 | SeekFloatPreference interfaceLayoutHeight = findPreference("interfaceLayoutHeight");
28 | interfaceLayoutHeight.setMaximum(1.0f);
29 | interfaceLayoutHeight.setMinimum(0.0f);
30 | interfaceLayoutHeight.setSteps(100);
31 | interfaceLayoutHeight.update();
32 |
33 | SeekIntegerPreference interfaceVibrationsSpecialIntensity = findPreference("interfaceVibrationsSpecialIntensity");
34 | interfaceVibrationsSpecialIntensity.setMaximum(100);
35 | interfaceVibrationsSpecialIntensity.setMinimum(0);
36 | interfaceVibrationsSpecialIntensity.setSteps(100);
37 | interfaceVibrationsSpecialIntensity.update();
38 |
39 | SeekIntegerPreference interfaceVibrationsScrollIntensity = findPreference("interfaceVibrationsScrollIntensity");
40 | interfaceVibrationsScrollIntensity.setMaximum(100);
41 | interfaceVibrationsScrollIntensity.setMinimum(0);
42 | interfaceVibrationsScrollIntensity.setSteps(100);
43 | interfaceVibrationsScrollIntensity.update();
44 |
45 | SeekIntegerPreference interfaceVibrationsButtonIntensity = findPreference("interfaceVibrationsButtonIntensity");
46 | interfaceVibrationsButtonIntensity.setMaximum(100);
47 | interfaceVibrationsButtonIntensity.setMinimum(0);
48 | interfaceVibrationsButtonIntensity.setSteps(100);
49 | interfaceVibrationsButtonIntensity.update();
50 |
51 | SeekFloatPreference interfaceVisualsIntensity = findPreference("interfaceVisualsIntensity");
52 | interfaceVisualsIntensity.setMaximum(1.0f);
53 | interfaceVisualsIntensity.setMinimum(0.0f);
54 | interfaceVisualsIntensity.setSteps(100);
55 | interfaceVisualsIntensity.update();
56 |
57 | checkAdvanced();
58 | }
59 |
60 | public void checkAdvanced() {
61 | boolean advanced = PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("advanced", false);
62 | findPreference("interfaceBehaviour").setVisible(advanced);
63 | findPreference("interfaceVisualsStrokeWeight").setVisible(advanced);
64 | findPreference("interfaceVisualsIntensity").setVisible(advanced);
65 | findPreference("interfaceVibrationsButtonIntensity").setVisible(advanced);
66 | findPreference("interfaceVibrationsButtonLength").setVisible(advanced);
67 | findPreference("interfaceVibrationsScrollIntensity").setVisible(advanced);
68 | findPreference("interfaceVibrationsScrollLength").setVisible(advanced);
69 | findPreference("interfaceVibrationsSpecialIntensity").setVisible(advanced);
70 | findPreference("interfaceVibrationsSpecialLength").setVisible(advanced);
71 | findPreference("interfaceLayout").setVisible(advanced);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/mouse/Calibration.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.mouse;
2 |
3 | import android.util.Log;
4 | import ch.virt.smartphonemouse.mouse.components.WindowAverage;
5 | import ch.virt.smartphonemouse.mouse.math.Vec3f;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | public class Calibration {
11 |
12 | private static final String TAG = "Calibration";
13 |
14 | public static final int STATE_START = 0;
15 | public static final int STATE_SAMPLING = 1;
16 | public static final int STATE_NOISE = 3;
17 | public static final int STATE_END = 4;
18 |
19 |
20 | int state = STATE_START;
21 | private StateListener listener;
22 |
23 | boolean started = false;
24 | float startTime = 0;
25 |
26 | private int samples;
27 |
28 | private List accelerationNoise;
29 | private List rotationNoise;
30 | private WindowAverage gravityAverage;
31 | private WindowAverage noiseAverage;
32 |
33 | private final Parameters params;
34 |
35 | private float durationSampling;
36 | private float durationNoise;
37 |
38 | public Calibration(StateListener listener, Parameters params) {
39 | this.listener = listener;
40 | this.params = params;
41 | }
42 |
43 | public void setListener(StateListener listener) {
44 | this.listener = listener;
45 | }
46 |
47 | public void startCalibration() {
48 | Log.d(TAG, "Starting Sampling Rate Calibration");
49 | samples = 0;
50 | this.durationSampling = params.getCalibrationSamplingTime();
51 |
52 | updateState(STATE_SAMPLING);
53 | }
54 |
55 | private void startNoise() {
56 | Log.d(TAG, "Starting Noise Level Calibration");
57 |
58 | float samplingRate = samples / durationSampling;
59 | params.calibrateSamplingRate(samplingRate);
60 |
61 | accelerationNoise = new ArrayList<>();
62 | rotationNoise = new ArrayList<>();
63 |
64 | gravityAverage = new WindowAverage(params.getLengthWindowGravity());
65 | noiseAverage = new WindowAverage(params.getLengthWindowNoise());
66 |
67 | this.durationNoise = params.getCalibrationNoiseTime();
68 |
69 | updateState(STATE_NOISE);
70 | }
71 |
72 | private void endCalibration() {
73 | Log.d(TAG, "Ending Calibration");
74 |
75 | params.calibrateNoiseLevels(accelerationNoise, rotationNoise);
76 | params.setCalibrated(true);
77 | updateState(STATE_END);
78 | }
79 |
80 | public void data(float time, Vec3f acceleration, Vec3f angularVelocity) {
81 | if (!started) {
82 | startTime = time;
83 | started = true;
84 | }
85 |
86 | if (state == STATE_SAMPLING) {
87 |
88 | if (time - startTime > durationSampling) {
89 | startNoise();
90 | }
91 |
92 | samples++;
93 |
94 |
95 | } else if (state == STATE_NOISE) {
96 |
97 | if (time - startTime > durationNoise) {
98 | endCalibration();
99 | }
100 |
101 | float acc = acceleration.xy().abs();
102 |
103 | // Remove gravity or rather lower frequencies
104 | float gravity = gravityAverage.avg(acc);
105 | acc -= gravity;
106 | acc = Math.abs(acc);
107 |
108 | // Remove noise
109 | acc = noiseAverage.avg(acc);
110 |
111 | // Calculate the rotation activation
112 | float rot = Math.abs(angularVelocity.z);
113 |
114 | accelerationNoise.add(acc);
115 | rotationNoise.add(rot);
116 | }
117 | }
118 |
119 | private void updateState(int state) {
120 | this.state = state;
121 | started = false;
122 |
123 | listener.update(state);
124 | }
125 |
126 | public interface StateListener {
127 | void update(int state);
128 | }
129 |
130 | }
131 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/transmission/DeviceStorage.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.transmission;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.preference.PreferenceManager;
6 |
7 | import com.google.gson.Gson;
8 | import com.google.gson.reflect.TypeToken;
9 |
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | /**
14 | * This class stores already known bluetooth devices.
15 | */
16 | public class DeviceStorage {
17 | public static final String DEVICES_KEY = "devices";
18 |
19 | private Context context;
20 | private List devices;
21 |
22 | /**
23 | * Creates and loads the device storage.
24 | *
25 | * @param context context to access preferences
26 | */
27 | public DeviceStorage(Context context) {
28 | this.context = context;
29 | load();
30 | }
31 |
32 | /**
33 | * Loads the devices from the preferences.
34 | */
35 | public void load() {
36 | String src = PreferenceManager.getDefaultSharedPreferences(context).getString(DEVICES_KEY, "[]");
37 | devices = new Gson().fromJson(src, new TypeToken>() { }.getType());
38 | }
39 |
40 | /**
41 | * Saves the devices to the preferences.
42 | */
43 | public void save() {
44 | devices.sort((o1, o2) -> -Long.compare(o1.getLastConnected(), o2.getLastConnected()));
45 | String src = new Gson().toJson(devices);
46 | PreferenceManager.getDefaultSharedPreferences(context).edit().putString(DEVICES_KEY, src).apply();
47 | }
48 |
49 | /**
50 | * Returns all known devices.
51 | *
52 | * @return list of known host devices.
53 | */
54 | public List getDevices() {
55 | return devices;
56 | }
57 |
58 | /**
59 | * Returns a specific host device at a certain index.
60 | *
61 | * @param i index of the device.
62 | * @return known device
63 | */
64 | public HostDevice getDevice(int i) {
65 | return devices.get(i);
66 | }
67 |
68 | /**
69 | * Returns a specific host device with a certain bluetooth mac address.
70 | *
71 | * @param address bluetooth mac address
72 | * @return known device
73 | */
74 | public HostDevice getDevice(String address) {
75 | for (HostDevice device : devices) {
76 | if (device.getAddress().equals(address)) return device;
77 | }
78 |
79 | return null;
80 | }
81 |
82 | /**
83 | * Adds a device to the storage.
84 | *
85 | * @param device known device to add
86 | */
87 | public void addDevice(HostDevice device) {
88 | devices.add(device);
89 | save();
90 | }
91 |
92 | /**
93 | * Removes a device at an index from the known devices.
94 | *
95 | * @param i index of that device
96 | */
97 | public void removeDevice(int i) {
98 | HostDevice device = getDevice(i);
99 |
100 | if (device != null) {
101 | removeDevice(device);
102 | }
103 | }
104 |
105 | /**
106 | * Removes a device with a specific bluetooth mac address.
107 | *
108 | * @param address bluetooth mac address of that device
109 | */
110 | public void removeDevice(String address) {
111 | HostDevice device = getDevice(address);
112 |
113 | if (device != null) {
114 | removeDevice(device);
115 | }
116 | }
117 |
118 | /**
119 | * Removes a known device.
120 | *
121 | * @param device device to remove
122 | */
123 | public void removeDevice(HostDevice device) {
124 | devices.remove(device);
125 | save();
126 | }
127 |
128 | /**
129 | * Returns whether a certain device is present in the known devices.
130 | *
131 | * @param address bluetooth mac address of said device
132 | * @return whether it is present
133 | */
134 | public boolean hasDevice(String address) {
135 | return getDevice(address) != null;
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/customization/CalibrationHandler.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.customization;
2 |
3 | import android.content.Context;
4 | import android.hardware.Sensor;
5 | import android.hardware.SensorEvent;
6 | import android.hardware.SensorEventListener;
7 | import android.hardware.SensorManager;
8 | import androidx.preference.PreferenceManager;
9 | import ch.virt.smartphonemouse.mouse.Calibration;
10 | import ch.virt.smartphonemouse.mouse.MovementHandler;
11 | import ch.virt.smartphonemouse.mouse.Parameters;
12 | import ch.virt.smartphonemouse.mouse.math.Vec3f;
13 |
14 | /**
15 | * This class is used to measure and save the sampling rate of the inbuilt accelerometer.
16 | */
17 | public class CalibrationHandler implements SensorEventListener {
18 |
19 | private static final float NANO_FULL_FACTOR = 1e-9f;
20 |
21 | private SensorManager manager;
22 | private Sensor accelerometer;
23 | private Sensor gyroscope;
24 |
25 | private boolean begun = false;
26 | private long firstTime = 0;
27 | private Vec3f gyroSample = new Vec3f();
28 | private boolean registered;
29 |
30 | private final Context context;
31 | private final Calibration calibration;
32 |
33 | /**
34 | * Creates the calibrator.
35 | *
36 | * @param context context to use
37 | */
38 | public CalibrationHandler(Context context) {
39 | this.context = context;
40 |
41 | calibration = new Calibration((state) -> {}, new Parameters(PreferenceManager.getDefaultSharedPreferences(context)));
42 | fetchSensor();
43 | }
44 |
45 | /**
46 | * Fetches the sensor from the system.
47 | */
48 | private void fetchSensor() {
49 | manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
50 | accelerometer = manager.getDefaultSensor(MovementHandler.SENSOR_TYPE_ACCELEROMETER);
51 | gyroscope = manager.getDefaultSensor(MovementHandler.SENSOR_TYPE_GYROSCOPE);
52 | }
53 |
54 | /**
55 | * Registers itself as a listener.
56 | */
57 | private void register() {
58 | if (registered) return;
59 | manager.registerListener(this, accelerometer, MovementHandler.SAMPLING_RATE);
60 | manager.registerListener(this, gyroscope, MovementHandler.SAMPLING_RATE);
61 |
62 | registered = true;
63 | }
64 |
65 | /**
66 | * Unregisters itself as a listener.
67 | */
68 | private void unregister() {
69 | if (!registered) return;
70 | manager.unregisterListener(this, accelerometer);
71 | manager.unregisterListener(this, gyroscope);
72 |
73 | registered = false;
74 | }
75 |
76 | /**
77 | * Starts the measuring process.
78 | *
79 | * @param doneListener listener that is executed once the process has finished
80 | */
81 | public void calibrate(Calibration.StateListener doneListener) {
82 | calibration.setListener((state) -> {
83 | if (state == Calibration.STATE_END) unregister();
84 |
85 | doneListener.update(state);
86 | });
87 |
88 | begun = false;
89 | calibration.startCalibration();
90 |
91 | register();
92 | }
93 |
94 | @Override
95 | public void onSensorChanged(SensorEvent event) {
96 | if (event.sensor.getType() == MovementHandler.SENSOR_TYPE_ACCELEROMETER) {
97 |
98 | if (!begun) {
99 | begun = true;
100 | firstTime = event.timestamp;
101 | }
102 |
103 |
104 | float time = (event.timestamp - firstTime) * NANO_FULL_FACTOR;
105 | Vec3f acceleration = new Vec3f(event.values[0], event.values[1], event.values[2]);
106 |
107 | calibration.data(time, acceleration, gyroSample);
108 |
109 | } else if (event.sensor.getType() == MovementHandler.SENSOR_TYPE_GYROSCOPE) {
110 | this.gyroSample = new Vec3f(event.values[0], event.values[1], event.values[2]);
111 | }
112 | }
113 |
114 | @Override
115 | public void onAccuracyChanged(Sensor sensor, int accuracy) {}
116 | }
117 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/mouse/MouseInputs.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.mouse;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.preference.PreferenceManager;
6 |
7 | import ch.virt.smartphonemouse.transmission.BluetoothHandler;
8 |
9 | /**
10 | * This class collects the calculated inputs and transmits them to the computer when needed.
11 | */
12 | public class MouseInputs {
13 |
14 | private final float NANO_TO_FULL = 1e9f;
15 |
16 | private float xPosition, yPosition;
17 | private int wheelPosition;
18 | private boolean leftButton, middleButton, rightButton;
19 |
20 | private int transmissionRate = 0;
21 | private BluetoothHandler bluetoothHandler;
22 | private Context context;
23 |
24 | private Thread thread;
25 | private boolean running;
26 | private long lastTime;
27 |
28 | /**
29 | * Creates this class.
30 | *
31 | * @param bluetoothHandler bluetooth handler to send the signals to
32 | * @param context context to fetch settings from
33 | */
34 | public MouseInputs(BluetoothHandler bluetoothHandler, Context context) {
35 | this.bluetoothHandler = bluetoothHandler;
36 | this.context = context;
37 | }
38 |
39 | /**
40 | * Starts the transmission to the pc.
41 | */
42 | public void start() {
43 | if (running) return;
44 |
45 | thread = new Thread(this::run);
46 |
47 | transmissionRate = PreferenceManager.getDefaultSharedPreferences(context).getInt("communicationTransmissionRate", 100);
48 |
49 | running = true;
50 | thread.start();
51 | }
52 |
53 | /**
54 | * Starts the sending loop.
55 | * Should be executed on a separate thread.
56 | */
57 | private void run() {
58 | lastTime = System.nanoTime();
59 |
60 | while (running) {
61 | long current = System.nanoTime();
62 |
63 | if (current - lastTime >= NANO_TO_FULL / transmissionRate) {
64 | sendUpdate();
65 | lastTime = current;
66 | }
67 | }
68 | }
69 |
70 | /**
71 | * Stops the transmission to the pc.
72 | */
73 | public void stop() {
74 | running = false;
75 | }
76 |
77 | /**
78 | * Sends an update to the pc.
79 | */
80 | private void sendUpdate() {
81 | int x = (int) xPosition;
82 | int y = (int) yPosition;
83 |
84 | if (bluetoothHandler.getHost().isConnected())
85 | bluetoothHandler.getHost().sendReport(leftButton, middleButton, rightButton, wheelPosition, x, y);
86 |
87 | // Reset Deltas
88 | xPosition -= x;
89 | yPosition -= y;
90 | wheelPosition = 0;
91 | }
92 |
93 |
94 | /**
95 | * Changes the current wheel position.
96 | *
97 | * @param delta change in wheel steps
98 | */
99 | public void changeWheelPosition(int delta) {
100 | wheelPosition += delta;
101 | }
102 |
103 | /**
104 | * Sets the current state of the left button.
105 | *
106 | * @param leftButton whether the left button is pressed
107 | */
108 | public void setLeftButton(boolean leftButton) {
109 | this.leftButton = leftButton;
110 | }
111 |
112 | /**
113 | * Sets the current state of the middle button.
114 | *
115 | * @param middleButton whether the middle button is pressed
116 | */
117 | public void setMiddleButton(boolean middleButton) {
118 | this.middleButton = middleButton;
119 | }
120 |
121 | /**
122 | * Sets the current state of the right button.
123 | *
124 | * @param rightButton whether the right button is pressed
125 | */
126 | public void setRightButton(boolean rightButton) {
127 | this.rightButton = rightButton;
128 | }
129 |
130 | /**
131 | * Changes the x position of the mouse
132 | *
133 | * @param x change of the position
134 | */
135 | public void changeXPosition(float x) {
136 | xPosition += x;
137 | }
138 |
139 | /**
140 | * Changes the y position of the mouse
141 | *
142 | * @param y change of the position
143 | */
144 | public void changeYPosition(float y) {
145 | yPosition += y;
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
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.
8 |
9 |
10 | ---
11 |
12 | ## State
13 | 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.
14 |
15 | ## Features
16 | This app is defined by the following three core features:
17 |
18 | - **Real Position**: The real position of the device is estimated like a normal mouse using the device's internal sensors.
19 | - **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.
20 | - **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.
21 |
22 | ## Installation
23 | The app can currently be installed via two methods:
24 | - Download a built APK from the [release section](https://github.com/VirtCode/SmartMouse/releases) and open it on your device.
25 | - The app is available in the [IzzyOnDroid F-Droid Repository](https://apt.izzysoft.de/fdroid/index/apk/ch.virt.smartphonemouse/). See the [webpage](https://apt.izzysoft.de/fdroid/index/info) for how to add this repository to your F-Droid App.
26 |
27 | 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.
28 |
29 | ## Screenshots
30 |
31 | Expand to view some screenshots!
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | ## Processing Algorithm
42 | This app uses a custom sensor-fusion algorithm to estimate the position of the device that is developed specifically for mouse-like movements.
43 |
44 | *This algorithm will be documented here in the future.*
45 |
46 | ## Debugging
47 | **The following steps and procedures are only intended for debugging and are NOT required for normal usage.**
48 |
49 | 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.
50 |
51 | 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.
52 |
53 | Follow these steps to set up a debugging environment:
54 |
55 | 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.
56 | 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.
57 | 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.
58 |
59 | Now you are ready to analyze transmissions which are stored as CSV in your folder by SensorServer.
60 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/settings/custom/SeekIntegerPreference.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.settings.custom;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 |
6 | import androidx.preference.SeekBarPreference;
7 |
8 | import ch.virt.smartphonemouse.R;
9 |
10 | /**
11 | * This preference is a seek bar preference that does seek for integers but is
12 | */
13 | public class SeekIntegerPreference extends SeekBarPreference {
14 |
15 | private int maximum;
16 | private int minimum;
17 | private int steps;
18 |
19 | /**
20 | * Creates a preference.
21 | *
22 | * @param context context for the preference to be in
23 | * @param attrs attributes
24 | * @param defStyleAttr style attributes
25 | * @param defStyleRes style resources
26 | */
27 | public SeekIntegerPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
28 | super(context, attrs, defStyleAttr, defStyleRes);
29 |
30 | setSteps(20000);
31 | setMaximum(100);
32 | setMinimum(-100);
33 |
34 | }
35 |
36 | /**
37 | * Creates a preference.
38 | *
39 | * @param context context for the preference to be in
40 | * @param attrs attributes
41 | * @param defStyleAttr style attributes
42 | */
43 | public SeekIntegerPreference(Context context, AttributeSet attrs, int defStyleAttr) {
44 | this(context, attrs, defStyleAttr, 0);
45 | }
46 |
47 | /**
48 | * Creates a preference.
49 | *
50 | * @param context context for the preference to be in
51 | * @param attrs attributes
52 | */
53 | public SeekIntegerPreference(Context context, AttributeSet attrs) {
54 | this(context, attrs, R.attr.seekBarPreferenceStyle);
55 | }
56 |
57 |
58 | /**
59 | * Creates a preference.
60 | *
61 | * @param context context for the preference to be in
62 | */
63 | public SeekIntegerPreference(Context context) {
64 | this(context, null);
65 | }
66 |
67 | /**
68 | * Returns the maximum value of the preference.
69 | *
70 | * @return maximum value
71 | */
72 | public int getMaximum() {
73 | return maximum;
74 | }
75 |
76 | /**
77 | * Sets the maximum value of the preference.
78 | *
79 | * @param maximum maximum value.
80 | */
81 | public void setMaximum(int maximum) {
82 | this.maximum = maximum;
83 | }
84 |
85 | /**
86 | * Returns the minimum value of the preference.
87 | *
88 | * @return minimum value
89 | */
90 | public int getMinimum() {
91 | return minimum;
92 | }
93 |
94 | /**
95 | * Sets the minimum value of the reference
96 | *
97 | * @param minimum minimum value
98 | */
99 | public void setMinimum(int minimum) {
100 | this.minimum = minimum;
101 | }
102 |
103 | /**
104 | * Returns the steps the seek bar can be in.
105 | *
106 | * @return amount of steps
107 | */
108 | public int getSteps() {
109 | return steps;
110 | }
111 |
112 | /**
113 | * Sets the steps the seek bar can be positioned in.
114 | *
115 | * @param steps amount of steps
116 | */
117 | public void setSteps(int steps) {
118 | this.steps = steps;
119 |
120 | setMin(0);
121 | setMax(steps);
122 | }
123 |
124 | /**
125 | * Returns the real value of the preference. Use this instead of realValue.
126 | *
127 | * @return value of the preference
128 | */
129 | public int getRealValue() {
130 | return (int) (getValue() / ((float) steps) * (maximum - minimum) + minimum);
131 | }
132 |
133 | /**
134 | * Updates the preference to the stored value.
135 | */
136 | public void update() {
137 | onSetInitialValue(null);
138 | }
139 |
140 |
141 | @Override
142 | protected boolean persistInt(int value) {
143 | // Have to use persistence methods, because otherwise, the variables are not accessible enough
144 | return super.persistInt((int) (value / ((float) steps) * (maximum - minimum) + minimum));
145 | }
146 |
147 | @Override
148 | protected int getPersistedInt(int defaultReturnValue) {
149 | // Have to use persistence methods, because otherwise, the variables are not accessible enough
150 | return (int) ((super.getPersistedInt(minimum) - minimum) / ((float) (maximum - minimum)) * steps);
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/mouse/MouseUsageDialog.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.mouse;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.os.Bundle;
7 | import android.view.LayoutInflater;
8 | import android.widget.Button;
9 |
10 | import androidx.annotation.NonNull;
11 | import androidx.annotation.Nullable;
12 | import androidx.fragment.app.DialogFragment;
13 | import androidx.fragment.app.Fragment;
14 |
15 | import ch.virt.smartphonemouse.R;
16 |
17 | /**
18 | * This dialog is shown when the user opens the mouse fragment. It tells the user how to use the mouse.
19 | */
20 | public class MouseUsageDialog extends DialogFragment {
21 |
22 | private AlertDialog dialog;
23 | private Button positiveButton;
24 |
25 | private int index;
26 |
27 | private UsageFinishedListener finishedListener;
28 |
29 |
30 | public MouseUsageDialog(UsageFinishedListener finishedListener) {
31 | this.finishedListener = finishedListener;
32 | }
33 |
34 | /**
35 | * Returns a message to show the user at the given index.
36 | *
37 | * @param index index of the message
38 | * @return resource string id of the message
39 | */
40 | private int getCurrentMessage(int index) {
41 |
42 | switch (index) {
43 | case 0:
44 | return R.string.dialog_mouse_usage_even;
45 | case 1:
46 | return R.string.dialog_mouse_usage_move;
47 | case 2:
48 | return R.string.dialog_mouse_usage_buttons;
49 | case 3:
50 | return R.string.dialog_mouse_usage_return;
51 | default:
52 | return 0;
53 | }
54 | }
55 |
56 | /**
57 | * Returns how many messages can be displayed.
58 | *
59 | * @return amount of messages
60 | */
61 | private int getMessageAmount() {
62 | return 4;
63 | }
64 |
65 |
66 | /**
67 | * Is called when the dialog is created.
68 | */
69 | private void create() {
70 | showFragment();
71 |
72 | dialog.setCanceledOnTouchOutside(false);
73 | }
74 |
75 |
76 | /**
77 | * Is called when the user skips to the next page.
78 | */
79 | private void next() {
80 | if (index == getMessageAmount()) {
81 | dismiss();
82 | return;
83 | }
84 |
85 | index++;
86 | if (index == getMessageAmount()) {
87 |
88 | dialog.setCanceledOnTouchOutside(true);
89 | setFragment(new MouseUsageFinishedSubdialog());
90 |
91 | } else showFragment();
92 |
93 | }
94 |
95 | /**
96 | * Shows a message fragment with the current index.
97 | */
98 | private void showFragment() {
99 | setFragment(new MouseMessageSubdialog(getResources().getString(getCurrentMessage(index))));
100 | }
101 |
102 | /**
103 | * Displays a fragment in the respective fragment container.
104 | *
105 | * @param fragment fragment to display
106 | */
107 | private void setFragment(Fragment fragment) {
108 | getChildFragmentManager().beginTransaction().setReorderingAllowed(true).replace(R.id.mouse_container, fragment).commit();
109 | }
110 |
111 |
112 | @NonNull
113 | @Override
114 | public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
115 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
116 | LayoutInflater inflater = requireActivity().getLayoutInflater();
117 |
118 | builder.setView(inflater.inflate(R.layout.dialog_mouse, null))
119 | .setPositiveButton(R.string.dialog_mouse_usage_next, null);
120 |
121 | dialog = builder.create();
122 | dialog.setTitle(R.string.dialog_mouse_usage_title);
123 | dialog.setOnShowListener(dialogInterface -> {
124 | positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
125 | positiveButton.setOnClickListener(view -> next());
126 |
127 | create();
128 | });
129 |
130 | return dialog;
131 | }
132 |
133 | @Override
134 | public void onDismiss(@NonNull DialogInterface dialog) {
135 | finishedListener.finished();
136 |
137 | super.onDismiss(dialog);
138 | }
139 |
140 |
141 | /**
142 | * This interface is a basic listener for when the user has finished reading the instructions.
143 | */
144 | public interface UsageFinishedListener {
145 |
146 | /**
147 | * Called when the user is finished.
148 | */
149 | void finished();
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/mouse/MovementHandler.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.mouse;
2 |
3 | import android.content.Context;
4 | import android.hardware.Sensor;
5 | import android.hardware.SensorEvent;
6 | import android.hardware.SensorEventListener;
7 | import android.hardware.SensorManager;
8 | import androidx.preference.PreferenceManager;
9 | import ch.virt.smartphonemouse.mouse.math.Vec2f;
10 | import ch.virt.smartphonemouse.mouse.math.Vec3f;
11 | import ch.virt.smartphonemouse.transmission.DebugTransmitter;
12 |
13 | /**
14 | * This class handles and calculates the movement of the mouse
15 | */
16 | public class MovementHandler implements SensorEventListener {
17 |
18 | private static final float NANO_FULL_FACTOR = 1e-9f;
19 |
20 | public static final int SENSOR_TYPE_ACCELEROMETER = Sensor.TYPE_ACCELEROMETER;
21 | public static final int SENSOR_TYPE_GYROSCOPE = Sensor.TYPE_GYROSCOPE;
22 | public static final int SAMPLING_RATE = SensorManager.SENSOR_DELAY_FASTEST;
23 |
24 | private SensorManager manager;
25 | private Sensor accelerometer;
26 | private Sensor gyroscope;
27 |
28 | private boolean registered;
29 |
30 |
31 | private Vec3f gyroSample = new Vec3f(); // TODO: Make this a buffer to accommodate for vastly different sampling rates
32 | private long lastTime = 0;
33 | private long firstTime = 0;
34 | private Processing processing;
35 |
36 | private final Context context;
37 | private final MouseInputs inputs;
38 |
39 | /**
40 | * Creates a movement handler.
41 | *
42 | * @param context context to get the sensor from
43 | * @param inputs inputs to send the movement to
44 | */
45 | public MovementHandler(Context context, MouseInputs inputs) {
46 | this.context = context;
47 | this.inputs = inputs;
48 |
49 | fetchSensor();
50 | }
51 |
52 | /**
53 | * Creates the signal processing pipelines.
54 | */
55 | public void create(DebugTransmitter debug) {
56 | processing = new Processing(debug, new Parameters(PreferenceManager.getDefaultSharedPreferences(context)));
57 | }
58 |
59 | /**
60 | * Fetches the sensor from the context.
61 | */
62 | private void fetchSensor() {
63 | manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
64 | accelerometer = manager.getDefaultSensor(SENSOR_TYPE_ACCELEROMETER);
65 | gyroscope = manager.getDefaultSensor(SENSOR_TYPE_GYROSCOPE);
66 | }
67 |
68 | /**
69 | * Registers the sensor for this handler.
70 | */
71 | public void register() {
72 | if (registered) return;
73 | manager.registerListener(this, accelerometer, SAMPLING_RATE);
74 | manager.registerListener(this, gyroscope, SAMPLING_RATE);
75 |
76 | lastTime = 0;
77 | firstTime = 0;
78 | registered = true;
79 | }
80 |
81 | /**
82 | * Unregisters the sensor for this handler.
83 | */
84 | public void unregister() {
85 | if (!registered) return;
86 | manager.unregisterListener(this, accelerometer);
87 | manager.unregisterListener(this, gyroscope);
88 |
89 | registered = false;
90 | }
91 |
92 | @Override
93 | public void onSensorChanged(SensorEvent event) {
94 | if (!registered) return; // Ignore Samples when the listener is not registered
95 |
96 | if (event.sensor.getType() == SENSOR_TYPE_ACCELEROMETER) {
97 |
98 | if (firstTime == 0) { // Ignore First sample, because there is no delta
99 | lastTime = event.timestamp;
100 | firstTime = event.timestamp;
101 | return;
102 | }
103 |
104 | float delta = (event.timestamp - lastTime) * NANO_FULL_FACTOR;
105 | float time = (event.timestamp - firstTime) * NANO_FULL_FACTOR;
106 | Vec3f acceleration = new Vec3f(event.values[0], event.values[1], event.values[2]);
107 |
108 | Vec2f distance = processing.next(time, delta, acceleration, gyroSample);
109 | inputs.changeXPosition(distance.x);
110 | inputs.changeYPosition(-distance.y);
111 |
112 | lastTime = event.timestamp;
113 |
114 | } else if (event.sensor.getType() == SENSOR_TYPE_GYROSCOPE) {
115 | // 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
116 | this.gyroSample = new Vec3f(event.values[0], event.values[1], event.values[2]);
117 | }
118 | }
119 |
120 | @Override
121 | public void onAccuracyChanged(Sensor sensor, int accuracy) {
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/settings/custom/SeekFloatPreference.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui.settings.custom;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import androidx.preference.SeekBarPreference;
6 | import ch.virt.smartphonemouse.R;
7 |
8 | /**
9 | * This is a Seek Bar preference that stores a float.
10 | */
11 | public class SeekFloatPreference extends SeekBarPreference {
12 |
13 | private float maximum;
14 | private float minimum;
15 | private int steps;
16 |
17 | /**
18 | * Creates a preference.
19 | *
20 | * @param context context for the preference to be in
21 | * @param attrs attributes
22 | * @param defStyleAttr style attributes
23 | * @param defStyleRes style resources
24 | */
25 | public SeekFloatPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
26 | super(context, attrs, defStyleAttr, defStyleRes);
27 |
28 | // Set a ludicrous min, max and amount of steps, because all data will be fitted to it for some reason
29 | // FIXME: Investigate this and fix it
30 | setSteps(10000000);
31 | setMaximum(50000);
32 | setMinimum(-50000);
33 |
34 | }
35 |
36 | /**
37 | * Creates a preference.
38 | *
39 | * @param context context for the preference to be in
40 | * @param attrs attributes
41 | * @param defStyleAttr style attributes
42 | */
43 | public SeekFloatPreference(Context context, AttributeSet attrs, int defStyleAttr) {
44 | this(context, attrs, defStyleAttr, 0);
45 | }
46 |
47 | /**
48 | * Creates a preference.
49 | *
50 | * @param context context for the preference to be in
51 | * @param attrs attributes
52 | */
53 | public SeekFloatPreference(Context context, AttributeSet attrs) {
54 | this(context, attrs, R.attr.seekBarPreferenceStyle);
55 | }
56 |
57 |
58 |
59 | /**
60 | * Creates a preference.
61 | *
62 | * @param context context for the preference to be in
63 | */
64 | public SeekFloatPreference(Context context) {
65 | this(context, null);
66 | }
67 |
68 |
69 | /**
70 | * Returns the maximum value of the preference.
71 | *
72 | * @return maximum value
73 | */
74 | public float getMaximum() {
75 | return maximum;
76 | }
77 |
78 | /**
79 | * Sets the maximum value of the preference.
80 | *
81 | * @param maximum maximum value
82 | */
83 | public void setMaximum(float maximum) {
84 | this.maximum = maximum;
85 | }
86 |
87 | /**
88 | * Returns the minimum value of the preference.
89 | *
90 | * @return minimum value
91 | */
92 | public float getMinimum() {
93 | return minimum;
94 | }
95 |
96 | /**
97 | * Sets the minimum value of the preference.
98 | *
99 | * @param minimum minimum value
100 | */
101 | public void setMinimum(float minimum) {
102 | this.minimum = minimum;
103 | }
104 |
105 | /**
106 | * Returns how many steps are present on the progress bar.
107 | *
108 | * @return steps on the bar
109 | */
110 | public int getSteps() {
111 | return steps;
112 | }
113 |
114 | /**
115 | * Sets the steps present on the progress bar.
116 | *
117 | * @param steps steps on the bar
118 | */
119 | public void setSteps(int steps) {
120 | this.steps = steps;
121 |
122 | setMin(0);
123 | setMax(steps);
124 | }
125 |
126 | /**
127 | * Returns the real value that the preference holds. Use this instead of getValue.
128 | *
129 | * @return value
130 | */
131 | public float getRealValue() {
132 | return (getValue() / ((float) steps) * (maximum - minimum) + minimum);
133 | }
134 |
135 | /**
136 | * Updates the preference to the values stored in the storage.
137 | */
138 | public void update() {
139 | onSetInitialValue(null);
140 | }
141 |
142 |
143 | @Override
144 | protected boolean persistInt(int value) {
145 | // Have to use persistence methods, because otherwise, the variables are not accessible enough
146 | return super.persistFloat(value / ((float) steps) * (maximum - minimum) + minimum);
147 | }
148 |
149 | @Override
150 | protected int getPersistedInt(int defaultReturnValue) {
151 | // Have to use persistence methods, because otherwise, the variables are not accessible enough
152 | return (int) ((super.getPersistedFloat(minimum) - minimum) / (maximum - minimum) * steps);
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/app/src/main/java/ch/virt/smartphonemouse/ui/HomeFragment.java:
--------------------------------------------------------------------------------
1 | package ch.virt.smartphonemouse.ui;
2 |
3 | import android.graphics.drawable.GradientDrawable;
4 | import android.os.Bundle;
5 | import android.view.View;
6 | import android.widget.Button;
7 | import android.widget.ImageView;
8 | import android.widget.TextView;
9 |
10 | import androidx.annotation.NonNull;
11 | import androidx.annotation.Nullable;
12 | import androidx.fragment.app.Fragment;
13 |
14 | import ch.virt.smartphonemouse.MainActivity;
15 | import ch.virt.smartphonemouse.R;
16 | import ch.virt.smartphonemouse.transmission.BluetoothHandler;
17 | import ch.virt.smartphonemouse.transmission.DebugTransmitter;
18 | import ch.virt.smartphonemouse.ui.home.HomeConnectedSubfragment;
19 | import ch.virt.smartphonemouse.ui.home.HomeDisabledSubfragment;
20 | import ch.virt.smartphonemouse.ui.home.HomeDisconnectedSubfragment;
21 | import ch.virt.smartphonemouse.ui.home.HomeUnsupportedSubfragment;
22 |
23 | /**
24 | * This fragment contains the home page of the app, which shows basic information.
25 | */
26 | public class HomeFragment extends Fragment {
27 |
28 | private BluetoothHandler bluetooth;
29 | private DebugTransmitter debug;
30 |
31 | private ImageView status;
32 | private TextView statusText;
33 | private TextView debugStatus;
34 |
35 | private Button button;
36 |
37 | /**
38 | * Creates the fragment.
39 | *
40 | * @param bluetooth bluetooth handler to use
41 | */
42 | public HomeFragment(BluetoothHandler bluetooth, DebugTransmitter debug) {
43 | super(R.layout.fragment_home);
44 |
45 | this.bluetooth = bluetooth;
46 | this.debug = debug;
47 | }
48 |
49 | @Override
50 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
51 | super.onViewCreated(view, savedInstanceState);
52 |
53 | status = view.findViewById(R.id.home_status);
54 | statusText = view.findViewById(R.id.home_status_text);
55 | button = view.findViewById(R.id.home_button);
56 |
57 | debugStatus = view.findViewById(R.id.home_debug_status);
58 | debugStatus.setOnClickListener(v -> update());
59 |
60 | update();
61 | }
62 |
63 | /**
64 | * Updates the content of the page according to the current status.
65 | */
66 | public void update() {
67 | if (bluetooth.isInitialized())
68 |
69 | if (!bluetooth.isEnabled())
70 | setStatus(R.color.status_init, R.string.home_status_disabled, R.string.home_button_disabled, v -> bluetooth.enableBluetooth(), new HomeDisabledSubfragment(bluetooth));
71 |
72 | else if (!bluetooth.isSupported())
73 | setStatus(R.color.status_unsupported, R.string.home_status_unsupported, R.string.home_button_unsupported, v -> getActivity().finish(), new HomeUnsupportedSubfragment());
74 |
75 | else if (bluetooth.isConnected())
76 | setStatus(R.color.status_connected, R.string.home_status_connected, R.string.home_button_connected, v -> ((MainActivity) getActivity()).navigate(R.id.drawer_mouse), new HomeConnectedSubfragment(bluetooth));
77 |
78 | else
79 | 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());
80 |
81 | if (debug.isEnabled()) {
82 | debugStatus.setVisibility(View.VISIBLE);
83 |
84 | if (debug.isConnected()) debugStatus.setText(String.format(getContext().getText(R.string.home_debug_connected).toString(), debug.getServerString()));
85 | else debugStatus.setText(String.format(getContext().getText(R.string.home_debug_disconnected).toString(), debug.getServerString()));
86 | }
87 | }
88 |
89 | /**
90 | * Sets the status of the page.
91 | *
92 | * @param statusColor color of the status
93 | * @param statusText name of the status
94 | * @param buttonText text of the primary button of this status
95 | * @param buttonListener action of the primary button of this status
96 | * @param fragment fragment to be displayed
97 | */
98 | private void setStatus(int statusColor, int statusText, int buttonText, View.OnClickListener buttonListener, Fragment fragment) {
99 | ((GradientDrawable) status.getBackground()).setColor(getResources().getColor(statusColor, null));
100 | this.statusText.setText(statusText);
101 |
102 | button.setEnabled(true);
103 | button.setText(buttonText);
104 |
105 | button.setOnClickListener(buttonListener);
106 |
107 | getChildFragmentManager().beginTransaction().setReorderingAllowed(true).replace(R.id.home_container, fragment).commit();
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/brand/badge.svg:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------
/brand/background.svg:
--------------------------------------------------------------------------------
1 |
23 |
--------------------------------------------------------------------------------
/brand/banner.svg:
--------------------------------------------------------------------------------
1 |
23 |
--------------------------------------------------------------------------------