diff --git a/README.md b/README.md index dc5a2de..a03293b 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,121 @@ -# SensorBox - record all your sensors to csv files - -This repository is now archived and will no longer be actively maintained. - -
-
-
-
-
|
|
+
+| Wear dashboard | Wear live-sensor picker |
+|:---:|:---:|
+|
|
|
+
+### First-run introduction
+
+| Welcome | Local data | Privacy and terms |
+|:---:|:---:|:---:|
+|
|
|
|
+
+| Android lifecycle | Battery optimization | Recording folder |
+|:---:|:---:|:---:|
+|
|
|
|
+
+The introduction uses the original repository artwork. Privacy Policy, Terms of Use, battery optimization, and folder selection are live native actions. Folder selection remains mandatory before setup can finish.
+
+## Current feature set
+
+- Record available phone or watch sensors at Android sampling periods.
+- Record foreground GPS samples alongside sensor data.
+- Run measurement work in an explicit foreground service with health/location service types.
+- Stop safely from the app, watch, notification, low-battery policy, or a paired-device command.
+- Preview a live watch sensor with a Compose-native chart.
+- Stream watch recordings to the phone with the Wear OS Channel API.
+- Store phone recordings in a user-selected Storage Access Framework folder.
+- Follow system/dynamic color with light, dark, and custom fallback palettes.
+
+## Architecture
+
+The UI modules use unidirectional MVI:
+
+`Composable → Intent → ViewModel → use case → repository/service → State + Effect`
+
+UI launchers execute one-shot effects, while decisions and state transitions remain in ViewModels, reducers, and focused use cases. Hilt provides production dependencies and interfaces keep platform boundaries replaceable in tests.
+
+Modules:
+
+- `app`: phone Compose UI, MVI, permissions, native document storage, and received watch files.
+- `wear`: Wear Compose Material 3 UI, MVI, live charts, recording, and phone launch flow.
+- `core`: DataStore preferences, storage contracts, reducers, and reusable test fixtures.
+- `sensorservices`: foreground measurement service and linear sensor/GPS writers.
+- `WearOsLib`: coroutine-based connectivity, versioned command protocol, and Channel file transport.
+
+## Platform and toolchain
+
+- Android Gradle Plugin 9.3.1 and Gradle 9.7
+- Android compile/target SDK 37 (Android 17)
+- Java 17 and Kotlin 2.4.10
+- Jetpack Compose Material 3 and Wear Compose Material 3
+- Hilt 2.60.1
+- DataStore Preferences 1.2.1
+- Detekt 2 with formatting rules and no baselines
+
+Every Kotlin function is checked at a maximum of 40 lines. Compose functions therefore also stay below the requested 60-line ceiling.
+
+## Build and quality checks
+
+Install JDK 17 and Android SDK 37, then run:
+
+```shell
+./gradlew :app:assembleDebug :wear:assembleDebug
+./gradlew testDebugUnitTest detekt
+./gradlew :app:lintDebug :wear:lintDebug
+```
+
+Instrumentation test sources can be compiled without a device:
+
+```shell
+./gradlew :app:compileDebugAndroidTestKotlin :wear:compileDebugAndroidTestKotlin
+```
+
+Tests use Given/When/Then naming, reusable state/repository fixtures, coroutine test contexts, and Compose robots for end-to-end UI interactions.
+
+## Emulator integration tests
+
+The phone sensor test starts the real foreground measurement service, injects three accelerometer values through the emulator console, and verifies the generated CSV:
+
+```shell
+ANDROID_HOME="$HOME/Library/Android/sdk" \
+PHONE_SERIAL=emulator-5554 \
+tools/emulator/run_phone_sensor_test.sh
+```
+
+The Wear sync test sends a fixture CSV through the real Wear OS Channel API and verifies its exact bytes on the phone. Use an Android 17 Google Play phone AVD and a Wear OS 7 AVD. Pair them once with Android Studio's Pairing Assistant and complete the Wear companion flow before running:
+
+```shell
+ANDROID_HOME="$HOME/Library/Android/sdk" \
+PHONE_SERIAL=emulator-5554 \
+WEAR_SERIAL=emulator-5556 \
+tools/emulator/run_wear_sync_test.sh
+```
+
+The runner creates Android Studio's ADB forward/reverse bridge and fails immediately with pairing guidance when the watch reports no peer. Received files use app-internal storage only in debuggable builds; release builds continue to require the user-selected Storage Access Framework directory.
+
+No Firebase project, Maps key, secrets file, or external storage permission is required.
+
+## Dependencies
+
+The former Flipper, AppIntro, Material Dialogs, NumberPicker, Android About Page, LicensesDialog, Toasty, GraphView, and custom countdown modules have been removed. Their replacements are native APIs or small project-owned Compose components.
+
+[Vico](https://github.com/patrykandpatrick/vico) is retained as the sole feature-level third-party UI library because it provides a maintained, Compose-native chart model and renderer suitable for the live Wear OS plot. AndroidX, Google Play services for Wear/location, Kotlin coroutines, Hilt, and Detekt remain infrastructure dependencies.
+
+## Privacy
+
+Measurements are initiated by the user, represented by an ongoing foreground-service notification, and written locally. SensorBox does not upload measurement data or include analytics/crash-reporting SDKs.
+
+## License
+
+SensorBox is licensed under the Apache License 2.0. See [LICENSE](LICENSE).
diff --git a/docs/images/sensorbox-intro-battery.png b/docs/images/sensorbox-intro-battery.png
new file mode 100644
index 0000000..f47d59e
Binary files /dev/null and b/docs/images/sensorbox-intro-battery.png differ
diff --git a/docs/images/sensorbox-intro-lifecycle.png b/docs/images/sensorbox-intro-lifecycle.png
new file mode 100644
index 0000000..0e13ac5
Binary files /dev/null and b/docs/images/sensorbox-intro-lifecycle.png differ
diff --git a/docs/images/sensorbox-intro-policy.png b/docs/images/sensorbox-intro-policy.png
new file mode 100644
index 0000000..dd665e3
Binary files /dev/null and b/docs/images/sensorbox-intro-policy.png differ
diff --git a/docs/images/sensorbox-intro-privacy.png b/docs/images/sensorbox-intro-privacy.png
new file mode 100644
index 0000000..1d2c15a
Binary files /dev/null and b/docs/images/sensorbox-intro-privacy.png differ
diff --git a/docs/images/sensorbox-intro-storage.png b/docs/images/sensorbox-intro-storage.png
new file mode 100644
index 0000000..654b290
Binary files /dev/null and b/docs/images/sensorbox-intro-storage.png differ
diff --git a/docs/images/sensorbox-intro-welcome.png b/docs/images/sensorbox-intro-welcome.png
new file mode 100644
index 0000000..9c2b0f7
Binary files /dev/null and b/docs/images/sensorbox-intro-welcome.png differ
diff --git a/docs/images/sensorbox-phone-record.png b/docs/images/sensorbox-phone-record.png
new file mode 100644
index 0000000..2bb22be
Binary files /dev/null and b/docs/images/sensorbox-phone-record.png differ
diff --git a/docs/images/sensorbox-phone-setup.png b/docs/images/sensorbox-phone-setup.png
new file mode 100644
index 0000000..fe4c522
Binary files /dev/null and b/docs/images/sensorbox-phone-setup.png differ
diff --git a/docs/images/sensorbox-wear-live.png b/docs/images/sensorbox-wear-live.png
new file mode 100644
index 0000000..a452929
Binary files /dev/null and b/docs/images/sensorbox-wear-live.png differ
diff --git a/docs/images/sensorbox-wear.png b/docs/images/sensorbox-wear.png
new file mode 100644
index 0000000..0416ad9
Binary files /dev/null and b/docs/images/sensorbox-wear.png differ
diff --git a/tools/emulator/run_phone_sensor_test.sh b/tools/emulator/run_phone_sensor_test.sh
new file mode 100755
index 0000000..50a78e1
--- /dev/null
+++ b/tools/emulator/run_phone_sensor_test.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+repo_root="$(cd "$(dirname "$0")/../.." && pwd)"
+phone_serial="${PHONE_SERIAL:-emulator-5554}"
+adb_bin="${ANDROID_HOME:?ANDROID_HOME must point to the Android SDK}/platform-tools/adb"
+test_class="com.motionapps.sensorbox.emulator.PhoneSensorRecordingEmulatorTest"
+runner="motionapps.sensorbox.test/androidx.test.runner.AndroidJUnitRunner"
+result_file="$(mktemp -t sensorbox-phone-test.XXXXXX)"
+
+cleanup() {
+ rm -f "$result_file"
+}
+trap cleanup EXIT
+
+cd "$repo_root"
+./gradlew :app:assembleDebug :app:assembleDebugAndroidTest
+"$adb_bin" -s "$phone_serial" install -r app/build/outputs/apk/debug/app-debug.apk
+"$adb_bin" -s "$phone_serial" install -r app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk
+"$adb_bin" -s "$phone_serial" shell pm grant motionapps.sensorbox android.permission.POST_NOTIFICATIONS || true
+"$adb_bin" -s "$phone_serial" logcat -c
+
+"$adb_bin" -s "$phone_serial" shell am instrument -w -r -e class "$test_class" "$runner" >"$result_file" &
+test_pid=$!
+
+sleep 3
+for value in "1.25:2.5:9.5" "-3.0:4.25:8.75" "6.5:-1.5:7.25"; do
+ "$adb_bin" -s "$phone_serial" emu sensor set acceleration "$value"
+ sleep 1
+done
+
+wait "$test_pid"
+cat "$result_file"
+grep -q "OK (1 test)" "$result_file"
diff --git a/tools/emulator/run_wear_sync_test.sh b/tools/emulator/run_wear_sync_test.sh
new file mode 100755
index 0000000..fc1f57b
--- /dev/null
+++ b/tools/emulator/run_wear_sync_test.sh
@@ -0,0 +1,67 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+repo_root="$(cd "$(dirname "$0")/../.." && pwd)"
+phone_serial="${PHONE_SERIAL:-emulator-5554}"
+wear_serial="${WEAR_SERIAL:-emulator-5556}"
+adb_bin="${ANDROID_HOME:?ANDROID_HOME must point to the Android SDK}/platform-tools/adb"
+phone_test="com.motionapps.sensorbox.emulator.WearToPhoneSyncReceiverEmulatorTest"
+wear_test="com.motionapps.sensorbox.emulator.WearToPhoneSyncSenderEmulatorTest"
+runner="motionapps.sensorbox.test/androidx.test.runner.AndroidJUnitRunner"
+result_directory="$(mktemp -d -t sensorbox-wear-sync.XXXXXX)"
+phone_result="$result_directory/phone.txt"
+wear_result="$result_directory/wear.txt"
+
+cleanup() {
+ rm -rf "$result_directory"
+}
+trap cleanup EXIT
+
+install_apks() {
+ "$adb_bin" -s "$phone_serial" install -r app/build/outputs/apk/debug/app-debug.apk
+ "$adb_bin" -s "$phone_serial" install -r app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk
+ "$adb_bin" -s "$wear_serial" install -r wear/build/outputs/apk/debug/wear-debug.apk
+ "$adb_bin" -s "$wear_serial" install -r wear/build/outputs/apk/androidTest/debug/wear-debug-androidTest.apk
+}
+
+connect_emulators() {
+ "$adb_bin" -s "$phone_serial" forward tcp:5602 tcp:5601
+ "$adb_bin" -s "$wear_serial" reverse tcp:5601 tcp:5602
+ "$adb_bin" -s "$wear_serial" shell am broadcast \
+ -a com.google.android.gms.wearable.EMULATOR \
+ --es operation refresh-emulator-connection >/dev/null
+}
+
+require_paired_emulators() {
+ pairing_status="$("$adb_bin" -s "$wear_serial" shell am broadcast \
+ -a com.google.android.gms.wearable.EMULATOR \
+ --es operation get-pairing-status)"
+ if [[ "$pairing_status" == *"Peer:[null"* ]]; then
+ echo "Wear emulator has no paired phone. Pair both devices with Android Studio's Pairing Assistant first." >&2
+ exit 2
+ fi
+}
+
+run_receiver() {
+ "$adb_bin" -s "$phone_serial" shell am instrument -w -r \
+ -e class "$phone_test" "$runner" >"$phone_result" &
+ phone_test_pid=$!
+}
+
+cd "$repo_root"
+connect_emulators
+require_paired_emulators
+./gradlew :app:assembleDebug :app:assembleDebugAndroidTest \
+ :wear:assembleDebug :wear:assembleDebugAndroidTest
+install_apks
+run_receiver
+sleep 2
+
+"$adb_bin" -s "$wear_serial" shell am instrument -w -r \
+ -e class "$wear_test" "$runner" >"$wear_result"
+wait "$phone_test_pid"
+
+cat "$wear_result"
+cat "$phone_result"
+grep -q "OK (1 test)" "$wear_result"
+grep -q "OK (1 test)" "$phone_result"