diff --git a/AGENTS.MD b/AGENTS.MD
index a82ab8c..7ab7ccb 100644
--- a/AGENTS.MD
+++ b/AGENTS.MD
@@ -23,8 +23,8 @@ Every screen is its own module and cross-cutting concerns are in their own modul
The modules are small and have a single responsibility.
Every screen has root composable, where the viewmodel and UI meet ends.
The UI only talks to viewmodel, not repos. Repos and usecases are part of the viewmodels.
-UI is dumb and only renders the state. Root is responsible for delegating actions, events and
-native side effects - it can use other classes to do so.
+UI is dumb and only renders the state. The root composable delegates intents and performs
+native side effects. It can use other classes to do so.
## Testing
@@ -42,15 +42,36 @@ The tests should be simple and readable, ideally only one line per statement in
Run only tests, which are required and at the end of large implementation, run the whole test suite.
-## Terminology
-
-- Screen / Root Screen - a screen is a module that contains all the code for a single screen. It has its own ViewModel, UI and repos. It is responsible for rendering the state and delegating actions to the ViewModel.
-- State - a state is an immutable data class that contains all the data needed to render the UI. It is returned by the ViewModel and rendered by the UI.
-- Action - an action is a user interaction that is delegated to the ViewModel. It can be a button click, a gesture, a keyboard shortcut, etc.
-- User intent - a user intent is a high-level action that the user wants to perform. It is translated into one or more actions that are delegated to the ViewModel. Naming is based on user intent, not technical terms. For example, "Open App" is a user intent, while "LaunchActivity" is a technical term. Contract between layers can be merged with technical term.
-- Contract - defines communication between layers. Techncial terms can be used for these classes too.
-- Repo - a repo is a class that is responsible for fetching data from a data source. It can be a local database, a remote API, or any other data source. It is used by the ViewModel to fetch data and return it as a state.
-- Usecase - a usecase is a class that contains business logic and orchestrates data from multiple repos. It is used by the ViewModel to perform complex actions and return a state. It equals to a user intention to do something.
+## Glossary
+
+### Recording domain
+
+- Recording - the activity of capturing data.
+- Recording session - one start-to-stop recording operation. It may run on the phone alone or on both phone and watch.
+- Recording setup - editable choices made before recording.
+- Recording request - the immutable command created from the setup when recording starts.
+- Recording source - an independently managed data producer, such as sensors or GPS. One source may create several files.
+- Paired recording - one session coordinated across phone and watch.
+- Measurement - one device's stored output for a recording session. A metadata-only measurement is valid.
+- Measurement file - one stored data stream within a measurement.
+- Measurement metadata - facts about the whole measurement, not a user-visible recording source.
+- Sample - one timestamped observation, normally stored as one row.
+- Recording archive - the selected phone directory containing phone and synced watch measurements.
+- Sync - copying watch measurements to the phone archive without merging or removing the watch originals.
+- Sensor - a sensor exposed by the phone or watch. Sensor type names its kind, such as accelerometer.
+- Use phone and watch in domain code. Reserve Android and Wear OS for platform code.
+
+### MVI
+
+- Screen - stateless UI that renders state and sends intents.
+- Intent - something the UI asks the ViewModel to handle. Do not use Action as a synonym.
+- State - immutable data needed to render a screen.
+- Effect - a one-time operation outside state rendering, such as navigation or requesting permission.
+- ViewModel - owns state, handles intents, and coordinates domain operations.
+- Reducer - a pure state transition.
+- Contract - the state, intents, effects, and reducer used by a screen.
+- Repo - a boundary for reading or changing stored data or platform state.
+- Use case - one named domain operation or user goal.
## People
@@ -58,4 +79,4 @@ You are agent.
Me as user I am programmer, and we are building this project in collaboration.
Explain your ideas, ask questions, give suggestions and think about possible architectural improvements
based on the current state of the project. Review the code and give feedback.
-Be straight and go to the point.
\ No newline at end of file
+Be straight and go to the point.
diff --git a/CONTEXT.md b/CONTEXT.md
deleted file mode 100644
index bd96788..0000000
--- a/CONTEXT.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Recording archive
-
-The recording archive contains measurements created on the phone or copied from Wear OS.
-
-## Language
-
-**Measurement**
-
-One recording session stored as a named folder in the selected recording directory.
-
-_Avoid:_ Recording file, sensor measurement
-
-**Measurement file**
-
-One data stream inside a measurement, such as accelerometer samples or GPS coordinates.
-
-_Avoid:_ Measurement
-
-**Measurement metadata**
-
-Session-level facts stored with a measurement, including its start time, notes, alarms, duration, and sensor ranges.
-
-_Avoid:_ Sensor metadata
diff --git a/README.md b/README.md
index 61c7505..bbda53d 100644
--- a/README.md
+++ b/README.md
@@ -6,9 +6,9 @@ This is the hard-cut Android 17 generation of the project. It does not retain th
## Screenshots
-| Sensor selection | Measurement setup |
+| Sensor selection | Recording setup |
|:---:|:---:|
-|
|
|
+|
|
|
| Wear dashboard | Wear live-sensor picker |
|:---:|:---:|
@@ -20,11 +20,11 @@ This is the hard-cut Android 17 generation of the project. It does not retain th
|:---:|:---:|:---:|
|
|
|
|
-| Android lifecycle | Battery optimization | Recording folder |
+| Android lifecycle | Battery optimization | Recording archive |
|:---:|:---:|:---:|
|
|
|
|
-The introduction uses tintable vector illustrations that follow the app theme. Privacy Policy, Terms of Use, battery optimization, and folder selection are live native actions. Folder selection remains mandatory before setup can finish.
+The introduction uses tintable vector illustrations that follow the app theme. Privacy Policy, Terms of Use, battery optimization, and recording-archive selection use native Android screens. A recording archive remains mandatory before setup can finish.
All screenshots above come from deterministic Compose preview fixtures. Refresh the complete gallery on the host without an emulator or connected device:
@@ -36,11 +36,11 @@ All screenshots above come from deterministic Compose preview fixtures. Refresh
- 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.
+- Run recording 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.
+- Store phone measurements in a user-selected recording archive through the Storage Access Framework.
- Follow system/dynamic color with light, dark, and custom fallback palettes.
## Architecture
@@ -96,14 +96,14 @@ Tests use Given/When/Then naming, reusable state/repository fixtures, coroutine
## Emulator integration tests
-The phone recording tests start the real foreground measurement service, read the device sensors, control test GPS and battery state from Kotlin, and verify the generated files. Run the class directly from Android Studio or with Gradle:
+The phone recording tests start the real foreground recording service, read the device sensors, control test GPS and battery state from Kotlin, and verify the generated files. Run the class directly from Android Studio or with Gradle:
```shell
ANDROID_SERIAL=emulator-5554 ./gradlew :app:connectedDebugAndroidTest \
"-Pandroid.testInstrumentationRunnerArguments.class=com.tomasrepcik.sensorbox.emulator.PhoneSensorRecordingEmulatorTest"
```
-Standalone Wear recording tests use the watch sensors and control test GPS and battery state from Kotlin. They do not require a paired phone:
+Standalone watch recording tests use the watch sensors and control test GPS and battery state from Kotlin. They do not require a paired phone:
```shell
ANDROID_SERIAL=emulator-5554 ./gradlew :wear:connectedDebugAndroidTest \
@@ -116,7 +116,7 @@ The paired sync matrix sends CSV, JSON, text, empty, Unicode, overwrite, duplica
ANDROID_HOME="$HOME/Library/Android/sdk" tools/emulator/run_wear_sync_test.sh
```
-The runner detects one phone and one watch automatically; `PHONE_SERIAL` and `WEAR_SERIAL` remain available when several devices are connected. It builds and installs once, refreshes the ADB bridge after installation, and launches each scenario on both devices. When an emulator transport exposes its paired node but does not propagate static capabilities, the instrumentation-only repository falls back to that connected node; file transfer still uses the production Channel client and receiver. Received files use app-internal storage only in debuggable builds; release builds continue to require the user-selected Storage Access Framework directory.
+The runner detects one phone and one watch automatically; `PHONE_SERIAL` and `WEAR_SERIAL` remain available when several devices are connected. It builds and installs once, refreshes the ADB bridge after installation, and launches each scenario on both devices. When an emulator transport exposes its paired node but does not propagate static capabilities, the instrumentation-only repository falls back to that connected node; file transfer still uses the production Channel client and receiver. Received files use app-internal storage only in debuggable builds; release builds continue to require the user-selected recording archive.
No Firebase project, Maps key, secrets file, or external storage permission is required.
@@ -128,7 +128,7 @@ The former Flipper, AppIntro, Material Dialogs, NumberPicker, Android About Page
## 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.
+Recordings are started by the user and remain visible through a foreground-service notification. SensorBox writes each device's measurement locally and does not include upload, analytics, or crash-reporting SDKs.
## License
diff --git a/WearOsLib/src/main/java/com/tomasrepcik/sensorbox/wearoslib/files/GooglePlayWearFileTransferClient.kt b/WearOsLib/src/main/java/com/tomasrepcik/sensorbox/wearoslib/files/GooglePlayWearFileTransferClient.kt
index 51d06cb..e4d1a14 100644
--- a/WearOsLib/src/main/java/com/tomasrepcik/sensorbox/wearoslib/files/GooglePlayWearFileTransferClient.kt
+++ b/WearOsLib/src/main/java/com/tomasrepcik/sensorbox/wearoslib/files/GooglePlayWearFileTransferClient.kt
@@ -50,7 +50,7 @@ class GooglePlayWearFileTransferClient @Inject constructor(@ApplicationContext c
is AppResult.Success -> opened.value.use(consume)
}
val close = closeChannel(channel)
- listOf(transfer, close).combineAppResults(AppErrorCode.CONNECTIVITY, "Receive Wear file")
+ listOf(transfer, close).combineAppResults(AppErrorCode.CONNECTIVITY, "Receive watch file")
}
private suspend fun openChannel(nodeId: String, path: String): AppResult =
diff --git a/WearOsLib/src/main/java/com/tomasrepcik/sensorbox/wearoslib/protocol/WearCommand.kt b/WearOsLib/src/main/java/com/tomasrepcik/sensorbox/wearoslib/protocol/WearCommand.kt
index 6789ec0..8da1224 100644
--- a/WearOsLib/src/main/java/com/tomasrepcik/sensorbox/wearoslib/protocol/WearCommand.kt
+++ b/WearOsLib/src/main/java/com/tomasrepcik/sensorbox/wearoslib/protocol/WearCommand.kt
@@ -1,6 +1,7 @@
package com.tomasrepcik.sensorbox.wearoslib.protocol
import com.tomasrepcik.sensorbox.core.error.AppErrorCode
+import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@@ -26,7 +27,8 @@ sealed interface WearCommand {
@Serializable
data class RecordingResult(
val sessionId: String,
- val action: WearRecordingAction,
+ @SerialName("action")
+ val operation: WearRecordingOperation,
val outcome: WearRecordingOutcome,
@Serializable(with = AppErrorCodeNameSerializer::class)
val errorCode: AppErrorCode? = null,
@@ -46,7 +48,7 @@ data class WearRecordingRequest(
)
@Serializable
-enum class WearRecordingAction {
+enum class WearRecordingOperation {
START,
STOP,
}
diff --git a/WearOsLib/src/main/java/com/tomasrepcik/sensorbox/wearoslib/protocol/WearCommandCodec.kt b/WearOsLib/src/main/java/com/tomasrepcik/sensorbox/wearoslib/protocol/WearCommandCodec.kt
index 5a23440..e429f75 100644
--- a/WearOsLib/src/main/java/com/tomasrepcik/sensorbox/wearoslib/protocol/WearCommandCodec.kt
+++ b/WearOsLib/src/main/java/com/tomasrepcik/sensorbox/wearoslib/protocol/WearCommandCodec.kt
@@ -57,6 +57,7 @@ internal object AppErrorCodeNameSerializer : KSerializer {
override fun deserialize(decoder: Decoder): AppErrorCode {
val name = decoder.decodeString()
+ if (name == "MEASUREMENT") return AppErrorCode.RECORDING
return AppErrorCode.entries.firstOrNull { it.name == name }
?: error("Unknown application error code")
}
diff --git a/WearOsLib/src/test/java/com/tomasrepcik/sensorbox/wearoslib/protocol/WearCommandCodecTest.kt b/WearOsLib/src/test/java/com/tomasrepcik/sensorbox/wearoslib/protocol/WearCommandCodecTest.kt
index 496b7dc..9033127 100644
--- a/WearOsLib/src/test/java/com/tomasrepcik/sensorbox/wearoslib/protocol/WearCommandCodecTest.kt
+++ b/WearOsLib/src/test/java/com/tomasrepcik/sensorbox/wearoslib/protocol/WearCommandCodecTest.kt
@@ -40,16 +40,16 @@ class WearCommandCodecTest {
WearCommand.StopRecording("session-123", WearStopReason.LOW_BATTERY),
WearCommand.RecordingResult(
sessionId = "session-123",
- action = WearRecordingAction.START,
+ operation = WearRecordingOperation.START,
outcome = WearRecordingOutcome.SUCCEEDED,
),
WearCommand.RecordingResult(
sessionId = "session-123",
- action = WearRecordingAction.STOP,
+ operation = WearRecordingOperation.STOP,
outcome = WearRecordingOutcome.FAILED,
- errorCode = AppErrorCode.MEASUREMENT,
- errorOperation = "Stop Wear recording",
- errorMessage = "Wear recording service could not stop",
+ errorCode = AppErrorCode.RECORDING,
+ errorOperation = "Stop watch recording",
+ errorMessage = "watch recording service could not stop",
errorContext = mapOf("serviceState" to "stopping"),
failureCount = 2,
),
@@ -79,9 +79,9 @@ class WearCommandCodecTest {
fun `Given a successful result with an error When encoded Then it is rejected`() {
val invalid = WearCommand.RecordingResult(
sessionId = "session-123",
- action = WearRecordingAction.START,
+ operation = WearRecordingOperation.START,
outcome = WearRecordingOutcome.SUCCEEDED,
- errorCode = AppErrorCode.MEASUREMENT,
+ errorCode = AppErrorCode.RECORDING,
errorOperation = "Unexpected failure",
errorMessage = "A successful result cannot contain an error",
)
diff --git a/app/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/PhoneSensorRecordingEmulatorTest.kt b/app/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/PhoneSensorRecordingEmulatorTest.kt
index 1e2c19c..d930c3c 100644
--- a/app/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/PhoneSensorRecordingEmulatorTest.kt
+++ b/app/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/PhoneSensorRecordingEmulatorTest.kt
@@ -142,7 +142,7 @@ class PhoneSensorRecordingEmulatorTest {
}
@Test
- fun givenSignificantMotionWhenStoppedThenThePreparedFileCloses() {
+ fun givenSignificantMotionRecordingWhenStoppedThenThePreparedFileCloses() {
fixture.start(
RecordingScenario(
name = "PHONE_SIGNIFICANT_MOTION_FAILURE_TEST",
diff --git a/app/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/PhoneWearDiscoveryEmulatorTest.kt b/app/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/PhoneWearDiscoveryEmulatorTest.kt
index ef34dc3..19d1526 100644
--- a/app/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/PhoneWearDiscoveryEmulatorTest.kt
+++ b/app/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/PhoneWearDiscoveryEmulatorTest.kt
@@ -12,7 +12,7 @@ import com.tomasrepcik.sensorbox.wearoslib.WearOsConstants.WEAR_MESSAGE_PATH
import com.tomasrepcik.sensorbox.wearoslib.connectivity.GooglePlayWearConnectionRepository
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearCommand
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearCommandCodec
-import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingAction
+import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingOperation
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingOutcome
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingRequest
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearStopReason
@@ -102,7 +102,7 @@ class PhoneWearDiscoveryEmulatorTest {
).getOrThrow()
val result = awaitCommand(commands) {
- it.sessionId == sessionId && it.action == WearRecordingAction.START
+ it.sessionId == sessionId && it.operation == WearRecordingOperation.START
}
assertEquals(WearRecordingOutcome.SUCCEEDED, result.outcome)
} finally {
@@ -147,10 +147,10 @@ class PhoneWearDiscoveryEmulatorTest {
).getOrThrow()
val result = awaitCommand(commands) {
- it.sessionId == sessionId && it.action == WearRecordingAction.START
+ it.sessionId == sessionId && it.operation == WearRecordingOperation.START
}
assertEquals(WearRecordingOutcome.FAILED, result.outcome)
- assertEquals("Validate Wear recording permissions", result.errorOperation)
+ assertEquals("Validate watch recording permissions", result.errorOperation)
assertEquals("Wear OS is missing required recording permissions", result.errorMessage)
assertTrue(
result.errorContext["missingPermissions"]
diff --git a/app/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/RecordingEmulatorFixture.kt b/app/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/RecordingEmulatorFixture.kt
index 07c7ea2..fea4258 100644
--- a/app/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/RecordingEmulatorFixture.kt
+++ b/app/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/RecordingEmulatorFixture.kt
@@ -10,9 +10,9 @@ import android.os.ParcelFileDescriptor
import androidx.core.content.ContextCompat
import androidx.test.platform.app.InstrumentationRegistry
import com.tomasrepcik.sensorbox.core.time.SystemEpochClock
-import com.tomasrepcik.sensorbox.sensorservices.intent.MeasurementIntentFactory
-import com.tomasrepcik.sensorbox.sensorservices.intent.MeasurementLaunchRequest
-import com.tomasrepcik.sensorbox.sensorservices.services.MeasurementService
+import com.tomasrepcik.sensorbox.sensorservices.intent.RecordingIntentFactory
+import com.tomasrepcik.sensorbox.sensorservices.intent.RecordingRequest
+import com.tomasrepcik.sensorbox.sensorservices.services.RecordingService
import org.json.JSONObject
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -84,7 +84,7 @@ internal class RecordingEmulatorFixture(private val context: Context) {
if (!scenario.useInternalStorage) clearDocumentStoragePermissions()
if (scenario.includesGps) prepareMockGps()
val scheduledStartMillis = SystemEpochClock.nowMillis()
- val request = MeasurementLaunchRequest(
+ val request = RecordingRequest(
sessionId = scenario.sessionId,
folderName = scenario.name,
useInternalStorage = scenario.useInternalStorage,
@@ -102,7 +102,7 @@ internal class RecordingEmulatorFixture(private val context: Context) {
activityRecognitionPeriodSeconds = scenario.activityRecognitionPeriodSeconds,
significantMotion = scenario.significantMotion,
)
- val intent = MeasurementIntentFactory(context, SystemEpochClock).create(request)
+ val intent = RecordingIntentFactory(context, SystemEpochClock).create(request)
assertIntentMatches(intent, request)
ContextCompat.startForegroundService(context, intent)
val expectedRecordedTypes = scenario.sensors.mapTo(mutableSetOf(), RecordedSensor::type).apply {
@@ -116,40 +116,40 @@ internal class RecordingEmulatorFixture(private val context: Context) {
return ActiveRecording(this, scenario, scheduledStartMillis, expectedRecordedTypes)
}
- private fun assertIntentMatches(intent: Intent, request: MeasurementLaunchRequest) {
- assertEquals(request.sessionId, intent.getStringExtra(MeasurementService.SESSION_ID))
- assertEquals(request.folderName, intent.getStringExtra(MeasurementService.FOLDER_NAME))
- assertEquals(request.useInternalStorage, intent.getBooleanExtra(MeasurementService.INTERNAL_STORAGE, false))
- val sensorIds = intent.getIntArrayExtra(MeasurementService.ANDROID_SENSORS) ?: intArrayOf()
+ private fun assertIntentMatches(intent: Intent, request: RecordingRequest) {
+ assertEquals(request.sessionId, intent.getStringExtra(RecordingService.SESSION_ID))
+ assertEquals(request.folderName, intent.getStringExtra(RecordingService.FOLDER_NAME))
+ assertEquals(request.useInternalStorage, intent.getBooleanExtra(RecordingService.INTERNAL_STORAGE, false))
+ val sensorIds = intent.getIntArrayExtra(RecordingService.ANDROID_SENSORS) ?: intArrayOf()
assertEquals(request.sensorIds, sensorIds.toSet())
assertEquals(
request.sensorSamplingPeriod,
- intent.getIntExtra(MeasurementService.ANDROID_SENSORS_SPEED, Int.MIN_VALUE),
+ intent.getIntExtra(RecordingService.ANDROID_SENSORS_SPEED, Int.MIN_VALUE),
)
- assertEquals(request.includesGps, intent.getBooleanExtra(MeasurementService.GPS, false))
- assertEquals(request.stopOnLowBattery, intent.getBooleanExtra(MeasurementService.STOP_ON_LOW_BATTERY, false))
- assertEquals(request.useWakeLock, intent.getBooleanExtra(MeasurementService.USE_WAKE_LOCK, false))
- assertEquals(request.gpsIntervalSeconds, intent.getIntExtra(MeasurementService.GPS_INTERVAL_SECONDS, -1))
- assertEquals(request.gpsMinDistanceMeters, intent.getIntExtra(MeasurementService.GPS_DISTANCE_METERS, -1))
- assertEquals(request.durationMillis, intent.getLongExtra(MeasurementService.DURATION_MILLIS, -1))
- assertEquals(request.notes, intent.getStringArrayListExtra(MeasurementService.NOTES).orEmpty())
+ assertEquals(request.includesGps, intent.getBooleanExtra(RecordingService.GPS, false))
+ assertEquals(request.stopOnLowBattery, intent.getBooleanExtra(RecordingService.STOP_ON_LOW_BATTERY, false))
+ assertEquals(request.useWakeLock, intent.getBooleanExtra(RecordingService.USE_WAKE_LOCK, false))
+ assertEquals(request.gpsIntervalSeconds, intent.getIntExtra(RecordingService.GPS_INTERVAL_SECONDS, -1))
+ assertEquals(request.gpsMinDistanceMeters, intent.getIntExtra(RecordingService.GPS_DISTANCE_METERS, -1))
+ assertEquals(request.durationMillis, intent.getLongExtra(RecordingService.DURATION_MILLIS, -1))
+ assertEquals(request.notes, intent.getStringArrayListExtra(RecordingService.NOTES).orEmpty())
assertEquals(
request.alarmOffsetsSeconds,
- (intent.getIntArrayExtra(MeasurementService.ALARM_OFFSETS_SECONDS) ?: intArrayOf()).toList(),
+ (intent.getIntArrayExtra(RecordingService.ALARM_OFFSETS_SECONDS) ?: intArrayOf()).toList(),
)
assertEquals(
request.activityRecognition,
- intent.getBooleanExtra(MeasurementService.ACTIVITY_RECOGNITION, false),
+ intent.getBooleanExtra(RecordingService.ACTIVITY_RECOGNITION, false),
)
assertEquals(
request.activityRecognitionPeriodSeconds,
- intent.getIntExtra(MeasurementService.ACTIVITY_RECOGNITION_PERIOD_SECONDS, -1),
+ intent.getIntExtra(RecordingService.ACTIVITY_RECOGNITION_PERIOD_SECONDS, -1),
)
- assertEquals(request.significantMotion, intent.getBooleanExtra(MeasurementService.SIGNIFICANT_MOTION, false))
+ assertEquals(request.significantMotion, intent.getBooleanExtra(RecordingService.SIGNIFICANT_MOTION, false))
}
fun stopAnyRecording() {
- context.startService(serviceIntent(MeasurementService.ACTION_STOP))
+ context.startService(serviceIntent(RecordingService.ACTION_STOP_RECORDING))
}
private fun grantRuntimePermissions() {
@@ -210,9 +210,9 @@ internal class RecordingEmulatorFixture(private val context: Context) {
.use { it.readText() }
private fun annotate(text: String) {
- context.startService(serviceIntent(MeasurementService.ACTION_ANNOTATE).apply {
- putExtra(MeasurementService.ANNOTATION_TIME, SystemEpochClock.nowMillis())
- putExtra(MeasurementService.ANNOTATION_TEXT, text)
+ context.startService(serviceIntent(RecordingService.ACTION_ANNOTATE).apply {
+ putExtra(RecordingService.ANNOTATION_TIME, SystemEpochClock.nowMillis())
+ putExtra(RecordingService.ANNOTATION_TEXT, text)
})
}
@@ -263,7 +263,7 @@ internal class RecordingEmulatorFixture(private val context: Context) {
private fun measurementDirectory(measurementName: String): File =
File(context.filesDir, "SensorBox/$measurementName")
- private fun serviceIntent(actionName: String): Intent = Intent(context, MeasurementService::class.java).apply {
+ private fun serviceIntent(actionName: String): Intent = Intent(context, RecordingService::class.java).apply {
action = actionName
}
diff --git a/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingScreenRobot.kt b/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingScreenRobot.kt
index 20d81b3..e7d21ac 100644
--- a/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingScreenRobot.kt
+++ b/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingScreenRobot.kt
@@ -17,10 +17,10 @@ class OnboardingScreenRobot(private val rule: ComposeContentTestRule) {
var lastIntent: OnboardingIntent? = null
private set
- fun givenInteractiveOnboarding(page: Int = 0, storagePath: String? = null) = apply {
+ fun givenInteractiveOnboarding(page: Int = 0, recordingArchivePath: String? = null) = apply {
rule.setContent {
var state by remember {
- mutableStateOf(OnboardingState(page = page, storagePath = storagePath))
+ mutableStateOf(OnboardingState(page = page, recordingArchivePath = recordingArchivePath))
}
SensorBoxTheme {
OnboardingScreen(state) { intent ->
@@ -57,7 +57,7 @@ class OnboardingScreenRobot(private val rule: ComposeContentTestRule) {
}
fun whenChooseFolderIsTapped() = apply {
- rule.onNodeWithText("Choose folder").performClick()
+ rule.onNodeWithText("Choose archive").performClick()
}
fun whenFinishIsTapped() = apply {
diff --git a/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingScreenTest.kt b/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingScreenTest.kt
index daaf4c2..fe75630 100644
--- a/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingScreenTest.kt
+++ b/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingScreenTest.kt
@@ -22,16 +22,16 @@ class OnboardingScreenTest {
robot.whenNextIsTapped().thenPageIsVisible("Android may pause recordings").whenNextIsTapped()
robot.thenPageIsVisible("Allow reliable background work").whenBatterySettingsIsTapped()
assertEquals(OnboardingIntent.RequestBatteryOptimizationExemption, robot.lastIntent)
- robot.whenNextIsTapped().thenPageIsVisible("Choose a recording folder")
+ robot.whenNextIsTapped().thenPageIsVisible("Choose a recording archive")
robot.thenFinishIsDisabled().whenChooseFolderIsTapped()
- assertEquals(OnboardingIntent.ChooseStorage, robot.lastIntent)
+ assertEquals(OnboardingIntent.ChooseRecordingArchive, robot.lastIntent)
}
@Test
fun givenSelectedFolderWhenFinishingThenCompleteIntroductionIntentIsSent() {
val robot = OnboardingScreenRobot(composeRule).givenInteractiveOnboarding(
page = 5,
- storagePath = "Documents/SensorBox",
+ recordingArchivePath = "Documents/SensorBox",
)
robot.thenFinishIsEnabled().whenFinishIsTapped()
diff --git a/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/RecordScreenRobot.kt b/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/RecordScreenRobot.kt
index 35162a9..53c1782 100644
--- a/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/RecordScreenRobot.kt
+++ b/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/RecordScreenRobot.kt
@@ -13,7 +13,7 @@ class RecordScreenRobot(private val rule: ComposeContentTestRule) {
fun givenRecordScreen(
selected: Boolean = false,
gpsSelected: Boolean = false,
- wearConnected: Boolean = false,
+ watchConnected: Boolean = false,
onIntent: (RecordingIntent) -> Unit = {},
) = apply {
rule.setContent {
@@ -23,13 +23,13 @@ class RecordScreenRobot(private val rule: ComposeContentTestRule) {
sensors = listOf(SensorDescriptor(1, "Accelerometer", "Fixture")),
selectedSensorIds = if (selected) setOf(1) else emptySet(),
includesGps = gpsSelected,
- isWearConnected = wearConnected,
- wearSensors = if (wearConnected) {
+ isWatchConnected = watchConnected,
+ watchSensors = if (watchConnected) {
listOf(SensorDescriptor(1, "Wear Accelerometer", "Fixture"))
} else {
emptyList()
},
- storagePath = "Fixture/SensorBox",
+ recordingArchivePath = "Fixture/SensorBox",
),
onIntent = onIntent,
)
@@ -58,7 +58,7 @@ class RecordScreenRobot(private val rule: ComposeContentTestRule) {
rule.onNodeWithText("Continue").performClick()
}
- fun thenRecordingActionIsVisible() = apply {
+ fun thenRecordingButtonIsVisible() = apply {
rule.onNodeWithText("Continue").assertIsDisplayed()
}
}
diff --git a/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/RecordScreenTest.kt b/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/RecordScreenTest.kt
index 4af5b7b..75c3bd3 100644
--- a/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/RecordScreenTest.kt
+++ b/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/RecordScreenTest.kt
@@ -14,7 +14,7 @@ class RecordScreenTest {
var actualIntent: RecordingIntent? = null
RecordScreenRobot(composeRule)
.givenRecordScreen { actualIntent = it }
- .thenRecordingActionIsVisible()
+ .thenRecordingButtonIsVisible()
.whenAccelerometerIsTapped()
composeRule.runOnIdle {
@@ -42,7 +42,7 @@ class RecordScreenTest {
.whenContinueIsTapped()
composeRule.runOnIdle {
- assertEquals(RecordingIntent.OpenMeasurementSetup, actualIntent)
+ assertEquals(RecordingIntent.OpenRecordingSetup, actualIntent)
}
}
@@ -62,12 +62,12 @@ class RecordScreenTest {
fun givenWearSensorWhenInfoIsTappedThenWearDetailsIntentIsSent() {
var actualIntent: RecordingIntent? = null
RecordScreenRobot(composeRule)
- .givenRecordScreen(wearConnected = true) { actualIntent = it }
+ .givenRecordScreen(watchConnected = true) { actualIntent = it }
.whenWearAccelerometerInfoIsTapped()
composeRule.runOnIdle {
assertEquals(
- RecordingIntent.OpenSensorDetails(1, RecordingSensorSource.WEAR),
+ RecordingIntent.OpenSensorDetails(1, RecordingDevice.WATCH),
actualIntent,
)
}
@@ -81,7 +81,7 @@ class RecordScreenTest {
.whenContinueIsTapped()
composeRule.runOnIdle {
- assertEquals(RecordingIntent.OpenMeasurementSetup, actualIntent)
+ assertEquals(RecordingIntent.OpenRecordingSetup, actualIntent)
}
}
}
diff --git a/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/MeasurementSetupScreenRobot.kt b/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/RecordingSetupScreenRobot.kt
similarity index 66%
rename from app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/MeasurementSetupScreenRobot.kt
rename to app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/RecordingSetupScreenRobot.kt
index 5631e72..25df53d 100644
--- a/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/MeasurementSetupScreenRobot.kt
+++ b/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/RecordingSetupScreenRobot.kt
@@ -7,15 +7,15 @@ import androidx.compose.ui.test.performClick
import com.tomasrepcik.sensorbox.domain.sensors.SensorDescriptor
import com.tomasrepcik.sensorbox.ui.theme.SensorBoxTheme
-class MeasurementSetupScreenRobot(private val rule: ComposeContentTestRule) {
- fun givenMeasurementSetup(onIntent: (RecordingIntent) -> Unit = {}) = apply {
+class RecordingSetupScreenRobot(private val rule: ComposeContentTestRule) {
+ fun givenRecordingSetup(onIntent: (RecordingIntent) -> Unit = {}) = apply {
rule.setContent {
SensorBoxTheme {
- MeasurementSetupScreen(
+ RecordingSetupScreen(
state = RecordingState(
sensors = listOf(SensorDescriptor(1, "Accelerometer", "Fixture")),
selectedSensorIds = setOf(1),
- storagePath = "Fixture/SensorBox",
+ recordingArchivePath = "Fixture/SensorBox",
),
onIntent = onIntent,
)
@@ -23,12 +23,12 @@ class MeasurementSetupScreenRobot(private val rule: ComposeContentTestRule) {
}
}
- fun whenStartMeasurementIsTapped() = apply {
- rule.onNodeWithText("Start measurement").performClick()
+ fun whenStartRecordingIsTapped() = apply {
+ rule.onNodeWithText("Start recording").performClick()
}
fun thenFolderAndSettingsAreVisible() = apply {
- rule.onNodeWithText("Recording folder").assertIsDisplayed()
+ rule.onNodeWithText("Recording archive").assertIsDisplayed()
rule.onNodeWithText("Sensor sampling").assertIsDisplayed()
}
}
diff --git a/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/MeasurementSetupScreenTest.kt b/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/RecordingSetupScreenTest.kt
similarity index 73%
rename from app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/MeasurementSetupScreenTest.kt
rename to app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/RecordingSetupScreenTest.kt
index 0ad3245..fb284e8 100644
--- a/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/MeasurementSetupScreenTest.kt
+++ b/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/RecordingSetupScreenTest.kt
@@ -8,20 +8,20 @@ import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
-class MeasurementSetupScreenTest {
+class RecordingSetupScreenTest {
@get:Rule
val composeRule = createComposeRule()
@Test
- fun givenConfiguredSetupWhenStartIsTappedThenMeasurementIntentIsSent() {
+ fun givenConfiguredSetupWhenStartIsTappedThenRecordingRequestIsSent() {
var actualIntent: RecordingIntent? = null
- MeasurementSetupScreenRobot(composeRule)
- .givenMeasurementSetup { actualIntent = it }
+ RecordingSetupScreenRobot(composeRule)
+ .givenRecordingSetup { actualIntent = it }
.thenFolderAndSettingsAreVisible()
- .whenStartMeasurementIsTapped()
+ .whenStartRecordingIsTapped()
composeRule.runOnIdle {
- assertEquals(RecordingIntent.StartMeasurement, actualIntent)
+ assertEquals(RecordingIntent.StartRecording, actualIntent)
}
}
@@ -29,7 +29,7 @@ class MeasurementSetupScreenTest {
fun givenWearPermissionFailureThenActionableWatchGuidanceIsDisplayed() {
composeRule.setContent {
SensorBoxTheme {
- RecordingMessageText(RecordingMessage.WEAR_PERMISSION_REQUIRED)
+ RecordingMessageText(RecordingMessage.WATCH_PERMISSION_REQUIRED)
}
}
@@ -43,10 +43,10 @@ class MeasurementSetupScreenTest {
fun givenRecordingIsStartingThenProgressLabelIsDisplayed() {
composeRule.setContent {
SensorBoxTheme {
- MeasurementSetupScreen(
+ RecordingSetupScreen(
state = RecordingState(
selectedSensorIds = setOf(1),
- storagePath = "Fixture/SensorBox",
+ recordingArchivePath = "Fixture/SensorBox",
isStarting = true,
),
onIntent = { },
diff --git a/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/SensorDetailsScreenTest.kt b/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/SensorDetailsScreenTest.kt
index 39e05cd..38477d7 100644
--- a/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/SensorDetailsScreenTest.kt
+++ b/app/src/androidTest/java/com/tomasrepcik/sensorbox/presentation/main/SensorDetailsScreenTest.kt
@@ -22,7 +22,7 @@ class SensorDetailsScreenTest {
SensorBoxTheme {
SensorDetailsScreen(
state = RecordingState(
- wearSensors = listOf(
+ watchSensors = listOf(
SensorDescriptor(
type = 1,
name = "Wear Accelerometer",
@@ -39,7 +39,7 @@ class SensorDetailsScreenTest {
),
),
detailsSensorType = 1,
- detailsSensorSource = RecordingSensorSource.WEAR,
+ detailsDevice = RecordingDevice.WATCH,
),
onBack = {},
onPreview = {},
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/MsgListener.kt b/app/src/main/java/com/tomasrepcik/sensorbox/MsgListener.kt
index e04b661..a13409b 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/MsgListener.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/MsgListener.kt
@@ -3,8 +3,8 @@ package com.tomasrepcik.sensorbox
import com.google.android.gms.wearable.ChannelClient
import com.google.android.gms.wearable.MessageEvent
import com.google.android.gms.wearable.WearableListenerService
-import com.tomasrepcik.sensorbox.domain.paired.PhoneWearMessageDispatcher
-import com.tomasrepcik.sensorbox.domain.sync.ReceiveWearFileUseCase
+import com.tomasrepcik.sensorbox.domain.paired.PhoneWatchMessageDispatcher
+import com.tomasrepcik.sensorbox.domain.sync.ReceiveWatchFileUseCase
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -16,10 +16,10 @@ import javax.inject.Inject
@AndroidEntryPoint
class MsgListener : WearableListenerService() {
@Inject
- lateinit var messageDispatcher: PhoneWearMessageDispatcher
+ lateinit var messageDispatcher: PhoneWatchMessageDispatcher
@Inject
- lateinit var receiveWearFile: ReceiveWearFileUseCase
+ lateinit var receiveWearFile: ReceiveWatchFileUseCase
private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/activities/MainActivity.kt b/app/src/main/java/com/tomasrepcik/sensorbox/activities/MainActivity.kt
index 29c8c65..f450cb9 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/activities/MainActivity.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/activities/MainActivity.kt
@@ -46,11 +46,11 @@ class MainActivity : ComponentActivity() {
private val recordingViewModel: RecordingViewModel by viewModels()
private val measurementBrowserViewModel: MeasurementBrowserViewModel by viewModels()
private val settingsViewModel: SettingsViewModel by viewModels()
- private var storageRequestOwner = StorageRequestOwner.RECORDING
+ private var recordingArchiveRequestOwner = RecordingArchiveRequestOwner.RECORDING
private val directoryPicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
- when (storageRequestOwner) {
- StorageRequestOwner.ONBOARDING -> onboardingViewModel.handleStorageResult(it.data)
- StorageRequestOwner.RECORDING -> recordingViewModel.handleStorageResult(it.data)
+ when (recordingArchiveRequestOwner) {
+ RecordingArchiveRequestOwner.ONBOARDING -> onboardingViewModel.handleRecordingArchiveResult(it.data)
+ RecordingArchiveRequestOwner.RECORDING -> recordingViewModel.handleRecordingArchiveResult(it.data)
}
}
private val permissionRequest = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
@@ -100,7 +100,7 @@ class MainActivity : ComponentActivity() {
private fun handleOnboardingEffect(effect: OnboardingEffect) {
when (effect) {
- OnboardingEffect.PickStorageDirectory -> openStoragePicker(StorageRequestOwner.ONBOARDING)
+ OnboardingEffect.PickRecordingArchive -> openRecordingArchivePicker(RecordingArchiveRequestOwner.ONBOARDING)
OnboardingEffect.OpenPrivacyPolicy -> openWebPage(getString(R.string.link_privacy_policy))
OnboardingEffect.OpenTermsOfUse -> openWebPage(getString(R.string.link_terms))
OnboardingEffect.RequestBatteryOptimizationExemption -> requestBatteryOptimizationExemption()
@@ -110,7 +110,7 @@ class MainActivity : ComponentActivity() {
private fun handleRecordingEffect(effect: RecordingEffect) {
when (effect) {
- RecordingEffect.PickStorageDirectory -> openStoragePicker(StorageRequestOwner.RECORDING)
+ RecordingEffect.PickRecordingArchive -> openRecordingArchivePicker(RecordingArchiveRequestOwner.RECORDING)
is RecordingEffect.Navigate -> mainViewModel.navigate(effect.route)
@@ -127,7 +127,7 @@ class MainActivity : ComponentActivity() {
SettingsEffect.ShareDiagnosticsFile -> shareDiagnosticsFile()
is SettingsEffect.CopyDiagnosticsText -> copyDiagnostics(effect.text)
SettingsEffect.DiagnosticsCleared -> showToast(R.string.diagnostics_cleared)
- is SettingsEffect.DiagnosticsFailed -> showToast(R.string.diagnostics_action_failed)
+ is SettingsEffect.DiagnosticsFailed -> showToast(R.string.diagnostics_operation_failed)
is SettingsEffect.Navigate -> mainViewModel.navigate(effect.route)
}
}
@@ -138,10 +138,10 @@ class MainActivity : ComponentActivity() {
}
}
- private fun openStoragePicker(owner: StorageRequestOwner) {
- storageRequestOwner = owner
- appResult(AppErrorCode.EXTERNAL_ACTION, "Open storage picker") {
- directoryPicker.launch(storageIntent())
+ private fun openRecordingArchivePicker(owner: RecordingArchiveRequestOwner) {
+ recordingArchiveRequestOwner = owner
+ appResult(AppErrorCode.EXTERNAL_ACTION, "Open recording archive picker") {
+ directoryPicker.launch(recordingArchiveIntent())
}
}
@@ -215,7 +215,7 @@ class MainActivity : ComponentActivity() {
)
}.fold(
onSuccess = { showToast(R.string.diagnostics_copied) },
- onFailure = { showToast(R.string.diagnostics_action_failed) },
+ onFailure = { showToast(R.string.diagnostics_operation_failed) },
)
}
@@ -223,13 +223,13 @@ class MainActivity : ComponentActivity() {
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}
- private fun storageIntent() = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
+ private fun recordingArchiveIntent() = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
}
- private enum class StorageRequestOwner {
+ private enum class RecordingArchiveRequestOwner {
ONBOARDING,
RECORDING,
}
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/data/measurements/AndroidMeasurementRepository.kt b/app/src/main/java/com/tomasrepcik/sensorbox/data/measurements/AndroidMeasurementRepository.kt
index fc2dab6..0476208 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/data/measurements/AndroidMeasurementRepository.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/data/measurements/AndroidMeasurementRepository.kt
@@ -81,10 +81,10 @@ class AndroidMeasurementRepository @Inject constructor(@ApplicationContext priva
val permission = context.contentResolver.persistedUriPermissions
.filter { it.isReadPermission }
.maxByOrNull { it.persistedTime }
- ?: throw IllegalStateException("Recording directory is not configured")
+ ?: throw IllegalStateException("Recording archive is not configured")
return DocumentFile.fromTreeUri(context, permission.uri)
?.takeIf(DocumentFile::isDirectory)
- ?: throw IllegalStateException("Recording directory is unavailable")
+ ?: throw IllegalStateException("Recording archive is unavailable")
}
private fun measurementDirectory(measurementId: String): DocumentFile = selectedMeasurementsDirectory().listFiles()
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/di/RecordingModule.kt b/app/src/main/java/com/tomasrepcik/sensorbox/di/RecordingModule.kt
index 6b5aee2..7e00246 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/di/RecordingModule.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/di/RecordingModule.kt
@@ -1,15 +1,15 @@
package com.tomasrepcik.sensorbox.di
import com.tomasrepcik.sensorbox.data.measurements.AndroidMeasurementRepository
-import com.tomasrepcik.sensorbox.domain.measurement.AndroidPhoneRecordingController
-import com.tomasrepcik.sensorbox.domain.measurement.DocumentStorageGateway
-import com.tomasrepcik.sensorbox.domain.measurement.DocumentStorageUseCase
-import com.tomasrepcik.sensorbox.domain.measurement.MeasurementControlUseCase
-import com.tomasrepcik.sensorbox.domain.measurement.MeasurementPermissionUseCase
-import com.tomasrepcik.sensorbox.domain.measurement.MeasurementPermissionsUseCase
-import com.tomasrepcik.sensorbox.domain.measurement.PhoneRecordingController
-import com.tomasrepcik.sensorbox.domain.measurement.RecordingControlUseCase
import com.tomasrepcik.sensorbox.domain.measurements.MeasurementRepository
+import com.tomasrepcik.sensorbox.domain.recording.AndroidPhoneRecordingController
+import com.tomasrepcik.sensorbox.domain.recording.DefaultRecordingControlUseCase
+import com.tomasrepcik.sensorbox.domain.recording.DocumentRecordingArchiveRepository
+import com.tomasrepcik.sensorbox.domain.recording.PhoneRecordingController
+import com.tomasrepcik.sensorbox.domain.recording.RecordingArchiveRepository
+import com.tomasrepcik.sensorbox.domain.recording.RecordingControlUseCase
+import com.tomasrepcik.sensorbox.domain.recording.RecordingPermissionUseCase
+import com.tomasrepcik.sensorbox.domain.recording.RecordingPermissionsUseCase
import com.tomasrepcik.sensorbox.domain.sensors.AvailableSensorsUseCase
import com.tomasrepcik.sensorbox.domain.sensors.GetAvailableSensorsUseCase
import com.tomasrepcik.sensorbox.presentation.main.AndroidElapsedRealtimeClock
@@ -28,18 +28,20 @@ abstract class RecordingModule {
): PhoneRecordingController
@Binds
- abstract fun bindDocumentStorageGateway(implementation: DocumentStorageUseCase): DocumentStorageGateway
+ abstract fun bindRecordingArchiveRepository(
+ implementation: DocumentRecordingArchiveRepository,
+ ): RecordingArchiveRepository
@Binds
abstract fun bindAvailableSensorsUseCase(implementation: GetAvailableSensorsUseCase): AvailableSensorsUseCase
@Binds
- abstract fun bindMeasurementPermissionsUseCase(
- implementation: MeasurementPermissionUseCase,
- ): MeasurementPermissionsUseCase
+ abstract fun bindRecordingPermissionsUseCase(
+ implementation: RecordingPermissionUseCase,
+ ): RecordingPermissionsUseCase
@Binds
- abstract fun bindRecordingControlUseCase(implementation: MeasurementControlUseCase): RecordingControlUseCase
+ abstract fun bindRecordingControlUseCase(implementation: DefaultRecordingControlUseCase): RecordingControlUseCase
@Binds
abstract fun bindMeasurementRepository(implementation: AndroidMeasurementRepository): MeasurementRepository
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/DocumentStorageUseCase.kt b/app/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/DocumentStorageUseCase.kt
deleted file mode 100644
index 7bcc99f..0000000
--- a/app/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/DocumentStorageUseCase.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.tomasrepcik.sensorbox.domain.measurement
-
-import android.content.Intent
-import com.tomasrepcik.sensorbox.core.error.AppResult
-import com.tomasrepcik.sensorbox.core.storage.DocumentStorage
-import javax.inject.Inject
-
-interface DocumentStorageGateway {
- fun hasStorage(): AppResult
-
- fun displayPath(): AppResult
-
- fun persist(resultIntent: Intent): AppResult
-}
-
-class DocumentStorageUseCase @Inject constructor(private val storage: DocumentStorage) : DocumentStorageGateway {
- override fun hasStorage(): AppResult = storage.hasConfiguredDirectory()
-
- override fun displayPath(): AppResult = storage.displayPath()
-
- override fun persist(resultIntent: Intent): AppResult = storage.persistRootAccess(resultIntent)
-}
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PairedRecordingCoordinator.kt b/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PairedRecordingCoordinator.kt
index e502643..f6c4b40 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PairedRecordingCoordinator.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PairedRecordingCoordinator.kt
@@ -6,14 +6,14 @@ import com.tomasrepcik.sensorbox.core.error.AppResult
import com.tomasrepcik.sensorbox.core.error.DiagnosticLogger
import com.tomasrepcik.sensorbox.core.error.combineAppResults
import com.tomasrepcik.sensorbox.core.error.toDiagnosticEvent
-import com.tomasrepcik.sensorbox.domain.measurement.MeasurementRequest
-import com.tomasrepcik.sensorbox.domain.measurement.PhoneRecordingController
+import com.tomasrepcik.sensorbox.domain.recording.PhoneRecordingController
+import com.tomasrepcik.sensorbox.domain.recording.RecordingSetup
import com.tomasrepcik.sensorbox.wearoslib.WearOsConstants.WEAR_APP_CAPABILITY
import com.tomasrepcik.sensorbox.wearoslib.WearOsConstants.WEAR_MESSAGE_PATH
import com.tomasrepcik.sensorbox.wearoslib.protocol.SendWearCommandUseCase
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearCommand
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearCommandCodec
-import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingAction
+import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingOperation
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearStopReason
import com.tomasrepcik.sensorbox.wearoslib.protocol.recordingSessionId
import kotlinx.coroutines.withTimeoutOrNull
@@ -25,13 +25,13 @@ import javax.inject.Singleton
class PairedRecordingCoordinator @Inject constructor(
private val phoneRecording: PhoneRecordingController,
private val sendCommand: SendWearCommandUseCase,
- private val watchResults: WearRecordingResultInbox,
+ private val watchResults: WatchRecordingResultInbox,
private val diagnosticLogger: DiagnosticLogger,
) {
private var state: PairedRecordingState = PairedRecordingState.Idle
@Suppress("ReturnCount")
- suspend fun start(request: MeasurementRequest): AppResult {
+ suspend fun start(request: RecordingSetup): AppResult {
if (state !is PairedRecordingState.Idle) return conflict("Start recording")
val sessionId = UUID.randomUUID().toString()
@@ -49,14 +49,14 @@ class PairedRecordingCoordinator @Inject constructor(
}
val watchStart = if (includesWatch) {
- exchange(startedPhone.toWatchStartCommand(request), WearRecordingAction.START)
+ exchange(startedPhone.toWatchStartCommand(request), WearRecordingOperation.START)
} else {
AppResult.success(Unit)
}
if (state !== starting) return AppResult.success(Unit)
if (watchStart is AppResult.Failure) {
- if (watchStart.error.wasWearCommandSent()) {
+ if (watchStart.error.wasWatchCommandSent()) {
record(watchStart.error)
state = PairedRecordingState.Recording(sessionId, includesWatch)
return AppResult.success(Unit)
@@ -103,20 +103,20 @@ class PairedRecordingCoordinator @Inject constructor(
val watchStop = stopWatch(sessionId, includesWatch, reason)
state = PairedRecordingState.Idle
return listOf(phoneStop, watchStop)
- .combineAppResults(AppErrorCode.MEASUREMENT, "Stop phone and Wear recording")
+ .combineAppResults(AppErrorCode.RECORDING, "Stop phone and watch recording")
.onFailure(::record)
}
private suspend fun stopWatch(sessionId: String, includesWatch: Boolean, reason: WearStopReason): AppResult =
if (includesWatch) {
- exchange(WearCommand.StopRecording(sessionId, reason), WearRecordingAction.STOP)
+ exchange(WearCommand.StopRecording(sessionId, reason), WearRecordingOperation.STOP)
} else {
AppResult.success(Unit)
}
- private suspend fun exchange(command: WearCommand, expectedAction: WearRecordingAction): AppResult {
+ private suspend fun exchange(command: WearCommand, expectedOperation: WearRecordingOperation): AppResult {
val sessionId = checkNotNull(command.recordingSessionId())
- watchResults.clear(sessionId, expectedAction)
+ watchResults.clear(sessionId, expectedOperation)
var lastSendError: AppError? = null
var commandWasSent = false
@@ -128,7 +128,7 @@ class PairedRecordingCoordinator @Inject constructor(
commandWasSent = true
lastSendError = null
val result = withTimeoutOrNull(RESULT_TIMEOUT_MILLIS / ATTEMPT_COUNT) {
- watchResults.await(sessionId, expectedAction)
+ watchResults.await(sessionId, expectedOperation)
}
if (result != null) return result.toAppResult(retryCount)
}
@@ -140,10 +140,10 @@ class PairedRecordingCoordinator @Inject constructor(
return AppResult.failure(
AppError(
code = AppErrorCode.TIMEOUT,
- operation = expectedAction.timeoutOperation(),
- diagnosticMessage = "Wear $expectedAction result timed out",
+ operation = expectedOperation.timeoutOperation(),
+ diagnosticMessage = "watch $expectedOperation result timed out",
context = mapOf(
- "source" to "wear",
+ "source" to "watch",
"sessionId" to sessionId,
"retryCount" to RETRY_COUNT.toString(),
"protocolVersion" to WearCommandCodec.PROTOCOL_VERSION.toString(),
@@ -154,7 +154,7 @@ class PairedRecordingCoordinator @Inject constructor(
)
}
- private fun AppError.wasWearCommandSent(): Boolean =
+ private fun AppError.wasWatchCommandSent(): Boolean =
code == AppErrorCode.TIMEOUT && context["commandWasSent"] == true.toString()
private fun conflict(operation: String): AppResult =
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PairedRecordingModel.kt b/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PairedRecordingModel.kt
index c5514a4..26a8e22 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PairedRecordingModel.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PairedRecordingModel.kt
@@ -3,12 +3,12 @@ package com.tomasrepcik.sensorbox.domain.paired
import com.tomasrepcik.sensorbox.core.error.AppError
import com.tomasrepcik.sensorbox.core.error.AppErrorCode
import com.tomasrepcik.sensorbox.core.error.AppResult
-import com.tomasrepcik.sensorbox.domain.measurement.MeasurementRequest
-import com.tomasrepcik.sensorbox.domain.measurement.StartedPhoneRecording
-import com.tomasrepcik.sensorbox.domain.measurement.toWearRecordingRequest
+import com.tomasrepcik.sensorbox.domain.recording.RecordingSetup
+import com.tomasrepcik.sensorbox.domain.recording.StartedPhoneRecording
+import com.tomasrepcik.sensorbox.domain.recording.toWatchRecordingRequest
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearCommand
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearCommandCodec
-import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingAction
+import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingOperation
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingOutcome
internal sealed interface PairedRecordingState {
@@ -30,11 +30,11 @@ internal fun PairedRecordingState.activeRecording(): ActivePairedRecording? = wh
PairedRecordingState.Idle -> null
}
-internal fun MeasurementRequest.includesWatchRecording(): Boolean = wearSensorIds.isNotEmpty() || wearIncludesGps
+internal fun RecordingSetup.includesWatchRecording(): Boolean = watchSensorIds.isNotEmpty() || watchIncludesGps
-internal fun StartedPhoneRecording.toWatchStartCommand(request: MeasurementRequest) = WearCommand.StartRecording(
+internal fun StartedPhoneRecording.toWatchStartCommand(request: RecordingSetup) = WearCommand.StartRecording(
sessionId = sessionId,
- request = toWearRecordingRequest(request),
+ request = toWatchRecordingRequest(request),
)
internal fun WearCommand.RecordingResult.toAppResult(retryCount: Int): AppResult =
@@ -44,10 +44,10 @@ internal fun WearCommand.RecordingResult.toAppResult(retryCount: Int): AppResult
AppResult.failure(
AppError(
code = errorCode ?: AppErrorCode.UNKNOWN,
- operation = errorOperation ?: "Handle Wear $action result",
- diagnosticMessage = errorMessage ?: "Wear $action failed",
+ operation = errorOperation ?: "Handle watch $operation result",
+ diagnosticMessage = errorMessage ?: "Watch $operation failed",
context = errorContext + mapOf(
- "source" to "wear",
+ "source" to "watch",
"sessionId" to sessionId,
"retryCount" to retryCount.toString(),
"failureCount" to failureCount.toString(),
@@ -57,4 +57,4 @@ internal fun WearCommand.RecordingResult.toAppResult(retryCount: Int): AppResult
)
}
-internal fun WearRecordingAction.timeoutOperation(): String = "Await Wear $name result"
+internal fun WearRecordingOperation.timeoutOperation(): String = "Await watch $name result"
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneRecordingSessionObserver.kt b/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneRecordingSessionObserver.kt
index baa3460..4bc1be8 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneRecordingSessionObserver.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneRecordingSessionObserver.kt
@@ -2,9 +2,9 @@ package com.tomasrepcik.sensorbox.domain.paired
import com.tomasrepcik.sensorbox.core.error.DiagnosticLogger
import com.tomasrepcik.sensorbox.core.error.toDiagnosticEvent
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementSessionStore
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementStopReason
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementStopped
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionStopReason
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionStopped
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionStore
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearStopReason
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -17,7 +17,7 @@ import javax.inject.Singleton
@Singleton
class PhoneRecordingSessionObserver @Inject constructor(
- private val sessionStore: MeasurementSessionStore,
+ private val sessionStore: RecordingSessionStore,
private val pairedRecordingCoordinator: PairedRecordingCoordinator,
private val diagnosticLogger: DiagnosticLogger,
) {
@@ -31,17 +31,17 @@ class PhoneRecordingSessionObserver @Inject constructor(
}
}
- private suspend fun onStopped(event: MeasurementStopped) {
+ private suspend fun onStopped(event: RecordingSessionStopped) {
event.result.errorOrNull()?.let { error -> diagnosticLogger.record(error.toDiagnosticEvent()) }
- val reason = event.reason.toAutomaticWearReason() ?: return
+ val reason = event.reason.toAutomaticWatchReason() ?: return
pairedRecordingCoordinator.onAutomaticPhoneStop(event.sessionId, reason)
}
- private fun MeasurementStopReason.toAutomaticWearReason(): WearStopReason? = when (this) {
- MeasurementStopReason.USER_REQUEST -> null
- MeasurementStopReason.DURATION_EXPIRED -> WearStopReason.DURATION_EXPIRED
- MeasurementStopReason.LOW_BATTERY -> WearStopReason.LOW_BATTERY
- MeasurementStopReason.SOURCE_FAILURE -> WearStopReason.SOURCE_FAILURE
- MeasurementStopReason.SERVICE_DESTROYED -> WearStopReason.SERVICE_DESTROYED
+ private fun RecordingSessionStopReason.toAutomaticWatchReason(): WearStopReason? = when (this) {
+ RecordingSessionStopReason.USER_REQUEST -> null
+ RecordingSessionStopReason.DURATION_EXPIRED -> WearStopReason.DURATION_EXPIRED
+ RecordingSessionStopReason.LOW_BATTERY -> WearStopReason.LOW_BATTERY
+ RecordingSessionStopReason.SOURCE_FAILURE -> WearStopReason.SOURCE_FAILURE
+ RecordingSessionStopReason.SERVICE_DESTROYED -> WearStopReason.SERVICE_DESTROYED
}
}
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneWearCommandHandler.kt b/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneWatchCommandHandler.kt
similarity index 72%
rename from app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneWearCommandHandler.kt
rename to app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneWatchCommandHandler.kt
index 6470a02..ba04a1e 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneWearCommandHandler.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneWatchCommandHandler.kt
@@ -7,20 +7,20 @@ import com.tomasrepcik.sensorbox.core.error.AppError
import com.tomasrepcik.sensorbox.core.error.AppErrorCode
import com.tomasrepcik.sensorbox.core.error.AppResult
import com.tomasrepcik.sensorbox.core.error.appResult
-import com.tomasrepcik.sensorbox.domain.sensors.WearSensorCatalogStore
+import com.tomasrepcik.sensorbox.domain.sensors.WatchSensorCatalogStore
import com.tomasrepcik.sensorbox.wearoslib.WearOsConstants.WEAR_APP_CAPABILITY
import com.tomasrepcik.sensorbox.wearoslib.WearOsConstants.WEAR_MESSAGE_PATH
import com.tomasrepcik.sensorbox.wearoslib.protocol.SendWearCommandUseCase
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearCommand
-import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingAction
+import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingOperation
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingOutcome
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
-class PhoneWearCommandHandler @Inject constructor(
+class PhoneWatchCommandHandler @Inject constructor(
@ApplicationContext private val context: Context,
- private val wearSensorCatalog: WearSensorCatalogStore,
- private val recordingResults: WearRecordingResultInbox,
+ private val watchSensorCatalog: WatchSensorCatalogStore,
+ private val recordingResults: WatchRecordingResultInbox,
private val pairedRecordingCoordinator: PairedRecordingCoordinator,
private val sendCommand: SendWearCommandUseCase,
) {
@@ -28,7 +28,7 @@ class PhoneWearCommandHandler @Inject constructor(
WearCommand.LaunchPhone -> launchPhone()
is WearCommand.AvailableSensors -> {
- wearSensorCatalog.update(command.sensors)
+ watchSensorCatalog.update(command.sensors)
AppResult.success(Unit)
}
@@ -37,28 +37,29 @@ class PhoneWearCommandHandler @Inject constructor(
AppResult.success(Unit)
}
- is WearCommand.StopRecording -> stopFromWear(command)
+ is WearCommand.StopRecording -> stopFromWatch(command)
WearCommand.RequestAvailableSensors,
WearCommand.SyncMeasurements,
is WearCommand.StartRecording,
- -> AppResult.failure(AppError(AppErrorCode.VALIDATION, "Handle unsupported phone Wear command"))
+ -> AppResult.failure(AppError(AppErrorCode.VALIDATION, "Handle unsupported phone watch command"))
}
- private fun launchPhone(): AppResult = appResult(AppErrorCode.EXTERNAL_ACTION, "Launch phone app from Wear") {
- context.startActivity(
- Intent(context, MainActivity::class.java).apply {
- flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
- },
- )
- }
+ private fun launchPhone(): AppResult =
+ appResult(AppErrorCode.EXTERNAL_ACTION, "Launch phone app from watch") {
+ context.startActivity(
+ Intent(context, MainActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ },
+ )
+ }
- private suspend fun stopFromWear(command: WearCommand.StopRecording): AppResult {
+ private suspend fun stopFromWatch(command: WearCommand.StopRecording): AppResult {
val stopResult = pairedRecordingCoordinator.stopFromWatch(command.sessionId, command.reason)
val error = stopResult.errorOrNull()
val result = WearCommand.RecordingResult(
sessionId = command.sessionId,
- action = WearRecordingAction.STOP,
+ operation = WearRecordingOperation.STOP,
outcome = if (stopResult.isSuccess) {
WearRecordingOutcome.SUCCEEDED
} else {
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneWearMessageDispatcher.kt b/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneWatchMessageDispatcher.kt
similarity index 88%
rename from app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneWearMessageDispatcher.kt
rename to app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneWatchMessageDispatcher.kt
index 3c8fbc1..ee2537f 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneWearMessageDispatcher.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneWatchMessageDispatcher.kt
@@ -8,8 +8,8 @@ import com.tomasrepcik.sensorbox.wearoslib.WearOsConstants.PHONE_MESSAGE_PATH
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearCommandCodec
import javax.inject.Inject
-class PhoneWearMessageDispatcher @Inject constructor(
- private val commandHandler: PhoneWearCommandHandler,
+class PhoneWatchMessageDispatcher @Inject constructor(
+ private val commandHandler: PhoneWatchCommandHandler,
private val diagnosticLogger: DiagnosticLogger,
) {
suspend fun dispatch(path: String, payload: ByteArray): AppResult {
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/WearRecordingResultInbox.kt b/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/WatchRecordingResultInbox.kt
similarity index 68%
rename from app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/WearRecordingResultInbox.kt
rename to app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/WatchRecordingResultInbox.kt
index 03412d0..32c2c14 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/WearRecordingResultInbox.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/WatchRecordingResultInbox.kt
@@ -1,7 +1,7 @@
package com.tomasrepcik.sensorbox.domain.paired
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearCommand
-import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingAction
+import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingOperation
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.first
import java.util.concurrent.ConcurrentHashMap
@@ -9,7 +9,7 @@ import javax.inject.Inject
import javax.inject.Singleton
@Singleton
-class WearRecordingResultInbox @Inject constructor() {
+class WatchRecordingResultInbox @Inject constructor() {
private val results = ConcurrentHashMap()
private val updates = MutableSharedFlow(extraBufferCapacity = BUFFER_SIZE)
@@ -18,19 +18,19 @@ class WearRecordingResultInbox @Inject constructor() {
updates.tryEmit(result)
}
- fun clear(sessionId: String, action: WearRecordingAction) {
- results.remove(Key(sessionId, action))
+ fun clear(sessionId: String, operation: WearRecordingOperation) {
+ results.remove(Key(sessionId, operation))
}
- suspend fun await(sessionId: String, action: WearRecordingAction): WearCommand.RecordingResult {
- val key = Key(sessionId, action)
+ suspend fun await(sessionId: String, operation: WearRecordingOperation): WearCommand.RecordingResult {
+ val key = Key(sessionId, operation)
results[key]?.let { return it }
return updates.first { result -> result.key() == key }
}
- private fun WearCommand.RecordingResult.key() = Key(sessionId, action)
+ private fun WearCommand.RecordingResult.key() = Key(sessionId, operation)
- private data class Key(val sessionId: String, val action: WearRecordingAction)
+ private data class Key(val sessionId: String, val operation: WearRecordingOperation)
private companion object {
const val BUFFER_SIZE = 16
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/PhoneRecordingController.kt b/app/src/main/java/com/tomasrepcik/sensorbox/domain/recording/PhoneRecordingController.kt
similarity index 54%
rename from app/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/PhoneRecordingController.kt
rename to app/src/main/java/com/tomasrepcik/sensorbox/domain/recording/PhoneRecordingController.kt
index 904f109..ec0f64f 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/PhoneRecordingController.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/domain/recording/PhoneRecordingController.kt
@@ -1,4 +1,4 @@
-package com.tomasrepcik.sensorbox.domain.measurement
+package com.tomasrepcik.sensorbox.domain.recording
import android.content.Context
import android.content.Intent
@@ -9,15 +9,15 @@ import com.tomasrepcik.sensorbox.core.error.AppResult
import com.tomasrepcik.sensorbox.core.error.appResult
import com.tomasrepcik.sensorbox.core.error.flatMap
import com.tomasrepcik.sensorbox.core.storage.DocumentStorage
-import com.tomasrepcik.sensorbox.sensorservices.intent.MeasurementIntentFactory
-import com.tomasrepcik.sensorbox.sensorservices.services.MeasurementService
+import com.tomasrepcik.sensorbox.sensorservices.intent.RecordingIntentFactory
+import com.tomasrepcik.sensorbox.sensorservices.services.RecordingService
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearStopReason
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton
interface PhoneRecordingController {
- fun start(sessionId: String, request: MeasurementRequest): AppResult
+ fun start(sessionId: String, request: RecordingSetup): AppResult
fun stop(sessionId: String, reason: WearStopReason): AppResult
@@ -29,31 +29,30 @@ interface PhoneRecordingController {
@Singleton
class AndroidPhoneRecordingController @Inject constructor(
@ApplicationContext private val context: Context,
- private val intentFactory: MeasurementIntentFactory,
+ private val intentFactory: RecordingIntentFactory,
private val documentStorage: DocumentStorage,
) : PhoneRecordingController {
- override fun start(sessionId: String, request: MeasurementRequest): AppResult =
- documentStorage
- .hasConfiguredDirectory()
- .flatMap { storageReady ->
- if (!storageReady) {
- AppResult.failure(AppError(AppErrorCode.STORAGE, "Start phone recording storage"))
- } else {
- startForegroundRecording(sessionId, request)
- }
+ override fun start(sessionId: String, request: RecordingSetup): AppResult = documentStorage
+ .hasConfiguredDirectory()
+ .flatMap { storageReady ->
+ if (!storageReady) {
+ AppResult.failure(AppError(AppErrorCode.STORAGE, "Start phone recording storage"))
+ } else {
+ startForegroundRecording(sessionId, request)
}
+ }
private fun startForegroundRecording(
sessionId: String,
- request: MeasurementRequest,
+ request: RecordingSetup,
): AppResult = appResult(
- AppErrorCode.MEASUREMENT,
+ AppErrorCode.RECORDING,
"Start phone recording",
) {
val folderName = intentFactory.newFolderName(request.customName)
- val launchRequest = request.toLaunchRequest(sessionId, folderName)
- ContextCompat.startForegroundService(context, intentFactory.create(launchRequest))
- StartedPhoneRecording(sessionId, folderName, launchRequest.durationMillis)
+ val recordingRequest = request.toRecordingRequest(sessionId, folderName)
+ ContextCompat.startForegroundService(context, intentFactory.create(recordingRequest))
+ StartedPhoneRecording(sessionId, folderName, recordingRequest.durationMillis)
}
override fun stop(sessionId: String, reason: WearStopReason): AppResult = stopService("Stop phone recording")
@@ -61,19 +60,19 @@ class AndroidPhoneRecordingController @Inject constructor(
override fun stopCurrent(reason: WearStopReason): AppResult = stopService("Stop current phone recording")
override fun annotate(text: String, timestampMillis: Long): AppResult = appResult(
- AppErrorCode.MEASUREMENT,
- "Request measurement annotation",
+ AppErrorCode.RECORDING,
+ "Request recording annotation",
) {
- val intent = Intent(context, MeasurementService::class.java)
- .setAction(MeasurementService.ACTION_ANNOTATE)
- .putExtra(MeasurementService.ANNOTATION_TIME, timestampMillis)
- .putExtra(MeasurementService.ANNOTATION_TEXT, text)
+ val intent = Intent(context, RecordingService::class.java)
+ .setAction(RecordingService.ACTION_ANNOTATE)
+ .putExtra(RecordingService.ANNOTATION_TIME, timestampMillis)
+ .putExtra(RecordingService.ANNOTATION_TEXT, text)
context.startService(intent)
}
- private fun stopService(operation: String): AppResult = appResult(AppErrorCode.MEASUREMENT, operation) {
- val stopIntent = Intent(context, MeasurementService::class.java)
- .setAction(MeasurementService.ACTION_STOP)
+ private fun stopService(operation: String): AppResult = appResult(AppErrorCode.RECORDING, operation) {
+ val stopIntent = Intent(context, RecordingService::class.java)
+ .setAction(RecordingService.ACTION_STOP_RECORDING)
context.startService(stopIntent)
}
}
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/domain/recording/RecordingArchiveRepository.kt b/app/src/main/java/com/tomasrepcik/sensorbox/domain/recording/RecordingArchiveRepository.kt
new file mode 100644
index 0000000..bd58020
--- /dev/null
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/domain/recording/RecordingArchiveRepository.kt
@@ -0,0 +1,23 @@
+package com.tomasrepcik.sensorbox.domain.recording
+
+import android.content.Intent
+import com.tomasrepcik.sensorbox.core.error.AppResult
+import com.tomasrepcik.sensorbox.core.storage.DocumentStorage
+import javax.inject.Inject
+
+interface RecordingArchiveRepository {
+ fun isSelected(): AppResult
+
+ fun path(): AppResult
+
+ fun select(resultIntent: Intent): AppResult
+}
+
+class DocumentRecordingArchiveRepository @Inject constructor(private val storage: DocumentStorage) :
+ RecordingArchiveRepository {
+ override fun isSelected(): AppResult = storage.hasConfiguredDirectory()
+
+ override fun path(): AppResult = storage.displayPath()
+
+ override fun select(resultIntent: Intent): AppResult = storage.persistRootAccess(resultIntent)
+}
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/MeasurementControlUseCase.kt b/app/src/main/java/com/tomasrepcik/sensorbox/domain/recording/RecordingControlUseCase.kt
similarity index 73%
rename from app/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/MeasurementControlUseCase.kt
rename to app/src/main/java/com/tomasrepcik/sensorbox/domain/recording/RecordingControlUseCase.kt
index 932044a..38e1a29 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/MeasurementControlUseCase.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/domain/recording/RecordingControlUseCase.kt
@@ -1,4 +1,4 @@
-package com.tomasrepcik.sensorbox.domain.measurement
+package com.tomasrepcik.sensorbox.domain.recording
import com.tomasrepcik.sensorbox.core.error.AppResult
import com.tomasrepcik.sensorbox.core.time.EpochClock
@@ -7,19 +7,19 @@ import com.tomasrepcik.sensorbox.wearoslib.protocol.WearStopReason
import javax.inject.Inject
interface RecordingControlUseCase {
- suspend fun start(request: MeasurementRequest): AppResult
+ suspend fun start(request: RecordingSetup): AppResult
suspend fun stop(): AppResult
fun annotate(text: String): AppResult
}
-class MeasurementControlUseCase @Inject constructor(
+class DefaultRecordingControlUseCase @Inject constructor(
private val pairedRecordingCoordinator: PairedRecordingCoordinator,
private val localController: PhoneRecordingController,
private val clock: EpochClock,
) : RecordingControlUseCase {
- override suspend fun start(request: MeasurementRequest): AppResult = pairedRecordingCoordinator.start(request)
+ override suspend fun start(request: RecordingSetup): AppResult = pairedRecordingCoordinator.start(request)
override suspend fun stop(): AppResult = pairedRecordingCoordinator.stop(WearStopReason.USER_REQUEST)
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/MeasurementPermissionUseCase.kt b/app/src/main/java/com/tomasrepcik/sensorbox/domain/recording/RecordingPermissionUseCase.kt
similarity index 51%
rename from app/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/MeasurementPermissionUseCase.kt
rename to app/src/main/java/com/tomasrepcik/sensorbox/domain/recording/RecordingPermissionUseCase.kt
index f14ec46..fdb7116 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/MeasurementPermissionUseCase.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/domain/recording/RecordingPermissionUseCase.kt
@@ -1,4 +1,4 @@
-package com.tomasrepcik.sensorbox.domain.measurement
+package com.tomasrepcik.sensorbox.domain.recording
import android.Manifest
import android.content.Context
@@ -8,20 +8,20 @@ import androidx.core.content.ContextCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
-fun interface MeasurementPermissionsUseCase {
- fun missingPermissions(request: MeasurementRequest): Set
+fun interface RecordingPermissionsUseCase {
+ fun missingPermissions(request: RecordingSetup): Set
}
-class MeasurementPermissionUseCase @Inject constructor(@ApplicationContext private val context: Context) :
- MeasurementPermissionsUseCase {
- override fun missingPermissions(request: MeasurementRequest): Set =
- requiredMeasurementPermissions(request, Build.VERSION.SDK_INT).filterNot(::isGranted).toSet()
+class RecordingPermissionUseCase @Inject constructor(@ApplicationContext private val context: Context) :
+ RecordingPermissionsUseCase {
+ override fun missingPermissions(request: RecordingSetup): Set =
+ requiredRecordingPermissions(request, Build.VERSION.SDK_INT).filterNot(::isGranted).toSet()
private fun isGranted(permission: String): Boolean =
ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
}
-internal fun requiredMeasurementPermissions(request: MeasurementRequest, sdkInt: Int): Set = buildSet {
+internal fun requiredRecordingPermissions(request: RecordingSetup, sdkInt: Int): Set = buildSet {
if (sdkInt >= 33) add(Manifest.permission.POST_NOTIFICATIONS)
if (request.includesGps) add(Manifest.permission.ACCESS_FINE_LOCATION)
if (request.activityRecognition && sdkInt >= 29) add(Manifest.permission.ACTIVITY_RECOGNITION)
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/MeasurementRequest.kt b/app/src/main/java/com/tomasrepcik/sensorbox/domain/recording/RecordingSetup.kt
similarity index 78%
rename from app/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/MeasurementRequest.kt
rename to app/src/main/java/com/tomasrepcik/sensorbox/domain/recording/RecordingSetup.kt
index 56de7e9..74abde8 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/MeasurementRequest.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/domain/recording/RecordingSetup.kt
@@ -1,10 +1,10 @@
-package com.tomasrepcik.sensorbox.domain.measurement
+package com.tomasrepcik.sensorbox.domain.recording
import android.hardware.SensorManager
-import com.tomasrepcik.sensorbox.sensorservices.intent.MeasurementLaunchRequest
+import com.tomasrepcik.sensorbox.sensorservices.intent.RecordingRequest
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingRequest
-data class MeasurementRequest(
+data class RecordingSetup(
val sensorIds: Set,
val includesGps: Boolean,
val samplingPeriodIndex: Int,
@@ -12,8 +12,8 @@ data class MeasurementRequest(
val useWakeLock: Boolean,
val gpsIntervalSeconds: Int,
val gpsMinDistanceMeters: Int,
- val wearSensorIds: Set = emptySet(),
- val wearIncludesGps: Boolean = false,
+ val watchSensorIds: Set = emptySet(),
+ val watchIncludesGps: Boolean = false,
val customName: String = "",
val durationSeconds: Int = 0,
val notes: List = emptyList(),
@@ -22,7 +22,7 @@ data class MeasurementRequest(
val activityRecognitionPeriodSeconds: Int = 30,
val significantMotion: Boolean = false,
) {
- fun toLaunchRequest(sessionId: String, folderName: String) = MeasurementLaunchRequest(
+ fun toRecordingRequest(sessionId: String, folderName: String) = RecordingRequest(
sessionId = sessionId,
folderName = folderName,
useInternalStorage = false,
@@ -55,9 +55,9 @@ data class MeasurementRequest(
data class StartedPhoneRecording(val sessionId: String, val folderName: String, val durationMillis: Long)
-fun StartedPhoneRecording.toWearRecordingRequest(request: MeasurementRequest) = WearRecordingRequest(
+fun StartedPhoneRecording.toWatchRecordingRequest(request: RecordingSetup) = WearRecordingRequest(
folderName = folderName,
- sensorIds = request.wearSensorIds.sorted(),
- includesGps = request.wearIncludesGps,
+ sensorIds = request.watchSensorIds.sorted(),
+ includesGps = request.watchIncludesGps,
durationMillis = durationMillis,
)
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/domain/sensors/WearSensorCatalogStore.kt b/app/src/main/java/com/tomasrepcik/sensorbox/domain/sensors/WatchSensorCatalogStore.kt
similarity index 94%
rename from app/src/main/java/com/tomasrepcik/sensorbox/domain/sensors/WearSensorCatalogStore.kt
rename to app/src/main/java/com/tomasrepcik/sensorbox/domain/sensors/WatchSensorCatalogStore.kt
index cbf57ec..caa3da1 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/domain/sensors/WearSensorCatalogStore.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/domain/sensors/WatchSensorCatalogStore.kt
@@ -8,7 +8,7 @@ import javax.inject.Inject
import javax.inject.Singleton
@Singleton
-class WearSensorCatalogStore @Inject constructor() {
+class WatchSensorCatalogStore @Inject constructor() {
private val mutableSensors = MutableStateFlow>(emptyList())
private val mutableAvailable = MutableStateFlow(false)
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/domain/sync/ReceiveWearFileUseCase.kt b/app/src/main/java/com/tomasrepcik/sensorbox/domain/sync/ReceiveWatchFileUseCase.kt
similarity index 91%
rename from app/src/main/java/com/tomasrepcik/sensorbox/domain/sync/ReceiveWearFileUseCase.kt
rename to app/src/main/java/com/tomasrepcik/sensorbox/domain/sync/ReceiveWatchFileUseCase.kt
index 7216b5a..e0d5172 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/domain/sync/ReceiveWearFileUseCase.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/domain/sync/ReceiveWatchFileUseCase.kt
@@ -10,9 +10,9 @@ import com.tomasrepcik.sensorbox.wearoslib.files.WearFilePathCodec
import com.tomasrepcik.sensorbox.wearoslib.files.WearFileTransferClient
import javax.inject.Inject
-class ReceiveWearFileUseCase @Inject constructor(
+class ReceiveWatchFileUseCase @Inject constructor(
private val transferClient: WearFileTransferClient,
- private val destination: WearFileDestination,
+ private val destination: WatchFileDestination,
) {
suspend operator fun invoke(channel: ChannelClient.Channel): AppResult =
WearFilePathCodec.decode(channel.path).suspendFlatMap { metadata ->
@@ -21,7 +21,7 @@ class ReceiveWearFileUseCase @Inject constructor(
AppResult.success(Unit)
} else {
AppResult.failure(
- AppError(AppErrorCode.STORAGE, "Receive Wear file"),
+ AppError(AppErrorCode.STORAGE, "Receive watch file"),
)
}
}.suspendFlatMap {
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/domain/sync/WearFileDestination.kt b/app/src/main/java/com/tomasrepcik/sensorbox/domain/sync/WatchFileDestination.kt
similarity index 91%
rename from app/src/main/java/com/tomasrepcik/sensorbox/domain/sync/WearFileDestination.kt
rename to app/src/main/java/com/tomasrepcik/sensorbox/domain/sync/WatchFileDestination.kt
index 5094bf1..cd02ee0 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/domain/sync/WearFileDestination.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/domain/sync/WatchFileDestination.kt
@@ -16,7 +16,7 @@ import javax.inject.Inject
import javax.inject.Singleton
@Singleton
-class WearFileDestination @Inject constructor(
+class WatchFileDestination @Inject constructor(
@ApplicationContext private val context: Context,
private val documentStorage: DocumentStorage,
) {
@@ -47,15 +47,15 @@ class WearFileDestination @Inject constructor(
)
private fun copyToDebugDirectory(measurementName: String, fileName: String, input: InputStream): AppResult {
- if (!isDebugBuild()) return AppResult.failure(AppError(AppErrorCode.STORAGE, "Copy debug Wear file"))
- return appResult(AppErrorCode.STORAGE, "Prepare debug Wear directory") {
+ if (!isDebugBuild()) return AppResult.failure(AppError(AppErrorCode.STORAGE, "Copy debug watch file"))
+ return appResult(AppErrorCode.STORAGE, "Prepare debug watch directory") {
val directory = File(context.filesDir, "$APP_DIRECTORY/$measurementName")
directory to (directory.isDirectory || directory.mkdirs())
}.flatMap { (directory, ready) ->
if (!ready) {
- AppResult.failure(AppError(AppErrorCode.STORAGE, "Prepare debug Wear directory"))
+ AppResult.failure(AppError(AppErrorCode.STORAGE, "Prepare debug watch directory"))
} else {
- appResult(AppErrorCode.STORAGE, "Copy debug Wear file") {
+ appResult(AppErrorCode.STORAGE, "Copy debug watch file") {
File(directory, fileName).outputStream().use(input::copyTo)
Unit
}
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/ActiveMeasurementScreen.kt b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/ActiveRecordingScreen.kt
similarity index 91%
rename from app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/ActiveMeasurementScreen.kt
rename to app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/ActiveRecordingScreen.kt
index 4d78b98..e50dba8 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/ActiveMeasurementScreen.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/ActiveRecordingScreen.kt
@@ -52,12 +52,12 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.tomasrepcik.sensorbox.R
import com.tomasrepcik.sensorbox.core.format.ValueFormats
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementSessionState
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionState
import com.tomasrepcik.sensorbox.ui.theme.SensorBoxRecording
@Composable
-fun ActiveMeasurementScreen(state: RecordingState, onIntent: (RecordingIntent) -> Unit, modifier: Modifier = Modifier) {
- val session = state.session as? MeasurementSessionState.Running ?: return
+fun ActiveRecordingScreen(state: RecordingState, onIntent: (RecordingIntent) -> Unit, modifier: Modifier = Modifier) {
+ val session = state.session as? RecordingSessionState.Running ?: return
Column(
modifier = modifier.fillMaxSize(),
) {
@@ -68,14 +68,14 @@ fun ActiveMeasurementScreen(state: RecordingState, onIntent: (RecordingIntent) -
.verticalScroll(rememberScrollState())
.padding(horizontal = 24.dp),
) {
- MeasurementTimer(state.elapsedSeconds, session.folderName)
+ RecordingTimer(state.elapsedSeconds, session.folderName)
SensorBoxSettingsDivider()
- MeasurementSummary(state, session)
- SensorBoxSettingsSection(stringResource(R.string.measurement_markers_category))
+ RecordingSummary(state, session)
+ SensorBoxSettingsSection(stringResource(R.string.recording_annotations_category))
AnnotationEditor(onIntent)
Spacer(Modifier.height(24.dp))
}
- ActiveMeasurementActionBar(onIntent)
+ ActiveRecordingTopBar(onIntent)
}
}
@@ -167,7 +167,7 @@ private fun RecordingIndicator() {
}
@Composable
-private fun MeasurementTimer(elapsedSeconds: Long, folderName: String) {
+private fun RecordingTimer(elapsedSeconds: Long, folderName: String) {
Column(
modifier = Modifier.fillMaxWidth().padding(vertical = 20.dp),
horizontalAlignment = Alignment.CenterHorizontally,
@@ -180,7 +180,7 @@ private fun MeasurementTimer(elapsedSeconds: Long, folderName: String) {
}
@Composable
-private fun MeasurementSummary(state: RecordingState, session: MeasurementSessionState.Running) {
+private fun RecordingSummary(state: RecordingState, session: RecordingSessionState.Running) {
var expanded by remember { mutableStateOf(false) }
val sources = recordingSourceNames(state, session, LocalContext.current.resources)
Column(Modifier.fillMaxWidth()) {
@@ -239,7 +239,7 @@ private fun RecordingSourceList(sources: List) {
private fun recordingSourceNames(
state: RecordingState,
- session: MeasurementSessionState.Running,
+ session: RecordingSessionState.Running,
resources: Resources,
): List = buildList {
session.sensorIds.forEach { sensorId ->
@@ -252,14 +252,14 @@ private fun recordingSourceNames(
}
@Composable
-private fun ActiveMeasurementActionBar(onIntent: (RecordingIntent) -> Unit) {
+private fun ActiveRecordingTopBar(onIntent: (RecordingIntent) -> Unit) {
Box(
Modifier.fillMaxWidth().padding(horizontal = 24.dp, vertical = 14.dp),
contentAlignment = Alignment.Center,
) {
SensorBoxDangerButton(
label = stringResource(R.string.stop_and_save),
- onClick = { onIntent(RecordingIntent.StopMeasurement) },
+ onClick = { onIntent(RecordingIntent.StopRecording) },
modifier = Modifier.widthIn(min = 176.dp, max = 240.dp),
)
}
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/MainContract.kt b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/MainContract.kt
index 3bcd9de..291fed0 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/MainContract.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/MainContract.kt
@@ -1,17 +1,17 @@
package com.tomasrepcik.sensorbox.presentation.main
import androidx.navigation3.runtime.NavKey
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementSessionState
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionState
import kotlinx.serialization.Serializable
@Serializable
enum class MainRoute : NavKey {
ONBOARDING,
RECORD,
- ACTIVE_MEASUREMENT,
+ ACTIVE_RECORDING,
SENSOR_DETAILS,
SENSOR_PREVIEW,
- SETUP,
+ RECORDING_SETUP,
MEASUREMENTS,
MEASUREMENT_DETAILS,
MEASUREMENT_FILE,
@@ -23,7 +23,7 @@ enum class MainRoute : NavKey {
data class MainState(
val route: MainRoute = MainRoute.ONBOARDING,
- val session: MeasurementSessionState = MeasurementSessionState.Idle,
+ val session: RecordingSessionState = RecordingSessionState.Idle,
val keepScreenAwake: Boolean = false,
val hasLoadedPreferences: Boolean = false,
)
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/MainViewModel.kt b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/MainViewModel.kt
index 90f8f9f..3d99276 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/MainViewModel.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/MainViewModel.kt
@@ -3,7 +3,7 @@ package com.tomasrepcik.sensorbox.presentation.main
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tomasrepcik.sensorbox.core.preferences.AppPreferencesRepository
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementSessionStore
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionStore
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -14,7 +14,7 @@ import javax.inject.Inject
@HiltViewModel
class MainViewModel @Inject constructor(
private val preferencesRepository: AppPreferencesRepository,
- private val sessionStore: MeasurementSessionStore,
+ private val sessionStore: RecordingSessionStore,
) : ViewModel() {
private val mutableState = MutableStateFlow(MainState())
private var hasChosenInitialRoute = false
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingContract.kt b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingContract.kt
index 8d418fd..ca26fd7 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingContract.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingContract.kt
@@ -2,7 +2,11 @@ package com.tomasrepcik.sensorbox.presentation.main
import com.tomasrepcik.sensorbox.core.error.AppErrorCode
-data class OnboardingState(val page: Int = 0, val storagePath: String? = null, val errorCode: AppErrorCode? = null)
+data class OnboardingState(
+ val page: Int = 0,
+ val recordingArchivePath: String? = null,
+ val errorCode: AppErrorCode? = null,
+)
sealed interface OnboardingIntent {
data object AdvanceOnboarding : OnboardingIntent
@@ -11,11 +15,11 @@ sealed interface OnboardingIntent {
data object OpenPrivacyPolicy : OnboardingIntent
data object OpenTermsOfUse : OnboardingIntent
data object RequestBatteryOptimizationExemption : OnboardingIntent
- data object ChooseStorage : OnboardingIntent
+ data object ChooseRecordingArchive : OnboardingIntent
}
sealed interface OnboardingEffect {
- data object PickStorageDirectory : OnboardingEffect
+ data object PickRecordingArchive : OnboardingEffect
data object OpenPrivacyPolicy : OnboardingEffect
data object OpenTermsOfUse : OnboardingEffect
data object RequestBatteryOptimizationExemption : OnboardingEffect
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingScreen.kt b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingScreen.kt
index da988c3..5255601 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingScreen.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingScreen.kt
@@ -81,7 +81,7 @@ private fun OnboardingLayout(
}
OnboardingControls(
pageIndex = pageIndex,
- hasStorage = state.storagePath != null,
+ archiveSelected = state.recordingArchivePath != null,
isLandscape = isLandscape,
onIntent = onIntent,
modifier = Modifier
@@ -186,7 +186,7 @@ private fun PortraitOnboardingMessage(
Spacer(Modifier.height(24.dp))
OnboardingText(page)
Spacer(Modifier.height(18.dp))
- OnboardingPageActions(page.action, state.storagePath, onIntent)
+ OnboardingPageButtons(page.prompt, state.recordingArchivePath, onIntent)
}
}
@@ -213,7 +213,7 @@ private fun LandscapeOnboardingMessage(
) {
OnboardingText(page)
Spacer(Modifier.height(12.dp))
- OnboardingPageActions(page.action, state.storagePath, onIntent)
+ OnboardingPageButtons(page.prompt, state.recordingArchivePath, onIntent)
}
}
}
@@ -252,17 +252,17 @@ private fun OnboardingText(page: OnboardingPage) {
}
@Composable
-private fun OnboardingPageActions(action: OnboardingAction, path: String?, onIntent: (OnboardingIntent) -> Unit) {
- when (action) {
- OnboardingAction.NONE -> Unit
- OnboardingAction.POLICIES -> PolicyActions(onIntent)
- OnboardingAction.BATTERY -> BatteryAction(onIntent)
- OnboardingAction.STORAGE -> StorageAction(path, onIntent)
+private fun OnboardingPageButtons(prompt: OnboardingPrompt, path: String?, onIntent: (OnboardingIntent) -> Unit) {
+ when (prompt) {
+ OnboardingPrompt.NONE -> Unit
+ OnboardingPrompt.POLICIES -> PolicyButtons(onIntent)
+ OnboardingPrompt.BATTERY -> BatteryButton(onIntent)
+ OnboardingPrompt.ARCHIVE -> ArchiveButton(path, onIntent)
}
}
@Composable
-private fun PolicyActions(onIntent: (OnboardingIntent) -> Unit) {
+private fun PolicyButtons(onIntent: (OnboardingIntent) -> Unit) {
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) {
SensorBoxSecondaryButton(
label = stringResource(R.string.intro_policy_button),
@@ -280,7 +280,7 @@ private fun PolicyActions(onIntent: (OnboardingIntent) -> Unit) {
}
@Composable
-private fun BatteryAction(onIntent: (OnboardingIntent) -> Unit) {
+private fun BatteryButton(onIntent: (OnboardingIntent) -> Unit) {
SensorBoxSecondaryButton(
label = stringResource(R.string.intro_battery_action),
onClick = { onIntent(OnboardingIntent.RequestBatteryOptimizationExemption) },
@@ -290,20 +290,20 @@ private fun BatteryAction(onIntent: (OnboardingIntent) -> Unit) {
}
@Composable
-private fun StorageAction(path: String?, onIntent: (OnboardingIntent) -> Unit) {
+private fun ArchiveButton(path: String?, onIntent: (OnboardingIntent) -> Unit) {
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
SensorBoxSecondaryButton(
label = stringResource(
- if (path == null) R.string.intro_storage_action else R.string.intro_storage_change_action,
+ if (path == null) R.string.intro_archive_action else R.string.intro_archive_change_action,
),
- onClick = { onIntent(OnboardingIntent.ChooseStorage) },
+ onClick = { onIntent(OnboardingIntent.ChooseRecordingArchive) },
modifier = Modifier.fillMaxWidth(),
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
)
if (path != null) {
Spacer(Modifier.height(10.dp))
Text(
- stringResource(R.string.intro_storage_selected, path),
+ stringResource(R.string.intro_archive_selected, path),
color = MaterialTheme.colorScheme.onPrimaryContainer,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyMedium,
@@ -315,7 +315,7 @@ private fun StorageAction(path: String?, onIntent: (OnboardingIntent) -> Unit) {
@Composable
private fun OnboardingControls(
pageIndex: Int,
- hasStorage: Boolean,
+ archiveSelected: Boolean,
isLandscape: Boolean,
onIntent: (OnboardingIntent) -> Unit,
modifier: Modifier = Modifier,
@@ -327,7 +327,7 @@ private fun OnboardingControls(
OnboardingPrimaryButton(
label = stringResource(if (isLastPage) R.string.intro_finish else R.string.next),
onClick = { onIntent(onboardingForwardIntent(isLastPage)) },
- enabled = !isLastPage || hasStorage,
+ enabled = !isLastPage || archiveSelected,
)
}
}
@@ -357,11 +357,11 @@ private data class OnboardingPage(
@StringRes val title: Int,
@StringRes val body: Int,
@DrawableRes val image: Int,
- val action: OnboardingAction = OnboardingAction.NONE,
+ val prompt: OnboardingPrompt = OnboardingPrompt.NONE,
val tintIllustration: Boolean = true,
)
-private enum class OnboardingAction { NONE, POLICIES, BATTERY, STORAGE }
+private enum class OnboardingPrompt { NONE, POLICIES, BATTERY, ARCHIVE }
private val ONBOARDING_MAX_WIDTH = 960.dp
private val ONBOARDING_MESSAGE_MAX_WIDTH = 520.dp
@@ -383,7 +383,7 @@ private val ONBOARDING_PAGES = listOf(
R.string.intro_policy_title,
R.string.intro_policy_body,
R.drawable.ic_onboarding_policy,
- OnboardingAction.POLICIES,
+ OnboardingPrompt.POLICIES,
),
OnboardingPage(
R.string.intro_lifecycle_title,
@@ -394,12 +394,12 @@ private val ONBOARDING_PAGES = listOf(
R.string.intro_battery_title,
R.string.intro_battery_body,
R.drawable.ic_onboarding_battery,
- OnboardingAction.BATTERY,
+ OnboardingPrompt.BATTERY,
),
OnboardingPage(
- R.string.intro_storage_title,
- R.string.intro_storage_body,
+ R.string.intro_archive_title,
+ R.string.intro_archive_body,
R.drawable.ic_onboarding_storage,
- OnboardingAction.STORAGE,
+ OnboardingPrompt.ARCHIVE,
),
)
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingViewModel.kt b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingViewModel.kt
index 0141d34..cd4622e 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingViewModel.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/OnboardingViewModel.kt
@@ -8,7 +8,7 @@ import com.tomasrepcik.sensorbox.core.error.AppErrorCode
import com.tomasrepcik.sensorbox.core.error.AppResult
import com.tomasrepcik.sensorbox.core.preferences.AppPreferencesIntent
import com.tomasrepcik.sensorbox.core.preferences.AppPreferencesRepository
-import com.tomasrepcik.sensorbox.domain.measurement.DocumentStorageGateway
+import com.tomasrepcik.sensorbox.domain.recording.RecordingArchiveRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
@@ -21,10 +21,10 @@ import javax.inject.Inject
@HiltViewModel
class OnboardingViewModel @Inject constructor(
private val preferencesRepository: AppPreferencesRepository,
- private val documentStorage: DocumentStorageGateway,
+ private val recordingArchive: RecordingArchiveRepository,
) : ViewModel() {
private val mutableState = MutableStateFlow(
- OnboardingState(storagePath = documentStorage.displayPath().getOrNull()),
+ OnboardingState(recordingArchivePath = recordingArchive.path().getOrNull()),
)
private val mutableEffects = Channel(Channel.BUFFERED)
@@ -45,7 +45,7 @@ class OnboardingViewModel @Inject constructor(
OnboardingIntent.CompleteOnboarding -> completeOnboarding()
- OnboardingIntent.ChooseStorage -> mutableEffects.trySend(OnboardingEffect.PickStorageDirectory)
+ OnboardingIntent.ChooseRecordingArchive -> mutableEffects.trySend(OnboardingEffect.PickRecordingArchive)
OnboardingIntent.OpenPrivacyPolicy -> mutableEffects.trySend(OnboardingEffect.OpenPrivacyPolicy)
@@ -56,18 +56,18 @@ class OnboardingViewModel @Inject constructor(
}
}
- fun handleStorageResult(resultIntent: Intent?) {
- val result = resultIntent?.let(documentStorage::persist) ?: AppResult.failure(
- AppError(AppErrorCode.STORAGE, "Select onboarding storage directory"),
+ fun handleRecordingArchiveResult(resultIntent: Intent?) {
+ val result = resultIntent?.let(recordingArchive::select) ?: AppResult.failure(
+ AppError(AppErrorCode.STORAGE, "Select onboarding recording archive"),
)
mutableState.value = state.value.copy(
- storagePath = documentStorage.displayPath().getOrNull(),
+ recordingArchivePath = recordingArchive.path().getOrNull(),
errorCode = result.errorOrNull()?.code,
)
}
private fun completeOnboarding() {
- if (documentStorage.hasStorage().getOrNull() != true) {
+ if (recordingArchive.isSelected().getOrNull() != true) {
mutableState.value = state.value.copy(errorCode = AppErrorCode.STORAGE)
return
}
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/RecordScreen.kt b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/RecordScreen.kt
index c2f52c8..f8bc292 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/RecordScreen.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/RecordScreen.kt
@@ -40,7 +40,7 @@ fun RecordScreen(state: RecordingState, onIntent: (RecordingIntent) -> Unit, mod
Box(Modifier.weight(1f)) {
RecordContent(state, onIntent)
}
- SensorSelectionActionBar(state, onIntent)
+ SensorSelectionTopBar(state, onIntent)
}
}
@@ -80,26 +80,26 @@ private fun RecordContent(state: RecordingState, onIntent: (RecordingIntent) ->
SourceDivider()
}
}
- if (state.isWearConnected) {
+ if (state.isWatchConnected) {
item { WearSectionHeader() }
item {
GpsRow(
- selected = state.wearIncludesGps,
- onToggle = { onIntent(RecordingIntent.ToggleWearGps) },
+ selected = state.watchIncludesGps,
+ onToggle = { onIntent(RecordingIntent.ToggleWatchGps) },
)
}
item { SourceDivider() }
- items(state.wearSensors, key = { "wear_${it.type}" }) { sensor ->
+ items(state.watchSensors, key = { "watch_${it.type}" }) { sensor ->
Column {
SensorRow(
sensor = sensor,
- selected = sensor.type in state.selectedWearSensorIds,
- onToggle = { onIntent(RecordingIntent.ToggleWearSensor(sensor.type)) },
+ selected = sensor.type in state.selectedWatchSensorIds,
+ onToggle = { onIntent(RecordingIntent.ToggleWatchSensor(sensor.type)) },
onInfo = {
onIntent(
RecordingIntent.OpenSensorDetails(
sensor.type,
- RecordingSensorSource.WEAR,
+ RecordingDevice.WATCH,
),
)
},
@@ -115,7 +115,7 @@ private fun RecordContent(state: RecordingState, onIntent: (RecordingIntent) ->
@Composable
private fun WearSectionHeader() {
Text(
- stringResource(R.string.wear_sensors),
+ stringResource(R.string.watch_sensors),
modifier = Modifier.fillMaxWidth().padding(top = 28.dp, bottom = 8.dp),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -217,7 +217,7 @@ private fun SourceDivider() {
}
@Composable
-private fun SensorSelectionActionBar(
+private fun SensorSelectionTopBar(
state: RecordingState,
onIntent: (RecordingIntent) -> Unit,
modifier: Modifier = Modifier,
@@ -234,7 +234,7 @@ private fun SensorSelectionActionBar(
) {
SensorBoxPrimaryButton(
label = stringResource(R.string.continue_action),
- onClick = { onIntent(RecordingIntent.OpenMeasurementSetup) },
+ onClick = { onIntent(RecordingIntent.OpenRecordingSetup) },
modifier = Modifier.fillMaxWidth(),
enabled = sensorCount > 0,
)
@@ -243,9 +243,9 @@ private fun SensorSelectionActionBar(
}
private fun selectedSourceCount(state: RecordingState): Int = state.selectedSensorIds.size +
- state.selectedWearSensorIds.size +
+ state.selectedWatchSensorIds.size +
(if (state.includesGps) 1 else 0) +
- (if (state.wearIncludesGps) 1 else 0) +
+ (if (state.watchIncludesGps) 1 else 0) +
(if (state.activityRecognition) 1 else 0) +
(if (state.significantMotion) 1 else 0)
@@ -287,19 +287,19 @@ fun RecordingMessageText(message: RecordingMessage) {
@StringRes
private fun messageTitleResource(message: RecordingMessage): Int = when (message) {
RecordingMessage.PICK_AT_LEAST_ONE_SOURCE -> R.string.message_pick_source_title
- RecordingMessage.STORAGE_REQUIRED -> R.string.message_storage_required_title
+ RecordingMessage.RECORDING_ARCHIVE_REQUIRED -> R.string.message_recording_archive_required_title
RecordingMessage.PERMISSION_REQUIRED -> R.string.message_permission_required_title
- RecordingMessage.WEAR_PERMISSION_REQUIRED -> R.string.message_wear_permission_required_title
- RecordingMessage.MEASUREMENT_FAILED -> R.string.message_measurement_failed_title
+ RecordingMessage.WATCH_PERMISSION_REQUIRED -> R.string.message_wear_permission_required_title
+ RecordingMessage.RECORDING_FAILED -> R.string.message_recording_failed_title
RecordingMessage.NONE -> R.string.app_name
}
@StringRes
private fun messageTextResource(message: RecordingMessage): Int = when (message) {
RecordingMessage.PICK_AT_LEAST_ONE_SOURCE -> R.string.message_pick_source
- RecordingMessage.STORAGE_REQUIRED -> R.string.message_storage_required
+ RecordingMessage.RECORDING_ARCHIVE_REQUIRED -> R.string.message_recording_archive_required
RecordingMessage.PERMISSION_REQUIRED -> R.string.message_permission_required
- RecordingMessage.WEAR_PERMISSION_REQUIRED -> R.string.message_wear_permission_required
- RecordingMessage.MEASUREMENT_FAILED -> R.string.message_measurement_failed
+ RecordingMessage.WATCH_PERMISSION_REQUIRED -> R.string.message_wear_permission_required
+ RecordingMessage.RECORDING_FAILED -> R.string.message_recording_failed
RecordingMessage.NONE -> R.string.app_name
}
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/RecordingContract.kt b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/RecordingContract.kt
index 772fc12..4c78933 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/RecordingContract.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/RecordingContract.kt
@@ -2,33 +2,33 @@ package com.tomasrepcik.sensorbox.presentation.main
import com.tomasrepcik.sensorbox.core.error.AppErrorCode
import com.tomasrepcik.sensorbox.core.preferences.AppPreferences
-import com.tomasrepcik.sensorbox.domain.measurement.MeasurementRequest
+import com.tomasrepcik.sensorbox.domain.recording.RecordingSetup
import com.tomasrepcik.sensorbox.domain.sensors.SensorDescriptor
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementSessionState
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionState
enum class RecordingMessage {
NONE,
PICK_AT_LEAST_ONE_SOURCE,
- STORAGE_REQUIRED,
+ RECORDING_ARCHIVE_REQUIRED,
PERMISSION_REQUIRED,
- WEAR_PERMISSION_REQUIRED,
- MEASUREMENT_FAILED,
+ WATCH_PERMISSION_REQUIRED,
+ RECORDING_FAILED,
}
-enum class RecordingSensorSource {
+enum class RecordingDevice {
PHONE,
- WEAR,
+ WATCH,
}
data class RecordingState(
val sensors: List = emptyList(),
- val wearSensors: List = emptyList(),
+ val watchSensors: List = emptyList(),
val detailsSensorType: Int? = null,
- val detailsSensorSource: RecordingSensorSource = RecordingSensorSource.PHONE,
+ val detailsDevice: RecordingDevice = RecordingDevice.PHONE,
val selectedSensorIds: Set = emptySet(),
val includesGps: Boolean = false,
- val selectedWearSensorIds: Set = emptySet(),
- val wearIncludesGps: Boolean = false,
+ val selectedWatchSensorIds: Set = emptySet(),
+ val watchIncludesGps: Boolean = false,
val customMeasurementName: String = "",
val startDelaySeconds: Int = 0,
val durationSeconds: Int = 0,
@@ -37,26 +37,26 @@ data class RecordingState(
val activityRecognition: Boolean = false,
val activityRecognitionPeriodSeconds: Int = 30,
val significantMotion: Boolean = false,
- val storagePath: String? = null,
+ val recordingArchivePath: String? = null,
val preferences: AppPreferences = AppPreferences(),
- val session: MeasurementSessionState = MeasurementSessionState.Idle,
+ val session: RecordingSessionState = RecordingSessionState.Idle,
val elapsedSeconds: Long = 0,
- val isWearConnected: Boolean = false,
+ val isWatchConnected: Boolean = false,
val isStarting: Boolean = false,
val startCountdownSeconds: Int? = null,
val message: RecordingMessage = RecordingMessage.NONE,
val errorCode: AppErrorCode? = null,
) {
- fun toMeasurementRequest() = MeasurementRequest(
+ fun toRecordingSetup() = RecordingSetup(
sensorIds = selectedSensorIds,
includesGps = includesGps,
samplingPeriodIndex = preferences.recording.sensorSamplingPeriod,
- stopOnLowBattery = preferences.recording.restrictMeasurementOnLowBattery,
+ stopOnLowBattery = preferences.recording.stopRecordingOnLowBattery,
useWakeLock = preferences.recording.useWakeLock,
gpsIntervalSeconds = preferences.recording.gpsIntervalSeconds,
gpsMinDistanceMeters = preferences.recording.gpsMinDistanceMeters,
- wearSensorIds = selectedWearSensorIds,
- wearIncludesGps = wearIncludesGps,
+ watchSensorIds = selectedWatchSensorIds,
+ watchIncludesGps = watchIncludesGps,
customName = customMeasurementName,
durationSeconds = durationSeconds.coerceAtLeast(0),
notes = notes.lines().map(String::trim).filter(String::isNotEmpty),
@@ -71,18 +71,16 @@ data class RecordingState(
sealed interface RecordingIntent {
data class Navigate(val route: MainRoute) : RecordingIntent
data class ToggleSensor(val sensorId: Int) : RecordingIntent
- data class ToggleWearSensor(val sensorId: Int) : RecordingIntent
- data class OpenSensorDetails(
- val sensorType: Int?,
- val source: RecordingSensorSource = RecordingSensorSource.PHONE,
- ) : RecordingIntent
+ data class ToggleWatchSensor(val sensorId: Int) : RecordingIntent
+ data class OpenSensorDetails(val sensorType: Int?, val device: RecordingDevice = RecordingDevice.PHONE) :
+ RecordingIntent
data object ToggleGps : RecordingIntent
- data object ToggleWearGps : RecordingIntent
- data object ChooseStorage : RecordingIntent
- data object OpenMeasurementSetup : RecordingIntent
+ data object ToggleWatchGps : RecordingIntent
+ data object ChooseRecordingArchive : RecordingIntent
+ data object OpenRecordingSetup : RecordingIntent
data object ReturnToSensorSelection : RecordingIntent
- data object StartMeasurement : RecordingIntent
- data object StopMeasurement : RecordingIntent
+ data object StartRecording : RecordingIntent
+ data object StopRecording : RecordingIntent
data object ClearMessage : RecordingIntent
data class SetCustomMeasurementName(val value: String) : RecordingIntent
data class SetStartDelay(val seconds: Int) : RecordingIntent
@@ -91,10 +89,10 @@ sealed interface RecordingIntent {
data class SetAlarmOffsets(val value: String) : RecordingIntent
data class SetActivityRecognition(val enabled: Boolean) : RecordingIntent
data class SetActivityRecognitionPeriod(val seconds: Int) : RecordingIntent
- data class SetSignificantMotion(val enabled: Boolean) : RecordingIntent
+ data class SetSignificantMotionRecording(val enabled: Boolean) : RecordingIntent
data class AddAnnotation(val text: String) : RecordingIntent
data class SetSamplingPeriod(val index: Int) : RecordingIntent
- data class SetLowBatteryRestriction(val enabled: Boolean) : RecordingIntent
+ data class SetStopOnLowBattery(val enabled: Boolean) : RecordingIntent
data class SetWakeLock(val enabled: Boolean) : RecordingIntent
data class SetKeepScreenAwake(val enabled: Boolean) : RecordingIntent
data class SetGpsInterval(val seconds: Int) : RecordingIntent
@@ -102,7 +100,7 @@ sealed interface RecordingIntent {
}
sealed interface RecordingEffect {
- data object PickStorageDirectory : RecordingEffect
+ data object PickRecordingArchive : RecordingEffect
data class RequestPermissions(val permissions: Set) : RecordingEffect
data class Navigate(val route: MainRoute) : RecordingEffect
}
@@ -114,45 +112,45 @@ object RecordingReducer {
?: reduceSelection(state, intent)
?: reduceConfiguration(state, intent)
- fun measurementStartRequested(state: RecordingState, countdownSeconds: Int): RecordingState = state.copy(
+ fun recordingStartRequested(state: RecordingState, countdownSeconds: Int): RecordingState = state.copy(
isStarting = true,
startCountdownSeconds = countdownSeconds.takeIf { it > 0 },
message = RecordingMessage.NONE,
errorCode = null,
)
- fun measurementCountdownChanged(state: RecordingState, seconds: Int?): RecordingState = state.copy(
+ fun recordingCountdownChanged(state: RecordingState, seconds: Int?): RecordingState = state.copy(
startCountdownSeconds = seconds,
)
- fun measurementStartFailed(state: RecordingState): RecordingState = state.copy(
+ fun recordingStartFailed(state: RecordingState): RecordingState = state.copy(
isStarting = false,
startCountdownSeconds = null,
)
- fun measurementSessionChanged(state: RecordingState, session: MeasurementSessionState): RecordingState = state.copy(
+ fun recordingSessionChanged(state: RecordingState, session: RecordingSessionState): RecordingState = state.copy(
session = session,
elapsedSeconds = 0,
- isStarting = if (session is MeasurementSessionState.Running) false else state.isStarting,
- startCountdownSeconds = if (session is MeasurementSessionState.Running) null else state.startCountdownSeconds,
+ isStarting = if (session is RecordingSessionState.Running) false else state.isStarting,
+ startCountdownSeconds = if (session is RecordingSessionState.Running) null else state.startCountdownSeconds,
)
private fun reduceNavigation(state: RecordingState, intent: RecordingIntent): RecordingNext? = when (intent) {
- RecordingIntent.ChooseStorage -> RecordingNext(state, RecordingEffect.PickStorageDirectory)
+ RecordingIntent.ChooseRecordingArchive -> RecordingNext(state, RecordingEffect.PickRecordingArchive)
is RecordingIntent.Navigate -> RecordingNext(state, RecordingEffect.Navigate(intent.route))
is RecordingIntent.OpenSensorDetails -> RecordingNext(
state.copy(
detailsSensorType = intent.sensorType,
- detailsSensorSource = intent.source,
+ detailsDevice = intent.device,
),
RecordingEffect.Navigate(MainRoute.SENSOR_DETAILS),
)
- RecordingIntent.OpenMeasurementSetup -> RecordingNext(
+ RecordingIntent.OpenRecordingSetup -> RecordingNext(
state.copy(message = RecordingMessage.NONE),
- RecordingEffect.Navigate(MainRoute.SETUP),
+ RecordingEffect.Navigate(MainRoute.RECORDING_SETUP),
)
RecordingIntent.ReturnToSensorSelection -> RecordingNext(
@@ -166,9 +164,9 @@ object RecordingReducer {
private fun reduceSelection(state: RecordingState, intent: RecordingIntent): RecordingNext? = when (intent) {
RecordingIntent.ClearMessage -> RecordingNext(state.copy(message = RecordingMessage.NONE, errorCode = null))
RecordingIntent.ToggleGps -> RecordingNext(state.copy(includesGps = !state.includesGps))
- RecordingIntent.ToggleWearGps -> RecordingNext(state.copy(wearIncludesGps = !state.wearIncludesGps))
+ RecordingIntent.ToggleWatchGps -> RecordingNext(state.copy(watchIncludesGps = !state.watchIncludesGps))
is RecordingIntent.ToggleSensor -> RecordingNext(state.toggleSensor(intent.sensorId))
- is RecordingIntent.ToggleWearSensor -> RecordingNext(state.toggleWearSensor(intent.sensorId))
+ is RecordingIntent.ToggleWatchSensor -> RecordingNext(state.toggleWatchSensor(intent.sensorId))
else -> null
}
@@ -191,7 +189,9 @@ object RecordingReducer {
state.copy(activityRecognitionPeriodSeconds = intent.seconds.coerceAtLeast(1)),
)
- is RecordingIntent.SetSignificantMotion -> RecordingNext(state.copy(significantMotion = intent.enabled))
+ is RecordingIntent.SetSignificantMotionRecording -> RecordingNext(
+ state.copy(significantMotion = intent.enabled),
+ )
else -> RecordingNext(state)
}
@@ -202,9 +202,9 @@ object RecordingReducer {
return copy(selectedSensorIds = updated, message = RecordingMessage.NONE, errorCode = null)
}
- private fun RecordingState.toggleWearSensor(sensorId: Int): RecordingState {
- val updated = selectedWearSensorIds.toMutableSet()
+ private fun RecordingState.toggleWatchSensor(sensorId: Int): RecordingState {
+ val updated = selectedWatchSensorIds.toMutableSet()
if (!updated.add(sensorId)) updated.remove(sensorId)
- return copy(selectedWearSensorIds = updated, message = RecordingMessage.NONE, errorCode = null)
+ return copy(selectedWatchSensorIds = updated, message = RecordingMessage.NONE, errorCode = null)
}
}
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/MeasurementSetupScreen.kt b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/RecordingSetupScreen.kt
similarity index 87%
rename from app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/MeasurementSetupScreen.kt
rename to app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/RecordingSetupScreen.kt
index be15281..ade1f46 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/MeasurementSetupScreen.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/RecordingSetupScreen.kt
@@ -22,7 +22,7 @@ import androidx.compose.ui.unit.dp
import com.tomasrepcik.sensorbox.R
@Composable
-fun MeasurementSetupScreen(
+fun RecordingSetupScreen(
state: RecordingState,
onIntent: (RecordingIntent) -> Unit,
modifier: Modifier = Modifier,
@@ -30,22 +30,22 @@ fun MeasurementSetupScreen(
) {
Column(modifier.fillMaxSize()) {
Box(Modifier.weight(1f)) {
- MeasurementSetupContent(state, onIntent, onBack)
+ RecordingSetupContent(state, onIntent, onBack)
}
- MeasurementSetupActionBar(state, onIntent)
+ RecordingSetupTopBar(state, onIntent)
}
}
@Composable
-private fun MeasurementSetupContent(state: RecordingState, onIntent: (RecordingIntent) -> Unit, onBack: () -> Unit) {
+private fun RecordingSetupContent(state: RecordingState, onIntent: (RecordingIntent) -> Unit, onBack: () -> Unit) {
SensorBoxBackScreen(
- title = stringResource(R.string.measurement_setup),
+ title = stringResource(R.string.recording_setup),
onBack = onBack,
bottomPadding = 24.dp,
itemSpacing = 0.dp,
) {
item { SensorBoxSettingsSection(stringResource(R.string.setup_storage_category)) }
- item { StorageSetupPanel(state.storagePath) { onIntent(RecordingIntent.ChooseStorage) } }
+ item { RecordingArchivePanel(state.recordingArchivePath) { onIntent(RecordingIntent.ChooseRecordingArchive) } }
item { SensorBoxSettingsSection(stringResource(R.string.setup_details_category)) }
item { MeasurementNameSetup(state, onIntent) }
item { NotesAndAlarmsSetup(state, onIntent) }
@@ -95,7 +95,7 @@ private fun RecordingTimingSetup(state: RecordingState, onIntent: (RecordingInte
86_400,
) { onIntent(RecordingIntent.SetStartDelay(it)) }
StepSetting(
- stringResource(R.string.measurement_duration),
+ stringResource(R.string.recording_duration),
state.durationSeconds,
pluralStringResource(R.plurals.seconds_count, state.durationSeconds, state.durationSeconds),
0,
@@ -110,8 +110,8 @@ private fun NotesAndAlarmsSetup(state: RecordingState, onIntent: (RecordingInten
value = state.notes,
onValueChange = { onIntent(RecordingIntent.SetNotes(it)) },
modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp),
- label = { Text(stringResource(R.string.measurement_notes)) },
- supportingText = { Text(stringResource(R.string.measurement_notes_description)) },
+ label = { Text(stringResource(R.string.recording_notes)) },
+ supportingText = { Text(stringResource(R.string.recording_notes_description)) },
minLines = 2,
)
SensorBoxSettingsDivider()
@@ -151,20 +151,20 @@ private fun SpecializedSourcesSetup(state: RecordingState, onIntent: (RecordingI
title = stringResource(R.string.significant_motion),
description = stringResource(R.string.significant_motion_description),
checked = state.significantMotion,
- ) { onIntent(RecordingIntent.SetSignificantMotion(it)) }
+ ) { onIntent(RecordingIntent.SetSignificantMotionRecording(it)) }
}
}
@Composable
-private fun StorageSetupPanel(path: String?, onChoose: () -> Unit) {
+private fun RecordingArchivePanel(path: String?, onChoose: () -> Unit) {
Row(
Modifier.fillMaxWidth().padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(3.dp)) {
- Text(stringResource(R.string.recording_folder), style = MaterialTheme.typography.titleMedium)
+ Text(stringResource(R.string.recording_archive), style = MaterialTheme.typography.titleMedium)
Text(
- path ?: stringResource(R.string.choose_recording_folder),
+ path ?: stringResource(R.string.choose_recording_archive),
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
@@ -182,8 +182,8 @@ private fun BatterySetup(state: RecordingState, onIntent: (RecordingIntent) -> U
BooleanSetting(
title = stringResource(R.string.battery_guard),
description = stringResource(R.string.battery_guard_setup_description),
- checked = state.preferences.recording.restrictMeasurementOnLowBattery,
- ) { onIntent(RecordingIntent.SetLowBatteryRestriction(it)) }
+ checked = state.preferences.recording.stopRecordingOnLowBattery,
+ ) { onIntent(RecordingIntent.SetStopOnLowBattery(it)) }
}
@Composable
@@ -239,7 +239,7 @@ private fun GpsDistanceSetup(state: RecordingState, onIntent: (RecordingIntent)
}
@Composable
-private fun MeasurementSetupActionBar(
+private fun RecordingSetupTopBar(
state: RecordingState,
onIntent: (RecordingIntent) -> Unit,
modifier: Modifier = Modifier,
@@ -258,11 +258,11 @@ private fun MeasurementSetupActionBar(
label = state.startCountdownSeconds?.let { seconds ->
pluralStringResource(R.plurals.starting_in_seconds, seconds, seconds)
} ?: stringResource(
- if (state.isStarting) R.string.starting_recording else R.string.start_measurement,
+ if (state.isStarting) R.string.starting_recording else R.string.start_recording,
),
- onClick = { onIntent(RecordingIntent.StartMeasurement) },
+ onClick = { onIntent(RecordingIntent.StartRecording) },
modifier = Modifier.fillMaxWidth(),
- enabled = state.storagePath != null && sourceCount > 0 && !state.isStarting,
+ enabled = state.recordingArchivePath != null && sourceCount > 0 && !state.isStarting,
loading = state.isStarting,
)
}
@@ -270,6 +270,6 @@ private fun MeasurementSetupActionBar(
}
private fun setupSourceCount(state: RecordingState): Int =
- state.selectedSensorIds.size + state.selectedWearSensorIds.size +
- (if (state.includesGps) 1 else 0) + (if (state.wearIncludesGps) 1 else 0) +
+ state.selectedSensorIds.size + state.selectedWatchSensorIds.size +
+ (if (state.includesGps) 1 else 0) + (if (state.watchIncludesGps) 1 else 0) +
(if (state.activityRecognition) 1 else 0) + (if (state.significantMotion) 1 else 0)
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/RecordingViewModel.kt b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/RecordingViewModel.kt
index b1510b6..534c40a 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/RecordingViewModel.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/RecordingViewModel.kt
@@ -8,15 +8,15 @@ import com.tomasrepcik.sensorbox.core.error.AppErrorCode
import com.tomasrepcik.sensorbox.core.error.AppResult
import com.tomasrepcik.sensorbox.core.preferences.AppPreferencesIntent
import com.tomasrepcik.sensorbox.core.preferences.AppPreferencesRepository
-import com.tomasrepcik.sensorbox.domain.measurement.DocumentStorageGateway
-import com.tomasrepcik.sensorbox.domain.measurement.MeasurementPermissionsUseCase
-import com.tomasrepcik.sensorbox.domain.measurement.MeasurementRequest
-import com.tomasrepcik.sensorbox.domain.measurement.RecordingControlUseCase
+import com.tomasrepcik.sensorbox.domain.recording.RecordingArchiveRepository
+import com.tomasrepcik.sensorbox.domain.recording.RecordingControlUseCase
+import com.tomasrepcik.sensorbox.domain.recording.RecordingPermissionsUseCase
+import com.tomasrepcik.sensorbox.domain.recording.RecordingSetup
import com.tomasrepcik.sensorbox.domain.sensors.AvailableSensorsUseCase
-import com.tomasrepcik.sensorbox.domain.sensors.WearSensorCatalogStore
+import com.tomasrepcik.sensorbox.domain.sensors.WatchSensorCatalogStore
import com.tomasrepcik.sensorbox.domain.sensors.toSensorDescriptor
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementSessionState
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementSessionStore
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionState
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionStore
import com.tomasrepcik.sensorbox.wearoslib.WearOsConstants.WEAR_APP_CAPABILITY
import com.tomasrepcik.sensorbox.wearoslib.WearOsConstants.WEAR_MESSAGE_PATH
import com.tomasrepcik.sensorbox.wearoslib.connectivity.ObserveWearCapabilityUseCase
@@ -42,13 +42,13 @@ import javax.inject.Inject
class RecordingViewModel @Inject constructor(
private val preferencesRepository: AppPreferencesRepository,
private val availableSensors: AvailableSensorsUseCase,
- private val storage: DocumentStorageGateway,
- private val permissions: MeasurementPermissionsUseCase,
+ private val recordingArchive: RecordingArchiveRepository,
+ private val permissions: RecordingPermissionsUseCase,
private val recording: RecordingControlUseCase,
- private val sessionStore: MeasurementSessionStore,
- private val observeWearCapability: ObserveWearCapabilityUseCase,
- private val sendWearCommand: SendWearCommandUseCase,
- private val wearSensorCatalog: WearSensorCatalogStore,
+ private val sessionStore: RecordingSessionStore,
+ private val observeWatchCapability: ObserveWearCapabilityUseCase,
+ private val sendWatchCommand: SendWearCommandUseCase,
+ private val watchSensorCatalog: WatchSensorCatalogStore,
private val elapsedRealtimeClock: ElapsedRealtimeClock,
) : ViewModel() {
private val mutableState = MutableStateFlow(initialState())
@@ -61,16 +61,16 @@ class RecordingViewModel @Inject constructor(
init {
observePreferences()
- observeMeasurementSession()
- observeWearConnection()
- observeWearSensors()
+ observeRecordingSession()
+ observeWatchConnection()
+ observeWatchSensors()
}
fun accept(intent: RecordingIntent) {
when (intent) {
- RecordingIntent.StartMeasurement -> startMeasurement()
+ RecordingIntent.StartRecording -> startRecording()
- RecordingIntent.StopMeasurement -> stopMeasurement()
+ RecordingIntent.StopRecording -> stopRecording()
is RecordingIntent.AddAnnotation -> recording.annotate(intent.text).showFailure()
@@ -78,8 +78,8 @@ class RecordingViewModel @Inject constructor(
AppPreferencesIntent.SetSensorSamplingPeriod(intent.index),
)
- is RecordingIntent.SetLowBatteryRestriction -> updatePreference(
- AppPreferencesIntent.SetLowBatteryRestriction(intent.enabled),
+ is RecordingIntent.SetStopOnLowBattery -> updatePreference(
+ AppPreferencesIntent.SetStopOnLowBattery(intent.enabled),
)
is RecordingIntent.SetWakeLock -> updatePreference(AppPreferencesIntent.SetWakeLock(intent.enabled))
@@ -96,22 +96,22 @@ class RecordingViewModel @Inject constructor(
}
}
- fun handleStorageResult(resultIntent: Intent?) {
- val persisted = resultIntent?.let(storage::persist) ?: AppResult.failure(
- AppError(AppErrorCode.STORAGE, "Select recording storage directory"),
+ fun handleRecordingArchiveResult(resultIntent: Intent?) {
+ val persisted = resultIntent?.let(recordingArchive::select) ?: AppResult.failure(
+ AppError(AppErrorCode.STORAGE, "Select recording archive"),
)
mutableState.value = state.value.copy(
- storagePath = storage.displayPath().getOrNull(),
- message = if (persisted.isSuccess) RecordingMessage.NONE else RecordingMessage.STORAGE_REQUIRED,
+ recordingArchivePath = recordingArchive.path().getOrNull(),
+ message = if (persisted.isSuccess) RecordingMessage.NONE else RecordingMessage.RECORDING_ARCHIVE_REQUIRED,
errorCode = persisted.errorOrNull()?.code,
)
}
fun handlePermissionResult() {
- val request = state.value.toMeasurementRequest()
+ val request = state.value.toRecordingSetup()
val missing = permissions.missingPermissions(request)
if (missing.isEmpty()) {
- startMeasurement()
+ startRecording()
} else {
showMessage(RecordingMessage.PERMISSION_REQUIRED, AppErrorCode.PERMISSION)
}
@@ -119,7 +119,7 @@ class RecordingViewModel @Inject constructor(
private fun initialState() = RecordingState(
sensors = availableSensors(),
- storagePath = storage.displayPath().getOrNull(),
+ recordingArchivePath = recordingArchive.path().getOrNull(),
)
private fun observePreferences() {
@@ -134,7 +134,7 @@ class RecordingViewModel @Inject constructor(
},
onFailure = { error ->
mutableState.value = state.value.copy(
- message = RecordingMessage.MEASUREMENT_FAILED,
+ message = RecordingMessage.RECORDING_FAILED,
errorCode = error.code,
)
},
@@ -143,7 +143,7 @@ class RecordingViewModel @Inject constructor(
}
}
- private fun observeMeasurementSession() {
+ private fun observeRecordingSession() {
viewModelScope.launch {
sessionStore.state.collect { session ->
onSessionChanged(session)
@@ -151,10 +151,10 @@ class RecordingViewModel @Inject constructor(
}
}
- private fun onSessionChanged(session: MeasurementSessionState) {
+ private fun onSessionChanged(session: RecordingSessionState) {
elapsedJob?.cancel()
- mutableState.value = RecordingReducer.measurementSessionChanged(state.value, session)
- if (session is MeasurementSessionState.Running) startElapsedTicker(session.startedAtElapsedRealtime)
+ mutableState.value = RecordingReducer.recordingSessionChanged(state.value, session)
+ if (session is RecordingSessionState.Running) startElapsedTicker(session.startedAtElapsedRealtime)
}
private fun startElapsedTicker(startedAt: Long) {
@@ -167,35 +167,35 @@ class RecordingViewModel @Inject constructor(
}
}
- private fun observeWearConnection() {
+ private fun observeWatchConnection() {
viewModelScope.launch {
- observeWearCapability(WEAR_APP_CAPABILITY)
+ observeWatchCapability(WEAR_APP_CAPABILITY)
.catch { error ->
AppError.from(AppErrorCode.CONNECTIVITY, "Observe Wear connection", error)
emit(WearConnection.Disconnected)
}
.collect { connection ->
if (connection is WearConnection.Connected) {
- sendWearCommand(
+ sendWatchCommand(
WEAR_APP_CAPABILITY,
WEAR_MESSAGE_PATH,
WearCommand.RequestAvailableSensors,
)
} else {
- wearSensorCatalog.clear()
+ watchSensorCatalog.clear()
}
}
}
}
- private fun observeWearSensors() {
+ private fun observeWatchSensors() {
viewModelScope.launch {
- combine(wearSensorCatalog.sensors, wearSensorCatalog.isAvailable) { sensors, isAvailable ->
+ combine(watchSensorCatalog.sensors, watchSensorCatalog.isAvailable) { sensors, isAvailable ->
sensors to isAvailable
}.collect { (sensors, isAvailable) ->
mutableState.value = state.value.copy(
- isWearConnected = isAvailable,
- wearSensors = sensors.map { sensor -> sensor.toSensorDescriptor() },
+ isWatchConnected = isAvailable,
+ watchSensors = sensors.map { sensor -> sensor.toSensorDescriptor() },
)
}
}
@@ -208,16 +208,16 @@ class RecordingViewModel @Inject constructor(
}
@Suppress("ReturnCount")
- private fun startMeasurement() {
+ private fun startRecording() {
if (state.value.isStarting) return
- val request = state.value.toMeasurementRequest()
+ val request = state.value.toRecordingSetup()
if (!request.hasAnySource()) {
showMessage(RecordingMessage.PICK_AT_LEAST_ONE_SOURCE, AppErrorCode.VALIDATION)
return
}
- if (storage.hasStorage().getOrNull() != true) {
- reduce(RecordingIntent.ChooseStorage)
- showMessage(RecordingMessage.STORAGE_REQUIRED, AppErrorCode.STORAGE)
+ if (recordingArchive.isSelected().getOrNull() != true) {
+ reduce(RecordingIntent.ChooseRecordingArchive)
+ showMessage(RecordingMessage.RECORDING_ARCHIVE_REQUIRED, AppErrorCode.STORAGE)
return
}
requestMissingPermissions(request)?.let {
@@ -225,21 +225,21 @@ class RecordingViewModel @Inject constructor(
mutableState.value = state.value.copy(errorCode = AppErrorCode.PERMISSION)
return
}
- startForegroundMeasurement(request, state.value.startDelaySeconds)
+ startRecordingAfterDelay(request, state.value.startDelaySeconds)
}
- private fun requestMissingPermissions(request: MeasurementRequest): Set? {
+ private fun requestMissingPermissions(request: RecordingSetup): Set? {
val missingPermissions = permissions.missingPermissions(request)
return missingPermissions.takeIf(Set::isNotEmpty)
}
- private fun startForegroundMeasurement(request: MeasurementRequest, countdownSeconds: Int) {
- mutableState.value = RecordingReducer.measurementStartRequested(state.value, countdownSeconds)
+ private fun startRecordingAfterDelay(request: RecordingSetup, countdownSeconds: Int) {
+ mutableState.value = RecordingReducer.recordingStartRequested(state.value, countdownSeconds)
startJob = viewModelScope.launch {
waitForStartCountdown(countdownSeconds)
val result = recording.start(request)
if (result.isFailure) {
- mutableState.value = RecordingReducer.measurementStartFailed(state.value)
+ mutableState.value = RecordingReducer.recordingStartFailed(state.value)
showFailure(result.errorOrNull())
}
}
@@ -247,17 +247,17 @@ class RecordingViewModel @Inject constructor(
private suspend fun waitForStartCountdown(seconds: Int) {
for (remaining in seconds downTo 1) {
- mutableState.value = RecordingReducer.measurementCountdownChanged(state.value, remaining)
+ mutableState.value = RecordingReducer.recordingCountdownChanged(state.value, remaining)
delay(1_000L)
}
- mutableState.value = RecordingReducer.measurementCountdownChanged(state.value, null)
+ mutableState.value = RecordingReducer.recordingCountdownChanged(state.value, null)
}
- private fun stopMeasurement() {
- if (state.value.isStarting && state.value.session is MeasurementSessionState.Idle) {
+ private fun stopRecording() {
+ if (state.value.isStarting && state.value.session is RecordingSessionState.Idle) {
startJob?.cancel()
startJob = null
- mutableState.value = RecordingReducer.measurementStartFailed(state.value)
+ mutableState.value = RecordingReducer.recordingStartFailed(state.value)
return
}
viewModelScope.launch {
@@ -285,17 +285,17 @@ class RecordingViewModel @Inject constructor(
private fun showFailure(error: AppError?) {
mutableState.value = state.value.copy(
message = when {
- error?.code == AppErrorCode.PERMISSION && error.context["source"] == "wear" ->
- RecordingMessage.WEAR_PERMISSION_REQUIRED
+ error?.code == AppErrorCode.PERMISSION && error.context["source"] == "watch" ->
+ RecordingMessage.WATCH_PERMISSION_REQUIRED
error?.code == AppErrorCode.PERMISSION -> RecordingMessage.PERMISSION_REQUIRED
- else -> RecordingMessage.MEASUREMENT_FAILED
+ else -> RecordingMessage.RECORDING_FAILED
},
errorCode = error?.code ?: AppErrorCode.UNKNOWN,
)
}
- private fun MeasurementRequest.hasAnySource(): Boolean = sensorIds.isNotEmpty() || includesGps ||
- wearSensorIds.isNotEmpty() || wearIncludesGps || activityRecognition || significantMotion
+ private fun RecordingSetup.hasAnySource(): Boolean = sensorIds.isNotEmpty() || includesGps ||
+ watchSensorIds.isNotEmpty() || watchIncludesGps || activityRecognition || significantMotion
}
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SensorBoxApp.kt b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SensorBoxApp.kt
index 732cddb..2aa05f3 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SensorBoxApp.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SensorBoxApp.kt
@@ -31,7 +31,7 @@ import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.ui.NavDisplay
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementSessionState
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionState
@Composable
fun SensorBoxApp(
@@ -50,7 +50,7 @@ fun SensorBoxApp(
Box(Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background))
return
}
- if (mainState.keepScreenAwake && mainState.session is MeasurementSessionState.Running) KeepScreenAwake()
+ if (mainState.keepScreenAwake && mainState.session is RecordingSessionState.Running) KeepScreenAwake()
SensorBoxNavHost(
mainState = mainState,
onboardingState = onboardingState,
@@ -108,8 +108,8 @@ private fun SensorBoxNavHost(
)
}
-private fun MainState.displayedRoute() = if (session is MeasurementSessionState.Running) {
- MainRoute.ACTIVE_MEASUREMENT
+private fun MainState.displayedRoute() = if (session is RecordingSessionState.Running) {
+ MainRoute.ACTIVE_RECORDING
} else {
route
}
@@ -117,7 +117,7 @@ private fun MainState.displayedRoute() = if (session is MeasurementSessionState.
@Composable
private fun SynchronizeBackStack(backStack: NavBackStack, displayedRoute: MainRoute) {
LaunchedEffect(displayedRoute) {
- if (displayedRoute != MainRoute.ACTIVE_MEASUREMENT && backStack.lastOrNull() == MainRoute.ACTIVE_MEASUREMENT) {
+ if (displayedRoute != MainRoute.ACTIVE_RECORDING && backStack.lastOrNull() == MainRoute.ACTIVE_RECORDING) {
backStack.removeLastOrNull()
}
if (backStack.lastOrNull() != displayedRoute) {
@@ -128,10 +128,10 @@ private fun SynchronizeBackStack(backStack: NavBackStack, displayedRoute
}
private fun navigateBack(state: MainState, backStack: NavBackStack, onNavigate: (MainRoute) -> Unit) {
- if (state.session is MeasurementSessionState.Running) return
+ if (state.session is RecordingSessionState.Running) return
if (backStack.size > 1) backStack.removeLastOrNull()
val destination = backStack.lastOrNull() as? MainRoute ?: MainRoute.RECORD
- if (destination != MainRoute.ACTIVE_MEASUREMENT) onNavigate(destination)
+ if (destination != MainRoute.ACTIVE_RECORDING) onNavigate(destination)
}
private fun MainRoute.isRootDestination() = when (this) {
@@ -139,10 +139,10 @@ private fun MainRoute.isRootDestination() = when (this) {
MainRoute.RECORD,
-> true
- MainRoute.ACTIVE_MEASUREMENT,
+ MainRoute.ACTIVE_RECORDING,
MainRoute.SENSOR_DETAILS,
MainRoute.SENSOR_PREVIEW,
- MainRoute.SETUP,
+ MainRoute.RECORDING_SETUP,
MainRoute.MEASUREMENTS,
MainRoute.MEASUREMENT_DETAILS,
MainRoute.MEASUREMENT_FILE,
@@ -169,8 +169,8 @@ private fun RouteContent(
when (route) {
MainRoute.ONBOARDING -> OnboardingScreen(onboardingState, onOnboardingIntent)
- MainRoute.ACTIVE_MEASUREMENT -> FullScreen { modifier ->
- ActiveMeasurementScreen(recordingState, onRecordingIntent, modifier)
+ MainRoute.ACTIVE_RECORDING -> FullScreen { modifier ->
+ ActiveRecordingScreen(recordingState, onRecordingIntent, modifier)
}
MainRoute.SENSOR_DETAILS -> FullScreen { modifier ->
@@ -186,8 +186,8 @@ private fun RouteContent(
SensorPreviewScreen(state = recordingState, onBack = onBack, modifier = modifier)
}
- MainRoute.SETUP -> FullScreen { modifier ->
- MeasurementSetupScreen(
+ MainRoute.RECORDING_SETUP -> FullScreen { modifier ->
+ RecordingSetupScreen(
state = recordingState,
onIntent = onRecordingIntent,
modifier = modifier,
@@ -242,7 +242,7 @@ private fun MeasurementBrowserRoute(
MainRoute.MEASUREMENTS -> MeasurementsScreen(state, onIntent, onBack, modifier)
MainRoute.MEASUREMENT_DETAILS -> MeasurementDetailsScreen(state, onIntent, onBack, modifier)
MainRoute.MEASUREMENT_FILE -> MeasurementFileScreen(state, onIntent, onBack, modifier)
- else -> error("Not a measurement archive route")
+ else -> error("Not a measurement browser route")
}
}
}
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SensorBoxComponents.kt b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SensorBoxComponents.kt
index 2a440ab..db492b1 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SensorBoxComponents.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SensorBoxComponents.kt
@@ -216,7 +216,7 @@ fun SensorBoxSecondaryButton(
}
@Composable
-fun SensorBoxBottomAction(
+fun SensorBoxBottomButton(
title: String,
description: String,
buttonLabel: String,
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SensorDetailsScreen.kt b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SensorDetailsScreen.kt
index 2f4c465..93c981f 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SensorDetailsScreen.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SensorDetailsScreen.kt
@@ -47,9 +47,9 @@ fun SensorDetailsScreen(
modifier: Modifier = Modifier,
) {
val sensor = state.detailsSensorType?.let { type ->
- val sensors = when (state.detailsSensorSource) {
- RecordingSensorSource.PHONE -> state.sensors
- RecordingSensorSource.WEAR -> state.wearSensors
+ val sensors = when (state.detailsDevice) {
+ RecordingDevice.PHONE -> state.sensors
+ RecordingDevice.WATCH -> state.watchSensors
}
sensors.firstOrNull { it.type == type }
}
@@ -59,12 +59,12 @@ fun SensorDetailsScreen(
sensor != null -> sensor.name
else -> stringResource(R.string.sensor_unavailable)
}
- val showPreview = state.detailsSensorSource == RecordingSensorSource.PHONE &&
+ val showPreview = state.detailsDevice == RecordingDevice.PHONE &&
(state.detailsSensorType == null || sensor != null) && canPreview
Box(modifier.fillMaxSize()) {
SensorDetailsList(state, sensor, title, showPreview, onBack)
if (showPreview) {
- PreviewAction(onPreview, Modifier.align(Alignment.BottomCenter))
+ PreviewButton(onPreview, Modifier.align(Alignment.BottomCenter))
}
}
}
@@ -142,7 +142,7 @@ private fun GpsDetails(state: RecordingState) {
GpsPermissionStatus(details.hasPermission)
GpsDetailRows(details, state, unavailableValue)
if (!details.hasPermission) {
- GpsPermissionAction {
+ GpsPermissionButton {
permissionRequest.launch(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
@@ -195,7 +195,7 @@ private fun localizedValue(value: Number?, unavailableValue: String): String =
@Composable
internal fun GpsPermission(hasPermission: Boolean, onRequest: () -> Unit) {
GpsPermissionStatus(hasPermission)
- if (!hasPermission) GpsPermissionAction(onRequest)
+ if (!hasPermission) GpsPermissionButton(onRequest)
}
@Composable
@@ -207,7 +207,7 @@ private fun GpsPermissionStatus(hasPermission: Boolean) {
}
@Composable
-private fun GpsPermissionAction(onRequest: () -> Unit) {
+private fun GpsPermissionButton(onRequest: () -> Unit) {
Text(
stringResource(R.string.location_permission_explanation),
color = MaterialTheme.colorScheme.error,
@@ -229,7 +229,7 @@ private fun ParameterRow(label: String, value: String, showDivider: Boolean = tr
}
@Composable
-private fun PreviewAction(onPreview: () -> Unit, modifier: Modifier = Modifier) {
+private fun PreviewButton(onPreview: () -> Unit, modifier: Modifier = Modifier) {
Surface(modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.background) {
Box(
Modifier.fillMaxWidth().padding(horizontal = 24.dp, vertical = 14.dp),
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SensorPreviewScreen.kt b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SensorPreviewScreen.kt
index 0ff584d..16cb86f 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SensorPreviewScreen.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SensorPreviewScreen.kt
@@ -50,7 +50,7 @@ import kotlin.math.max
@Composable
fun SensorPreviewScreen(state: RecordingState, onBack: () -> Unit, modifier: Modifier = Modifier) {
- if (state.detailsSensorSource == RecordingSensorSource.WEAR) {
+ if (state.detailsDevice == RecordingDevice.WATCH) {
SensorBoxBackScreen(
title = stringResource(R.string.sensor_unavailable),
onBack = onBack,
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SettingsContract.kt b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SettingsContract.kt
index a951f4f..56aecc4 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SettingsContract.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SettingsContract.kt
@@ -13,7 +13,7 @@ data class SettingsState(
sealed interface SettingsIntent {
data class SetSamplingPeriod(val index: Int) : SettingsIntent
- data class SetLowBatteryRestriction(val enabled: Boolean) : SettingsIntent
+ data class SetStopOnLowBattery(val enabled: Boolean) : SettingsIntent
data class SetWakeLock(val enabled: Boolean) : SettingsIntent
data class SetKeepScreenAwake(val enabled: Boolean) : SettingsIntent
data class SetGpsInterval(val seconds: Int) : SettingsIntent
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SettingsScreen.kt b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SettingsScreen.kt
index 75237eb..8e1e8ed 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SettingsScreen.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SettingsScreen.kt
@@ -58,7 +58,7 @@ fun SettingsScreen(
val isBatteryOptimizationExempt = rememberBatteryOptimizationExemption()
LaunchedEffect(Unit) { onIntent(SettingsIntent.ViewDiagnostics) }
SensorBoxBackScreen(
- title = stringResource(R.string.measurement_settings),
+ title = stringResource(R.string.recording_settings),
onBack = onBack,
modifier = modifier,
itemSpacing = 0.dp,
@@ -121,15 +121,15 @@ private fun DiagnosticsSetting(state: SettingsState, onIntent: (SettingsIntent)
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
} else {
- SettingsActionRow(
+ SettingsControlRow(
title = stringResource(R.string.diagnostics_view),
description = stringResource(R.string.diagnostics_view_summary),
) { onIntent(SettingsIntent.Navigate(MainRoute.DIAGNOSTICS)) }
- SettingsActionRow(
+ SettingsControlRow(
title = stringResource(R.string.diagnostics_share),
description = stringResource(R.string.diagnostics_share_summary),
) { onIntent(SettingsIntent.ShareDiagnosticsFile) }
- SettingsActionRow(
+ SettingsControlRow(
title = stringResource(R.string.diagnostics_clear),
description = stringResource(R.string.diagnostics_clear_summary),
showDivider = false,
@@ -259,8 +259,8 @@ private fun BatteryGuardSetting(state: SettingsState, onIntent: (SettingsIntent)
BooleanSetting(
title = stringResource(R.string.battery_guard),
description = stringResource(R.string.battery_guard_settings_description),
- checked = state.preferences.recording.restrictMeasurementOnLowBattery,
- ) { onIntent(SettingsIntent.SetLowBatteryRestriction(it)) }
+ checked = state.preferences.recording.stopRecordingOnLowBattery,
+ ) { onIntent(SettingsIntent.SetStopOnLowBattery(it)) }
}
@Composable
@@ -306,7 +306,7 @@ private fun BatteryOptimizationSetting(isExempt: Boolean, onIntent: (SettingsInt
private fun AboutSetting(onIntent: (SettingsIntent) -> Unit) {
var showAboutDialog by rememberSaveable { mutableStateOf(false) }
- SettingsActionRow(
+ SettingsControlRow(
title = stringResource(R.string.menu_about),
description = stringResource(R.string.about_summary),
showDivider = false,
@@ -411,7 +411,7 @@ fun BooleanSetting(title: String, description: String, checked: Boolean, onCheck
}
@Composable
-private fun SettingsActionRow(
+private fun SettingsControlRow(
title: String,
description: String,
showDivider: Boolean = true,
diff --git a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SettingsViewModel.kt b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SettingsViewModel.kt
index 9002d9a..f86f7be 100644
--- a/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SettingsViewModel.kt
+++ b/app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/SettingsViewModel.kt
@@ -69,7 +69,7 @@ class SettingsViewModel @Inject constructor(
is SettingsIntent.Navigate -> mutableEffects.trySend(SettingsEffect.Navigate(intent.route))
is SettingsIntent.SetSamplingPeriod,
- is SettingsIntent.SetLowBatteryRestriction,
+ is SettingsIntent.SetStopOnLowBattery,
is SettingsIntent.SetWakeLock,
is SettingsIntent.SetKeepScreenAwake,
is SettingsIntent.SetGpsInterval,
@@ -82,7 +82,7 @@ class SettingsViewModel @Inject constructor(
private fun SettingsIntent.toPreferencesIntent(): AppPreferencesIntent? = when (this) {
is SettingsIntent.SetSamplingPeriod -> AppPreferencesIntent.SetSensorSamplingPeriod(index)
- is SettingsIntent.SetLowBatteryRestriction -> AppPreferencesIntent.SetLowBatteryRestriction(enabled)
+ is SettingsIntent.SetStopOnLowBattery -> AppPreferencesIntent.SetStopOnLowBattery(enabled)
is SettingsIntent.SetWakeLock -> AppPreferencesIntent.SetWakeLock(enabled)
is SettingsIntent.SetKeepScreenAwake -> AppPreferencesIntent.SetKeepPhoneDisplayOn(enabled)
is SettingsIntent.SetGpsInterval -> AppPreferencesIntent.SetGpsInterval(seconds)
@@ -122,7 +122,7 @@ class SettingsViewModel @Inject constructor(
withDiagnostics { mutableEffects.send(SettingsEffect.ShareDiagnosticsFile) }
}
- private fun withDiagnostics(action: suspend () -> Unit) {
+ private fun withDiagnostics(operation: suspend () -> Unit) {
viewModelScope.launch(ioDispatcher) {
when (val result = diagnosticsStore.readText()) {
is AppResult.Success -> {
@@ -131,7 +131,7 @@ class SettingsViewModel @Inject constructor(
diagnosticsLoaded = true,
errorCode = null,
)
- if (result.value.isNotBlank()) action()
+ if (result.value.isNotBlank()) operation()
}
is AppResult.Failure -> fail(result.error.code)
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6fd53c5..93a1c96 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -29,7 +29,7 @@
Nothing is going out
Measurements stay locally on your devices. SensorBox has no analytics, cloud upload, or account requirement.
Privacy and terms
- Review the policies before continuing. SensorBox only records when you explicitly start a measurement.
+ Review the policies before continuing. SensorBox only records when you explicitly start recording.
Privacy policy
Terms of use
Android may pause recordings
@@ -37,14 +37,14 @@
Allow reliable background work
You can ask Android to exempt SensorBox from battery optimization. This is optional and can be changed later in system settings.
Review battery settings
- Choose a recording folder
- Android’s folder picker gives SensorBox access only to the location you choose. A folder is required before finishing setup.
- Choose folder
- Change folder
- Selected folder: %1$s
+ Choose a recording archive
+ Android’s folder picker gives SensorBox access only to the directory you choose as the recording archive. An archive is required before finishing setup.
+ Choose archive
+ Change archive
+ Recording archive: %1$s
Back
Start SensorBox
- SensorBox does not collect or upload your sensor or GPS recordings. Your measurements stay in the folder you select. Recording begins only after your action and remains visible through a foreground-service notification.
+ SensorBox does not collect or upload your sensor or GPS recordings. Your measurements stay in the recording archive you select. Recording begins only after your intent and remains visible through a foreground-service notification.
https://tomasrepcik.dev/sensorbox/terms-of-use
https://tomasrepcik.dev/sensorbox/privacy-policy
@@ -71,16 +71,16 @@
Privacy and health data
Why SensorBox requests access
Sensor and location permissions are requested only after you select those sources and tap Start recording.
- Recordings are written to the folder you choose through Android’s system document picker.
+ Measurements are written to the recording archive you choose through Android’s system document picker.
SensorBox does not upload recorded sensor or location data.
Pick sensors
Phone and Wear OS ready
- Choose signals for this measurement
+ Choose recording sources
Options
Sensors
Phone
- Wear OS
+ Wear OS
%1$d of %2$d selected
Device location
Information about GPS
@@ -98,14 +98,14 @@
Select a recording source
Pick at least one sensor or GPS.
- Recording folder required
- Choose a folder before recording.
+ Recording archive required
+ Choose a recording archive before recording.
Permission required
Grant the required permission, then try again.
Permission required on Wear OS
Open SensorBox on your watch, grant Location permission, then try again.
- Recording could not start
- Check diagnostics for details, then try again.
+ Recording could not start
+ Check diagnostics for details, then try again.
Measurements
Measurement details
@@ -122,8 +122,8 @@
GPS coordinates
Text data
Loading measurements…
- The selected recording folder could not be read.
- No measurements were found in the selected recording folder.
+ The recording archive could not be read.
+ No measurements were found in the recording archive.
%1$d chart samples
Zoom out
Show all
@@ -138,15 +138,15 @@
Altitude: %1$s m
Accuracy: %1$s m
Only the first 100,000 characters are shown.
- Measurement settings
- Control how measurements are recorded
- Measurement setup
+ Recording settings
+ Control how recordings run
+ Recording setup
%1$s ready
- Recording folder
- Choose where recordings are saved
- Folder required
+ Recording archive
+ Choose the phone directory for measurements
+ Recording archive required
Ready to record
- Start measurement
+ Start recording
Starting recording…
- Starting in %d second…
@@ -155,9 +155,9 @@
Measurement name
Optional folder-name prefix
Start delay
- Duration
- Notes
- One note per line; saved in extra.json
+ Duration
+ Notes
+ One note per line; saved in extra.json
Audible alarms
Seconds after start, separated by commas
Activity recognition
@@ -166,7 +166,7 @@
Significant motion
Record and re-arm the one-shot trigger sensor
Battery guard
- Stop a session when the battery becomes low
+ Stop recording when the battery becomes low
Stop when the battery becomes low
Keep CPU awake
Use a partial wake lock while recording
@@ -198,7 +198,7 @@
Details
Timing
Sources
- Markers
+ Markers
Theme
Choose how the app follows the light and dark theme
Automatic
@@ -291,6 +291,6 @@
Delete both retained diagnostic log files? This cannot be undone.
Diagnostics copied.
Diagnostics cleared.
- The diagnostics action failed.
+ The diagnostics operation failed.
Diagnostics could not be shared. The error was saved for a later attempt.
diff --git a/app/src/screenshotTest/kotlin/com/tomasrepcik/sensorbox/presentation/main/ReadmePhoneScreenshotPreviews.kt b/app/src/screenshotTest/kotlin/com/tomasrepcik/sensorbox/presentation/main/ReadmePhoneScreenshotPreviews.kt
index de8dd23..c86670e 100644
--- a/app/src/screenshotTest/kotlin/com/tomasrepcik/sensorbox/presentation/main/ReadmePhoneScreenshotPreviews.kt
+++ b/app/src/screenshotTest/kotlin/com/tomasrepcik/sensorbox/presentation/main/ReadmePhoneScreenshotPreviews.kt
@@ -22,8 +22,8 @@ fun readmeSourceSelection() = PhoneReadmeFrame {
@PreviewTest
@ReadmePhonePreview
@Composable
-fun readmeMeasurementSetup() = PhoneReadmeFrame {
- MeasurementSetupScreen(
+fun readmeRecordingSetup() = PhoneReadmeFrame {
+ RecordingSetupScreen(
state = readmeRecordingState().copy(selectedSensorIds = setOf(Sensor.TYPE_GRAVITY)),
onIntent = {},
onBack = {},
@@ -58,11 +58,11 @@ fun readmeIntroBattery() = OnboardingReadmePreview(page = 4)
@PreviewTest
@ReadmePhonePreview
@Composable
-fun readmeIntroStorage() = OnboardingReadmePreview(page = 5, storagePath = "Documents/SensorBox")
+fun readmeIntroStorage() = OnboardingReadmePreview(page = 5, recordingArchivePath = "Documents/SensorBox")
@Composable
-private fun OnboardingReadmePreview(page: Int, storagePath: String? = null) = PhoneReadmeFrame {
- OnboardingScreen(state = OnboardingState(page = page, storagePath = storagePath), onIntent = {})
+private fun OnboardingReadmePreview(page: Int, recordingArchivePath: String? = null) = PhoneReadmeFrame {
+ OnboardingScreen(state = OnboardingState(page = page, recordingArchivePath = recordingArchivePath), onIntent = {})
}
@Composable
@@ -82,7 +82,7 @@ private fun readmeRecordingState() = RecordingState(
SensorDescriptor(Sensor.TYPE_MAGNETIC_FIELD, "MMC56X3X Magnetometer", "MEMSIC"),
SensorDescriptor(Sensor.TYPE_ROTATION_VECTOR, "Rotation Vector Sensor", "Google"),
),
- storagePath = "Documents/SensorBox",
+ recordingArchivePath = "Documents/SensorBox",
)
@Preview(
diff --git a/app/src/test/java/com/tomasrepcik/sensorbox/domain/paired/PairedRecordingCoordinatorTest.kt b/app/src/test/java/com/tomasrepcik/sensorbox/domain/paired/PairedRecordingCoordinatorTest.kt
index 484872a..126848c 100644
--- a/app/src/test/java/com/tomasrepcik/sensorbox/domain/paired/PairedRecordingCoordinatorTest.kt
+++ b/app/src/test/java/com/tomasrepcik/sensorbox/domain/paired/PairedRecordingCoordinatorTest.kt
@@ -4,9 +4,9 @@ import com.tomasrepcik.sensorbox.core.error.AppError
import com.tomasrepcik.sensorbox.core.error.AppErrorCode
import com.tomasrepcik.sensorbox.core.error.AppResult
import com.tomasrepcik.sensorbox.core.error.DiagnosticLogger
-import com.tomasrepcik.sensorbox.domain.measurement.MeasurementRequest
-import com.tomasrepcik.sensorbox.domain.measurement.PhoneRecordingController
-import com.tomasrepcik.sensorbox.domain.measurement.StartedPhoneRecording
+import com.tomasrepcik.sensorbox.domain.recording.PhoneRecordingController
+import com.tomasrepcik.sensorbox.domain.recording.RecordingSetup
+import com.tomasrepcik.sensorbox.domain.recording.StartedPhoneRecording
import com.tomasrepcik.sensorbox.wearoslib.connectivity.SendWearMessageUseCase
import com.tomasrepcik.sensorbox.wearoslib.connectivity.WearConnection
import com.tomasrepcik.sensorbox.wearoslib.connectivity.WearConnectionRepository
@@ -14,7 +14,7 @@ import com.tomasrepcik.sensorbox.wearoslib.connectivity.WearNode
import com.tomasrepcik.sensorbox.wearoslib.protocol.SendWearCommandUseCase
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearCommand
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearCommandCodec
-import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingAction
+import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingOperation
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingOutcome
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearStopReason
import kotlinx.coroutines.flow.Flow
@@ -26,7 +26,7 @@ import org.junit.Test
class PairedRecordingCoordinatorTest {
@Test
- fun `Given phone sources only When recording starts Then no Wear command is sent`() = runTest {
+ fun `Given phone sources only When recording starts Then no watch command is sent`() = runTest {
// Given
val fixture = Fixture()
@@ -40,12 +40,12 @@ class PairedRecordingCoordinatorTest {
}
@Test
- fun `Given Wear sources When recording starts Then phone starts before direct Wear start`() = runTest {
+ fun `Given watch sources When recording starts Then phone starts before direct watch start`() = runTest {
// Given
val fixture = Fixture()
// When
- val result = fixture.coordinator.start(request(wearSensorIds = setOf(1)))
+ val result = fixture.coordinator.start(request(watchSensorIds = setOf(1)))
// Then
assertTrue(result.isSuccess)
@@ -60,23 +60,23 @@ class PairedRecordingCoordinatorTest {
// When
val result = fixture.coordinator.start(
- request(wearSensorIds = setOf(1), durationSeconds = 12),
+ request(watchSensorIds = setOf(1), durationSeconds = 12),
)
// Then
- val wearStart = fixture.repository.commands.single() as WearCommand.StartRecording
+ val watchStart = fixture.repository.commands.single() as WearCommand.StartRecording
assertTrue(result.isSuccess)
assertEquals(12_000L, fixture.phone.startedDurationMillis)
- assertEquals(fixture.phone.startedDurationMillis, wearStart.request.durationMillis)
+ assertEquals(fixture.phone.startedDurationMillis, watchStart.request.durationMillis)
}
@Test
- fun `Given Wear start fails When recording starts Then phone and Wear are stopped`() = runTest {
+ fun `Given watch start fails When recording starts Then phone and watch are stopped`() = runTest {
// Given
val fixture = Fixture().apply { repository.failStart = true }
// When
- val result = fixture.coordinator.start(request(wearSensorIds = setOf(1)))
+ val result = fixture.coordinator.start(request(watchSensorIds = setOf(1)))
// Then
assertTrue(result.isFailure)
@@ -85,13 +85,13 @@ class PairedRecordingCoordinatorTest {
}
@Test
- fun `Given Wear start result is lost When recording starts Then phone keeps recording`() = runTest {
+ fun `Given watch start result is lost When recording starts Then phone keeps recording`() = runTest {
// Given
val fixture = Fixture().apply { repository.dropStartResult = true }
// When
val result = fixture.coordinator.start(
- request(wearSensorIds = setOf(1), durationSeconds = 12),
+ request(watchSensorIds = setOf(1), durationSeconds = 12),
)
// Then
@@ -102,10 +102,10 @@ class PairedRecordingCoordinatorTest {
}
@Test
- fun `Given paired recording When stopped Then phone and Wear receive stop`() = runTest {
+ fun `Given paired recording When stopped Then phone and watch receive stop`() = runTest {
// Given
val fixture = Fixture()
- fixture.coordinator.start(request(wearSensorIds = setOf(1)))
+ fixture.coordinator.start(request(watchSensorIds = setOf(1)))
// When
val result = fixture.coordinator.stop()
@@ -132,7 +132,7 @@ class PairedRecordingCoordinatorTest {
}
@Test
- fun `Given active recording When stale Wear stop arrives Then phone keeps recording`() = runTest {
+ fun `Given active recording When stale watch stop arrives Then phone keeps recording`() = runTest {
// Given
val fixture = Fixture()
fixture.coordinator.start(request())
@@ -146,11 +146,11 @@ class PairedRecordingCoordinatorTest {
}
@Test
- fun `Given phone timer expires while Wear is unreachable When recording starts again Then phone is available`() =
+ fun `Given phone timer expires while watch is unreachable When recording starts again Then phone is available`() =
runTest {
// Given
val fixture = Fixture()
- fixture.coordinator.start(request(wearSensorIds = setOf(1), durationSeconds = 12))
+ fixture.coordinator.start(request(watchSensorIds = setOf(1), durationSeconds = 12))
val sessionId = (fixture.repository.commands.single() as WearCommand.StartRecording).sessionId
fixture.repository.failSends = true
@@ -169,7 +169,7 @@ class PairedRecordingCoordinatorTest {
assertEquals(2, fixture.phone.startCalls)
}
- private fun request(wearSensorIds: Set = emptySet(), durationSeconds: Int = 0) = MeasurementRequest(
+ private fun request(watchSensorIds: Set = emptySet(), durationSeconds: Int = 0) = RecordingSetup(
sensorIds = setOf(1),
includesGps = false,
samplingPeriodIndex = 0,
@@ -177,12 +177,12 @@ class PairedRecordingCoordinatorTest {
useWakeLock = false,
gpsIntervalSeconds = 10,
gpsMinDistanceMeters = 20,
- wearSensorIds = wearSensorIds,
+ watchSensorIds = watchSensorIds,
durationSeconds = durationSeconds,
)
private class Fixture {
- val inbox = WearRecordingResultInbox()
+ val inbox = WatchRecordingResultInbox()
val phone = FakePhoneRecordingController()
val repository = RecordingResultRepository(inbox)
val coordinator = PairedRecordingCoordinator(
@@ -199,7 +199,7 @@ private class FakePhoneRecordingController : PhoneRecordingController {
var stopCalls = 0
var startedDurationMillis = 0L
- override fun start(sessionId: String, request: MeasurementRequest): AppResult {
+ override fun start(sessionId: String, request: RecordingSetup): AppResult {
startCalls += 1
startedDurationMillis = request.durationSeconds.coerceAtLeast(0) * 1_000L
return AppResult.success(StartedPhoneRecording(sessionId, "fixture", startedDurationMillis))
@@ -218,7 +218,7 @@ private class FakePhoneRecordingController : PhoneRecordingController {
override fun annotate(text: String, timestampMillis: Long): AppResult = AppResult.success(Unit)
}
-private class RecordingResultRepository(private val inbox: WearRecordingResultInbox) : WearConnectionRepository {
+private class RecordingResultRepository(private val inbox: WatchRecordingResultInbox) : WearConnectionRepository {
val commands = mutableListOf()
var failStart = false
var failSends = false
@@ -239,26 +239,26 @@ private class RecordingResultRepository(private val inbox: WearRecordingResultIn
is WearCommand.StartRecording -> if (!dropStartResult) {
publish(
command.sessionId,
- WearRecordingAction.START,
+ WearRecordingOperation.START,
failed = failStart,
)
}
- is WearCommand.StopRecording -> publish(command.sessionId, WearRecordingAction.STOP, failed = false)
+ is WearCommand.StopRecording -> publish(command.sessionId, WearRecordingOperation.STOP, failed = false)
else -> Unit
}
return AppResult.success(Unit)
}
- private fun publish(sessionId: String, action: WearRecordingAction, failed: Boolean) {
+ private fun publish(sessionId: String, operation: WearRecordingOperation, failed: Boolean) {
inbox.publish(
WearCommand.RecordingResult(
sessionId = sessionId,
- action = action,
+ operation = operation,
outcome = if (failed) WearRecordingOutcome.FAILED else WearRecordingOutcome.SUCCEEDED,
- errorCode = AppErrorCode.MEASUREMENT.takeIf { failed },
- errorOperation = "Start Wear recording".takeIf { failed },
+ errorCode = AppErrorCode.RECORDING.takeIf { failed },
+ errorOperation = "Start watch recording".takeIf { failed },
errorMessage = "Fixture start failed".takeIf { failed },
failureCount = if (failed) 1 else 0,
),
diff --git a/app/src/test/java/com/tomasrepcik/sensorbox/domain/measurement/DocumentStorageUseCaseTest.kt b/app/src/test/java/com/tomasrepcik/sensorbox/domain/recording/DocumentRecordingArchiveRepositoryTest.kt
similarity index 76%
rename from app/src/test/java/com/tomasrepcik/sensorbox/domain/measurement/DocumentStorageUseCaseTest.kt
rename to app/src/test/java/com/tomasrepcik/sensorbox/domain/recording/DocumentRecordingArchiveRepositoryTest.kt
index 9c5ee7b..f46b004 100644
--- a/app/src/test/java/com/tomasrepcik/sensorbox/domain/measurement/DocumentStorageUseCaseTest.kt
+++ b/app/src/test/java/com/tomasrepcik/sensorbox/domain/recording/DocumentRecordingArchiveRepositoryTest.kt
@@ -1,4 +1,4 @@
-package com.tomasrepcik.sensorbox.domain.measurement
+package com.tomasrepcik.sensorbox.domain.recording
import android.content.Intent
import com.tomasrepcik.sensorbox.core.error.AppResult
@@ -10,16 +10,16 @@ import org.junit.Test
import java.io.InputStream
import java.io.OutputStream
-class DocumentStorageUseCaseTest {
+class DocumentRecordingArchiveRepositoryTest {
@Test
- fun `Given a document storage adapter When storage operations run Then the same interface owns every result`() {
+ fun `Given document storage When recording archive operations run Then each result is returned`() {
val storage = FakeDocumentStorage()
- val useCase = DocumentStorageUseCase(storage)
+ val repository = DocumentRecordingArchiveRepository(storage)
val intent = Intent()
- assertTrue(useCase.hasStorage().getOrNull() == true)
- assertEquals("Selected folder", useCase.displayPath().getOrNull())
- assertTrue(useCase.persist(intent).isSuccess)
+ assertTrue(repository.isSelected().getOrNull() == true)
+ assertEquals("Documents/SensorBox", repository.path().getOrNull())
+ assertTrue(repository.select(intent).isSuccess)
assertSame(intent, storage.persistedIntent)
}
@@ -33,7 +33,7 @@ class DocumentStorageUseCaseTest {
override fun hasConfiguredDirectory(): AppResult = AppResult.success(true)
- override fun displayPath(): AppResult = AppResult.success("Selected folder")
+ override fun displayPath(): AppResult = AppResult.success("Documents/SensorBox")
override fun createMeasurementDirectory(measurementName: String): AppResult = AppResult.success(Unit)
diff --git a/app/src/test/java/com/tomasrepcik/sensorbox/domain/measurement/MeasurementPermissionUseCaseTest.kt b/app/src/test/java/com/tomasrepcik/sensorbox/domain/recording/RecordingPermissionUseCaseTest.kt
similarity index 78%
rename from app/src/test/java/com/tomasrepcik/sensorbox/domain/measurement/MeasurementPermissionUseCaseTest.kt
rename to app/src/test/java/com/tomasrepcik/sensorbox/domain/recording/RecordingPermissionUseCaseTest.kt
index 444fcc1..f6a230a 100644
--- a/app/src/test/java/com/tomasrepcik/sensorbox/domain/measurement/MeasurementPermissionUseCaseTest.kt
+++ b/app/src/test/java/com/tomasrepcik/sensorbox/domain/recording/RecordingPermissionUseCaseTest.kt
@@ -1,10 +1,10 @@
-package com.tomasrepcik.sensorbox.domain.measurement
+package com.tomasrepcik.sensorbox.domain.recording
import android.Manifest
import org.junit.Assert.assertEquals
import org.junit.Test
-class MeasurementPermissionUseCaseTest {
+class RecordingPermissionUseCaseTest {
@Test
fun `Given optional phone sources When permissions are derived Then only supported permissions are used`() {
val request = request(includesGps = true, activityRecognition = true)
@@ -15,7 +15,7 @@ class MeasurementPermissionUseCaseTest {
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACTIVITY_RECOGNITION,
),
- requiredMeasurementPermissions(request, sdkInt = 37),
+ requiredRecordingPermissions(request, sdkInt = 37),
)
}
@@ -23,11 +23,11 @@ class MeasurementPermissionUseCaseTest {
fun `Given a legacy phone When permissions are derived Then version gated permissions are absent`() {
assertEquals(
setOf(Manifest.permission.ACCESS_FINE_LOCATION),
- requiredMeasurementPermissions(request(includesGps = true, activityRecognition = true), sdkInt = 28),
+ requiredRecordingPermissions(request(includesGps = true, activityRecognition = true), sdkInt = 28),
)
}
- private fun request(includesGps: Boolean, activityRecognition: Boolean) = MeasurementRequest(
+ private fun request(includesGps: Boolean, activityRecognition: Boolean) = RecordingSetup(
sensorIds = emptySet(),
includesGps = includesGps,
samplingPeriodIndex = 0,
diff --git a/app/src/test/java/com/tomasrepcik/sensorbox/domain/measurement/MeasurementRequestTest.kt b/app/src/test/java/com/tomasrepcik/sensorbox/domain/recording/RecordingSetupTest.kt
similarity index 79%
rename from app/src/test/java/com/tomasrepcik/sensorbox/domain/measurement/MeasurementRequestTest.kt
rename to app/src/test/java/com/tomasrepcik/sensorbox/domain/recording/RecordingSetupTest.kt
index 3534c8c..9abf379 100644
--- a/app/src/test/java/com/tomasrepcik/sensorbox/domain/measurement/MeasurementRequestTest.kt
+++ b/app/src/test/java/com/tomasrepcik/sensorbox/domain/recording/RecordingSetupTest.kt
@@ -1,13 +1,13 @@
-package com.tomasrepcik.sensorbox.domain.measurement
+package com.tomasrepcik.sensorbox.domain.recording
import android.hardware.SensorManager
import org.junit.Assert.assertEquals
import org.junit.Test
-class MeasurementRequestTest {
+class RecordingSetupTest {
@Test
- fun `Given a measurement request When converted Then launch configuration is preserved`() {
- val request = MeasurementRequest(
+ fun `Given a recording setup When converted Then recording request is preserved`() {
+ val request = RecordingSetup(
sensorIds = setOf(1, 4),
includesGps = true,
samplingPeriodIndex = 2,
@@ -23,7 +23,7 @@ class MeasurementRequestTest {
significantMotion = true,
)
- val launch = request.toLaunchRequest("session-1", "folder")
+ val launch = request.toRecordingRequest("session-1", "folder")
assertEquals("session-1", launch.sessionId)
assertEquals("folder", launch.folderName)
@@ -38,7 +38,7 @@ class MeasurementRequestTest {
@Test
fun `Given an unknown sampling index When converted Then fastest is used`() {
- val request = MeasurementRequest(
+ val request = RecordingSetup(
sensorIds = emptySet(),
includesGps = false,
samplingPeriodIndex = Int.MAX_VALUE,
@@ -50,7 +50,7 @@ class MeasurementRequestTest {
assertEquals(
SensorManager.SENSOR_DELAY_FASTEST,
- request.toLaunchRequest("session-1", "folder").sensorSamplingPeriod,
+ request.toRecordingRequest("session-1", "folder").sensorSamplingPeriod,
)
}
}
diff --git a/app/src/test/java/com/tomasrepcik/sensorbox/domain/sensors/WearSensorCatalogStoreTest.kt b/app/src/test/java/com/tomasrepcik/sensorbox/domain/sensors/WatchSensorCatalogStoreTest.kt
similarity index 59%
rename from app/src/test/java/com/tomasrepcik/sensorbox/domain/sensors/WearSensorCatalogStoreTest.kt
rename to app/src/test/java/com/tomasrepcik/sensorbox/domain/sensors/WatchSensorCatalogStoreTest.kt
index 70cbfaa..0053880 100644
--- a/app/src/test/java/com/tomasrepcik/sensorbox/domain/sensors/WearSensorCatalogStoreTest.kt
+++ b/app/src/test/java/com/tomasrepcik/sensorbox/domain/sensors/WatchSensorCatalogStoreTest.kt
@@ -4,10 +4,10 @@ import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
-class WearSensorCatalogStoreTest {
+class WatchSensorCatalogStoreTest {
@Test
- fun `Given an empty sensor response When catalog updates Then Wear is available`() {
- val catalog = WearSensorCatalogStore()
+ fun `Given an empty sensor response When catalog updates Then watch is available`() {
+ val catalog = WatchSensorCatalogStore()
catalog.update(emptyList())
@@ -15,8 +15,8 @@ class WearSensorCatalogStoreTest {
}
@Test
- fun `Given an available Wear app When catalog clears Then Wear is unavailable`() {
- val catalog = WearSensorCatalogStore().apply { update(emptyList()) }
+ fun `Given an available watch app When catalog clears Then watch is unavailable`() {
+ val catalog = WatchSensorCatalogStore().apply { update(emptyList()) }
catalog.clear()
diff --git a/app/src/test/java/com/tomasrepcik/sensorbox/presentation/main/FeatureViewModelTest.kt b/app/src/test/java/com/tomasrepcik/sensorbox/presentation/main/FeatureViewModelTest.kt
index 9edd339..e7e7b7e 100644
--- a/app/src/test/java/com/tomasrepcik/sensorbox/presentation/main/FeatureViewModelTest.kt
+++ b/app/src/test/java/com/tomasrepcik/sensorbox/presentation/main/FeatureViewModelTest.kt
@@ -7,15 +7,15 @@ import com.tomasrepcik.sensorbox.core.error.AppResult
import com.tomasrepcik.sensorbox.core.error.DiagnosticsStore
import com.tomasrepcik.sensorbox.core.preferences.AppThemeMode
import com.tomasrepcik.sensorbox.core.testing.FakeAppPreferencesRepository
-import com.tomasrepcik.sensorbox.domain.measurement.DocumentStorageGateway
-import com.tomasrepcik.sensorbox.domain.measurement.MeasurementPermissionsUseCase
-import com.tomasrepcik.sensorbox.domain.measurement.MeasurementRequest
-import com.tomasrepcik.sensorbox.domain.measurement.RecordingControlUseCase
+import com.tomasrepcik.sensorbox.domain.recording.RecordingArchiveRepository
+import com.tomasrepcik.sensorbox.domain.recording.RecordingControlUseCase
+import com.tomasrepcik.sensorbox.domain.recording.RecordingPermissionsUseCase
+import com.tomasrepcik.sensorbox.domain.recording.RecordingSetup
import com.tomasrepcik.sensorbox.domain.sensors.AvailableSensorsUseCase
import com.tomasrepcik.sensorbox.domain.sensors.SensorDescriptor
-import com.tomasrepcik.sensorbox.domain.sensors.WearSensorCatalogStore
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementSessionState
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementSessionStore
+import com.tomasrepcik.sensorbox.domain.sensors.WatchSensorCatalogStore
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionState
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionStore
import com.tomasrepcik.sensorbox.testing.MainDispatcherRule
import com.tomasrepcik.sensorbox.wearoslib.connectivity.FakeWearConnectionRepository
import com.tomasrepcik.sensorbox.wearoslib.connectivity.ObserveWearCapabilityUseCase
@@ -43,35 +43,37 @@ class FeatureViewModelTest {
val mainDispatcherRule = MainDispatcherRule()
@Test
- fun `Given onboarding storage When completed Then state and navigation effect are owned by onboarding`() = runTest {
- val viewModel = OnboardingViewModel(
- preferencesRepository = FakeAppPreferencesRepository(),
- documentStorage = FakeDocumentStorage(hasStorage = true),
- )
- val effect = async { viewModel.effects.first() }
+ fun `Given onboarding recording archive When completed Then state and navigation effect are owned by onboarding`() =
+ runTest {
+ val viewModel = OnboardingViewModel(
+ preferencesRepository = FakeAppPreferencesRepository(),
+ recordingArchive = FakeRecordingArchiveRepository(isSelected = true),
+ )
+ val effect = async { viewModel.effects.first() }
- viewModel.accept(OnboardingIntent.AdvanceOnboarding)
- viewModel.accept(OnboardingIntent.CompleteOnboarding)
- advanceUntilIdle()
+ viewModel.accept(OnboardingIntent.AdvanceOnboarding)
+ viewModel.accept(OnboardingIntent.CompleteOnboarding)
+ advanceUntilIdle()
- assertEquals(1, viewModel.state.value.page)
- assertEquals(OnboardingEffect.Navigate(MainRoute.RECORD), effect.await())
- }
+ assertEquals(1, viewModel.state.value.page)
+ assertEquals(OnboardingEffect.Navigate(MainRoute.RECORD), effect.await())
+ }
@Test
- fun `Given onboarding has no storage When completed Then storage error code is exposed`() = runTest {
- val viewModel = OnboardingViewModel(
- preferencesRepository = FakeAppPreferencesRepository(),
- documentStorage = FakeDocumentStorage(hasStorage = false),
- )
+ fun `Given onboarding has no recording archive When completed Then recording archive error code is exposed`() =
+ runTest {
+ val viewModel = OnboardingViewModel(
+ preferencesRepository = FakeAppPreferencesRepository(),
+ recordingArchive = FakeRecordingArchiveRepository(isSelected = false),
+ )
- viewModel.accept(OnboardingIntent.CompleteOnboarding)
+ viewModel.accept(OnboardingIntent.CompleteOnboarding)
- assertEquals(AppErrorCode.STORAGE, viewModel.state.value.errorCode)
- }
+ assertEquals(AppErrorCode.STORAGE, viewModel.state.value.errorCode)
+ }
@Test
- fun `Given settings action When sampling changes Then settings owns updated preference state`() = runTest {
+ fun `Given settings intent When sampling changes Then settings owns updated preference state`() = runTest {
val viewModel = SettingsViewModel(
preferencesRepository = FakeAppPreferencesRepository(),
diagnosticsStore = FakeDiagnosticsStore(),
@@ -86,7 +88,7 @@ class FeatureViewModelTest {
}
@Test
- fun `Given settings action When theme changes Then settings owns updated appearance state`() = runTest {
+ fun `Given settings intent When theme changes Then settings owns updated appearance state`() = runTest {
val viewModel = SettingsViewModel(
preferencesRepository = FakeAppPreferencesRepository(),
diagnosticsStore = FakeDiagnosticsStore(),
@@ -124,32 +126,32 @@ class FeatureViewModelTest {
val viewModel = recordingViewModel(FakeRecordingWorkflow())
advanceUntilIdle()
- viewModel.accept(RecordingIntent.StartMeasurement)
+ viewModel.accept(RecordingIntent.StartRecording)
assertEquals(RecordingMessage.PICK_AT_LEAST_ONE_SOURCE, viewModel.state.value.message)
assertEquals(AppErrorCode.VALIDATION, viewModel.state.value.errorCode)
}
@Test
- fun `Given Wear permission rejection When recording starts Then Wear guidance is shown`() = runTest {
+ fun `Given watch permission rejection When recording starts Then watch guidance is shown`() = runTest {
val workflow = FakeRecordingWorkflow().apply {
startResult = AppResult.failure(
AppError(
code = AppErrorCode.PERMISSION,
operation = "Handle paired PREPARE acknowledgement",
diagnosticMessage = "Paired PREPARE permission rejected",
- context = mapOf("source" to "wear"),
+ context = mapOf("source" to "watch"),
),
)
}
val viewModel = recordingViewModel(workflow)
advanceUntilIdle()
- viewModel.accept(RecordingIntent.ToggleWearSensor(1))
- viewModel.accept(RecordingIntent.StartMeasurement)
+ viewModel.accept(RecordingIntent.ToggleWatchSensor(1))
+ viewModel.accept(RecordingIntent.StartRecording)
advanceUntilIdle()
- assertEquals(RecordingMessage.WEAR_PERMISSION_REQUIRED, viewModel.state.value.message)
+ assertEquals(RecordingMessage.WATCH_PERMISSION_REQUIRED, viewModel.state.value.message)
assertEquals(AppErrorCode.PERMISSION, viewModel.state.value.errorCode)
}
@@ -157,16 +159,16 @@ class FeatureViewModelTest {
fun `Given waiting start When tapped Then progress appears and duplicate taps are ignored`() = runTest {
val startGate = CompletableDeferred()
val workflow = FakeRecordingWorkflow().apply { this.startGate = startGate }
- val sessionStore = MeasurementSessionStore()
+ val sessionStore = RecordingSessionStore()
val viewModel = recordingViewModel(workflow, sessionStore = sessionStore)
advanceUntilIdle()
viewModel.accept(RecordingIntent.ToggleSensor(1))
- viewModel.accept(RecordingIntent.StartMeasurement)
+ viewModel.accept(RecordingIntent.StartRecording)
runCurrent()
assertTrue(viewModel.state.value.isStarting)
- viewModel.accept(RecordingIntent.StartMeasurement)
+ viewModel.accept(RecordingIntent.StartRecording)
assertEquals(1, workflow.startCalls)
startGate.complete(Unit)
@@ -174,7 +176,7 @@ class FeatureViewModelTest {
assertTrue(viewModel.state.value.isStarting)
sessionStore.markRunning(
- MeasurementSessionState.Running("session", "fixture", 0L, listOf(1), false),
+ RecordingSessionState.Running("session", "fixture", 0L, listOf(1), false),
)
runCurrent()
@@ -193,7 +195,7 @@ class FeatureViewModelTest {
viewModel.accept(RecordingIntent.SetStartDelay(2))
// When
- viewModel.accept(RecordingIntent.StartMeasurement)
+ viewModel.accept(RecordingIntent.StartRecording)
runCurrent()
// Then
@@ -210,21 +212,21 @@ class FeatureViewModelTest {
}
@Test
- fun `Given storage action When chosen Then recording emits only its picker effect`() = runTest {
+ fun `Given recording archive intent When chosen Then recording emits only its picker effect`() = runTest {
val viewModel = recordingViewModel(FakeRecordingWorkflow())
val effect = async { viewModel.effects.first() }
- viewModel.accept(RecordingIntent.ChooseStorage)
+ viewModel.accept(RecordingIntent.ChooseRecordingArchive)
- assertEquals(RecordingEffect.PickStorageDirectory, effect.await())
+ assertEquals(RecordingEffect.PickRecordingArchive, effect.await())
}
@Test
- fun `Given an active measurement When stopped Then recording navigates to the main screen`() = runTest {
+ fun `Given an active recording When stopped Then recording navigates to the main screen`() = runTest {
val viewModel = recordingViewModel(FakeRecordingWorkflow())
val effect = async { viewModel.effects.first() }
- viewModel.accept(RecordingIntent.StopMeasurement)
+ viewModel.accept(RecordingIntent.StopRecording)
advanceUntilIdle()
assertEquals(RecordingEffect.Navigate(MainRoute.RECORD), effect.await())
@@ -232,7 +234,7 @@ class FeatureViewModelTest {
@Test
fun `Given a Wear catalog When observed Then every sensor detail is retained`() = runTest {
- val catalog = WearSensorCatalogStore()
+ val catalog = WatchSensorCatalogStore()
val viewModel = recordingViewModel(FakeRecordingWorkflow(), catalog)
advanceUntilIdle()
@@ -271,37 +273,37 @@ class FeatureViewModelTest {
reportingMode = com.tomasrepcik.sensorbox.domain.sensors.SensorReportingMode.CONTINUOUS,
isWakeUpSensor = true,
),
- viewModel.state.value.wearSensors.single(),
+ viewModel.state.value.watchSensors.single(),
)
}
private fun recordingViewModel(
workflow: RecordingControlUseCase,
- wearSensorCatalog: WearSensorCatalogStore = WearSensorCatalogStore(),
- sessionStore: MeasurementSessionStore = MeasurementSessionStore(),
+ watchSensorCatalog: WatchSensorCatalogStore = WatchSensorCatalogStore(),
+ sessionStore: RecordingSessionStore = RecordingSessionStore(),
): RecordingViewModel {
val repository = FakeWearConnectionRepository()
return RecordingViewModel(
preferencesRepository = FakeAppPreferencesRepository(),
availableSensors = AvailableSensorsUseCase { emptyList() },
- storage = FakeDocumentStorage(hasStorage = true),
- permissions = MeasurementPermissionsUseCase { emptySet() },
+ recordingArchive = FakeRecordingArchiveRepository(isSelected = true),
+ permissions = RecordingPermissionsUseCase { emptySet() },
recording = workflow,
sessionStore = sessionStore,
- observeWearCapability = ObserveWearCapabilityUseCase(repository),
- sendWearCommand = SendWearCommandUseCase(SendWearMessageUseCase(repository)),
- wearSensorCatalog = wearSensorCatalog,
+ observeWatchCapability = ObserveWearCapabilityUseCase(repository),
+ sendWatchCommand = SendWearCommandUseCase(SendWearMessageUseCase(repository)),
+ watchSensorCatalog = watchSensorCatalog,
elapsedRealtimeClock = ElapsedRealtimeClock { 10_000L },
)
}
}
-private class FakeDocumentStorage(private val hasStorage: Boolean) : DocumentStorageGateway {
- override fun hasStorage(): AppResult = AppResult.success(hasStorage)
+private class FakeRecordingArchiveRepository(private val isSelected: Boolean) : RecordingArchiveRepository {
+ override fun isSelected(): AppResult = AppResult.success(isSelected)
- override fun displayPath(): AppResult = AppResult.success(if (hasStorage) "fixture" else null)
+ override fun path(): AppResult = AppResult.success(if (isSelected) "fixture" else null)
- override fun persist(resultIntent: Intent): AppResult = AppResult.success(Unit)
+ override fun select(resultIntent: Intent): AppResult = AppResult.success(Unit)
}
private class FakeDiagnosticsStore(
@@ -321,7 +323,7 @@ private class FakeRecordingWorkflow : RecordingControlUseCase {
var startGate: CompletableDeferred? = null
var startCalls = 0
- override suspend fun start(request: MeasurementRequest): AppResult {
+ override suspend fun start(request: RecordingSetup): AppResult {
startCalls += 1
startGate?.await()
return startResult
diff --git a/app/src/test/java/com/tomasrepcik/sensorbox/presentation/main/RecordingReducerTest.kt b/app/src/test/java/com/tomasrepcik/sensorbox/presentation/main/RecordingReducerTest.kt
index f303da6..61bb469 100644
--- a/app/src/test/java/com/tomasrepcik/sensorbox/presentation/main/RecordingReducerTest.kt
+++ b/app/src/test/java/com/tomasrepcik/sensorbox/presentation/main/RecordingReducerTest.kt
@@ -15,12 +15,12 @@ class RecordingReducerTest {
}
@Test
- fun `Given record state When storage is requested Then picker effect is emitted`() {
+ fun `Given record state When recording archive is requested Then picker effect is emitted`() {
val givenState = RecordingStateFixtures.state()
- val actual = RecordingReducer.reduce(givenState, RecordingIntent.ChooseStorage)
+ val actual = RecordingReducer.reduce(givenState, RecordingIntent.ChooseRecordingArchive)
- assertEquals(RecordingEffect.PickStorageDirectory, actual.effect)
+ assertEquals(RecordingEffect.PickRecordingArchive, actual.effect)
}
@Test
@@ -36,9 +36,9 @@ class RecordingReducerTest {
fun `Given an unselected Wear sensor When toggled Then only Wear selection changes`() {
val givenState = RecordingStateFixtures.state()
- val actual = RecordingReducer.reduce(givenState, RecordingIntent.ToggleWearSensor(sensorId = 21))
+ val actual = RecordingReducer.reduce(givenState, RecordingIntent.ToggleWatchSensor(sensorId = 21))
- assertTrue(21 in actual.state.selectedWearSensorIds)
+ assertTrue(21 in actual.state.selectedWatchSensorIds)
assertTrue(actual.state.selectedSensorIds.isEmpty())
}
@@ -64,9 +64,9 @@ class RecordingReducerTest {
fun `Given selected sensors When setup is opened Then setup route is shown`() {
val givenState = RecordingStateFixtures.state(selectedSensorIds = setOf(1))
- val actual = RecordingReducer.reduce(givenState, RecordingIntent.OpenMeasurementSetup)
+ val actual = RecordingReducer.reduce(givenState, RecordingIntent.OpenRecordingSetup)
- assertEquals(RecordingEffect.Navigate(MainRoute.SETUP), actual.effect)
+ assertEquals(RecordingEffect.Navigate(MainRoute.RECORDING_SETUP), actual.effect)
}
@Test
@@ -85,16 +85,16 @@ class RecordingReducerTest {
val actual = RecordingReducer.reduce(
givenState,
- RecordingIntent.OpenSensorDetails(21, RecordingSensorSource.WEAR),
+ RecordingIntent.OpenSensorDetails(21, RecordingDevice.WATCH),
)
assertEquals(RecordingEffect.Navigate(MainRoute.SENSOR_DETAILS), actual.effect)
assertEquals(21, actual.state.detailsSensorType)
- assertEquals(RecordingSensorSource.WEAR, actual.state.detailsSensorSource)
+ assertEquals(RecordingDevice.WATCH, actual.state.detailsDevice)
}
@Test
- fun `Given measurement setup When returning Then sensor selection is shown`() {
+ fun `Given recording setup When returning Then sensor selection is shown`() {
val givenState = RecordingStateFixtures.state(selectedSensorIds = setOf(1))
val actual = RecordingReducer.reduce(givenState, RecordingIntent.ReturnToSensorSelection)
diff --git a/app/src/test/java/com/tomasrepcik/sensorbox/presentation/main/RecordingStateConversionTest.kt b/app/src/test/java/com/tomasrepcik/sensorbox/presentation/main/RecordingStateConversionTest.kt
index 00d7c79..3c537c8 100644
--- a/app/src/test/java/com/tomasrepcik/sensorbox/presentation/main/RecordingStateConversionTest.kt
+++ b/app/src/test/java/com/tomasrepcik/sensorbox/presentation/main/RecordingStateConversionTest.kt
@@ -24,7 +24,7 @@ class RecordingStateConversionTest {
),
)
- val request = state.toMeasurementRequest()
+ val request = state.toRecordingSetup()
assertEquals(setOf(1), request.sensorIds)
assertEquals(0, request.durationSeconds)
diff --git a/build.gradle.kts b/build.gradle.kts
index 590839f..0d3d52f 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -29,7 +29,7 @@ data class ReadmeScreenshot(val module: String, val preview: String, val destina
val readmeScreenshots = listOf(
ReadmeScreenshot(":app", "readmeSourceSelection", "sensorbox-phone-record.png"),
- ReadmeScreenshot(":app", "readmeMeasurementSetup", "sensorbox-phone-setup.png"),
+ ReadmeScreenshot(":app", "readmeRecordingSetup", "sensorbox-phone-setup.png"),
ReadmeScreenshot(":app", "readmeIntroWelcome", "sensorbox-intro-welcome.png"),
ReadmeScreenshot(":app", "readmeIntroPrivacy", "sensorbox-intro-privacy.png"),
ReadmeScreenshot(":app", "readmeIntroPolicy", "sensorbox-intro-policy.png"),
diff --git a/core-common/src/main/kotlin/com/tomasrepcik/sensorbox/core/error/AppError.kt b/core-common/src/main/kotlin/com/tomasrepcik/sensorbox/core/error/AppError.kt
index 69634f8..eb42d6b 100644
--- a/core-common/src/main/kotlin/com/tomasrepcik/sensorbox/core/error/AppError.kt
+++ b/core-common/src/main/kotlin/com/tomasrepcik/sensorbox/core/error/AppError.kt
@@ -34,7 +34,7 @@ data class AppError(
enum class AppErrorCode {
CONNECTIVITY,
EXTERNAL_ACTION,
- MEASUREMENT,
+ RECORDING,
PERMISSION,
PREFERENCES,
STORAGE,
diff --git a/core/src/main/java/com/tomasrepcik/sensorbox/core/preferences/AppPreferences.kt b/core/src/main/java/com/tomasrepcik/sensorbox/core/preferences/AppPreferences.kt
index a275f58..b2bb407 100644
--- a/core/src/main/java/com/tomasrepcik/sensorbox/core/preferences/AppPreferences.kt
+++ b/core/src/main/java/com/tomasrepcik/sensorbox/core/preferences/AppPreferences.kt
@@ -12,7 +12,7 @@ data class RecordingPreferences(
val gpsIntervalSeconds: Int = 10,
val gpsMinDistanceMeters: Int = 20,
val sensorSamplingPeriod: Int = 0,
- val restrictMeasurementOnLowBattery: Boolean = true,
+ val stopRecordingOnLowBattery: Boolean = true,
val useWakeLock: Boolean = false,
)
diff --git a/core/src/main/java/com/tomasrepcik/sensorbox/core/preferences/AppPreferencesIntent.kt b/core/src/main/java/com/tomasrepcik/sensorbox/core/preferences/AppPreferencesIntent.kt
index bdac142..f5f928d 100644
--- a/core/src/main/java/com/tomasrepcik/sensorbox/core/preferences/AppPreferencesIntent.kt
+++ b/core/src/main/java/com/tomasrepcik/sensorbox/core/preferences/AppPreferencesIntent.kt
@@ -11,7 +11,7 @@ sealed interface AppPreferencesIntent {
data class SetSensorSamplingPeriod(val period: Int) : AppPreferencesIntent
- data class SetLowBatteryRestriction(val enabled: Boolean) : AppPreferencesIntent
+ data class SetStopOnLowBattery(val enabled: Boolean) : AppPreferencesIntent
data class SetWakeLock(val enabled: Boolean) : AppPreferencesIntent
diff --git a/core/src/main/java/com/tomasrepcik/sensorbox/core/preferences/AppPreferencesReducer.kt b/core/src/main/java/com/tomasrepcik/sensorbox/core/preferences/AppPreferencesReducer.kt
index 0485e7c..d46b464 100644
--- a/core/src/main/java/com/tomasrepcik/sensorbox/core/preferences/AppPreferencesReducer.kt
+++ b/core/src/main/java/com/tomasrepcik/sensorbox/core/preferences/AppPreferencesReducer.kt
@@ -34,8 +34,8 @@ object AppPreferencesReducer {
display = current.display.copy(dynamicColors = intent.enabled),
)
- is AppPreferencesIntent.SetLowBatteryRestriction -> current.copy(
- recording = current.recording.copy(restrictMeasurementOnLowBattery = intent.enabled),
+ is AppPreferencesIntent.SetStopOnLowBattery -> current.copy(
+ recording = current.recording.copy(stopRecordingOnLowBattery = intent.enabled),
)
is AppPreferencesIntent.SetSensorSamplingPeriod -> current.copy(
diff --git a/core/src/main/java/com/tomasrepcik/sensorbox/core/preferences/DataStoreAppPreferencesRepository.kt b/core/src/main/java/com/tomasrepcik/sensorbox/core/preferences/DataStoreAppPreferencesRepository.kt
index 43cf396..b0a2870 100644
--- a/core/src/main/java/com/tomasrepcik/sensorbox/core/preferences/DataStoreAppPreferencesRepository.kt
+++ b/core/src/main/java/com/tomasrepcik/sensorbox/core/preferences/DataStoreAppPreferencesRepository.kt
@@ -41,7 +41,7 @@ class DataStoreAppPreferencesRepository(private val dataStore: DataStore` with stable `AppErrorCode` values. Infrastructure adapters translate platform exceptions. Diagnostics remain local to the device. Tests use fakes at real seams and assert the outcomes seen by production callers.
diff --git a/docs/recording-architecture-guided-review.md b/docs/recording-architecture-guided-review.md
index 655dfd5..517e1f1 100644
--- a/docs/recording-architecture-guided-review.md
+++ b/docs/recording-architecture-guided-review.md
@@ -22,13 +22,13 @@ Read:
- `../app/src/main/java/com/tomasrepcik/sensorbox/presentation/main/RecordScreen.kt`
- `../app/src/main/java/com/tomasrepcik/sensorbox/di/RecordingModule.kt`
-The root UI sends actions to `RecordingViewModel`. The ViewModel owns the countdown in `startJob`. Until the countdown reaches zero, it does not call the recording use case. The UI only renders `RecordingState.startCountdownSeconds`.
+The root UI sends intents to `RecordingViewModel`. The ViewModel owns the countdown in `startJob`. Until the countdown reaches zero, it does not call the recording use case. The UI only renders `RecordingState.startCountdownSeconds`.
The useful seams injected into the ViewModel are small and named by intent:
- `AvailableSensorsUseCase`
-- `MeasurementPermissionsUseCase`
-- `DocumentStorageGateway`
+- `RecordingPermissionsUseCase`
+- `RecordingArchiveRepository`
- `RecordingControlUseCase`
- `SendWearCommandUseCase`
@@ -38,10 +38,10 @@ Check that these interfaces isolate real side effects or a module boundary. Ther
Read:
-- `../app/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/MeasurementControlUseCase.kt`
+- `../app/src/main/java/com/tomasrepcik/sensorbox/domain/recording/RecordingControlUseCase.kt`
- `../app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PairedRecordingCoordinator.kt`
- `../app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PairedRecordingModel.kt`
-- `../app/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/PhoneRecordingController.kt`
+- `../app/src/main/java/com/tomasrepcik/sensorbox/domain/recording/PhoneRecordingController.kt`
The execution path is:
@@ -50,14 +50,14 @@ RecordingViewModel
-> RecordingControlUseCase
-> PairedRecordingCoordinator
-> PhoneRecordingController
- -> SendWearCommandUseCase (only when Wear sources are selected)
+ -> SendWearCommandUseCase (only when watch sources are selected)
```
-`PairedRecordingCoordinator` owns one small state gate. It starts the phone first, sends a direct Wear start command, and cleans up both sides if Wear fails. It has no prepare/commit/rollback protocol and no mutex. Model-to-command conversions live beside the models in `PairedRecordingModel.kt`.
+`PairedRecordingCoordinator` owns one small state gate. It starts the phone first, sends a direct watch start command, and cleans up both sides if the watch fails. It has no prepare/commit/rollback protocol and no mutex. Model-to-command conversions live beside the models in `PairedRecordingModel.kt`.
-Check the stale-command rule: a stop received from Wear only affects the matching active session.
+Check the stale-command rule: a stop received from the watch only affects the matching active recording session.
-Also check the disconnected-timer rule. Once the Wear start command has been sent, losing its result does not tear down the phone recording. The phone and Wear services receive the same duration and each local `RecordingEngine` schedules its own stop. Automatic peer notifications are best effort after local cleanup.
+Also check the disconnected-timer rule. Once the watch start command has been sent, losing its result does not tear down the phone recording. The phone and watch services receive the same duration and each local `RecordingEngine` schedules its own stop. Automatic peer notifications are best effort after local cleanup.
## 4. Review the JSON command boundary
@@ -73,42 +73,42 @@ Commands are serializable data classes inside a versioned JSON envelope. Sensor
Phone side:
-- `../app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneWearMessageDispatcher.kt`
-- `../app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneWearCommandHandler.kt`
-- `../app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/WearRecordingResultInbox.kt`
+- `../app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneWatchMessageDispatcher.kt`
+- `../app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/PhoneWatchCommandHandler.kt`
+- `../app/src/main/java/com/tomasrepcik/sensorbox/domain/paired/WatchRecordingResultInbox.kt`
Wear side:
- `../wear/src/main/java/com/tomasrepcik/sensorbox/communication/WearMessageDispatcher.kt`
- `../wear/src/main/java/com/tomasrepcik/sensorbox/communication/WearCommandHandler.kt`
- `../wear/src/main/java/com/tomasrepcik/sensorbox/communication/WearCommandEnvironment.kt`
-- `../wear/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/WearMeasurementControlUseCase.kt`
+- `../wear/src/main/java/com/tomasrepcik/sensorbox/domain/recording/WatchRecordingControlUseCase.kt`
- `../wear/src/main/java/com/tomasrepcik/sensorbox/di/RecordingModule.kt`
-Dispatchers decode and route. Handlers state-gate commands and execute them. On Wear, `WearRecordingRequirementsUseCase` isolates checks that are needed at the moment recording starts, while `WearRecordingControlUseCase` isolates starting and stopping the Android service. These are deliberate seams because they wrap separate effects and make command behavior testable.
+Dispatchers decode and route. Handlers state-gate commands and execute them. On Wear, `WearRecordingRequirementsUseCase` isolates checks that are needed at the moment recording starts, while `WatchRecordingControlUseCase` isolates starting and stopping the Android service. These are deliberate seams because they wrap separate effects and make command behavior testable.
## 6. Follow the service into the recording engine
Read:
-- `../sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/MeasurementService.kt`
-- `../sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/MeasurementHostResources.kt`
+- `../sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/RecordingService.kt`
+- `../sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/RecordingHostResources.kt`
- `../sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/ServiceControllerFactory.kt`
- `../sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/ServiceController.kt`
- `../recording-core/src/main/kotlin/com/tomasrepcik/sensorbox/recording/RecordingEngine.kt`
- `../recording-core/src/main/kotlin/com/tomasrepcik/sensorbox/recording/RecordingSource.kt`
- `../recording-core/src/main/kotlin/com/tomasrepcik/sensorbox/recording/RecordingModel.kt`
-`MeasurementService` routes lifecycle and intents. `MeasurementHostResources` owns Android host concerns such as notification, wake lock, receiver, and alarm. The factory assembles a controller. The controller translates the launch request into a direct recording plan. The engine then starts or stops ordered `RecordingSource` instances and knows nothing about Android.
+`RecordingService` routes lifecycle and intents. `RecordingHostResources` owns Android host concerns such as notification, wake lock, receiver, and alarm. The factory assembles a controller. The controller translates the platform recording request into the engine recording request. The engine then starts or stops ordered `RecordingSource` instances and knows nothing about Android.
-The key engine seam is `RecordingSource`. It is intentionally small: `start(spec)` and `stop()`.
+The key engine seam is `RecordingSource`. It is intentionally small: `start(spec)` and `stop(context)`.
## 7. Review Android source construction
Read:
- `../sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/AndroidRecordingSources.kt`
-- `../sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/SessionRecordingSource.kt`
+- `../sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/SessionMetadataRecordingSource.kt`
- `../sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/SensorRecordingSource.kt`
- `../sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/GpsRecordingSource.kt`
- `../sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/ActivityRecordingSource.kt`
@@ -121,7 +121,7 @@ Read:
Read:
- `../sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/StorageHandler.kt`
-- `../sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ExtraInfoHandler.kt`
+- `../sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/MeasurementMetadataWriter.kt`
- `../core/src/main/java/com/tomasrepcik/sensorbox/core/error/FileDiagnostics.kt`
- `../WearOsLib/src/main/java/com/tomasrepcik/sensorbox/wearoslib/files/WearFileMetadata.kt`
- `../WearOsLib/src/main/java/com/tomasrepcik/sensorbox/wearoslib/files/WearFilePathCodec.kt`
diff --git a/fastlane/metadata/android/en-GB/full_description.txt b/fastlane/metadata/android/en-GB/full_description.txt
index 590a769..4a97309 100644
--- a/fastlane/metadata/android/en-GB/full_description.txt
+++ b/fastlane/metadata/android/en-GB/full_description.txt
@@ -1,19 +1,19 @@
-Use your sensors in mobile to track motion by accelerometer, gyroscope, magnetometer, ... and location data by GPS, which are stored in CSV files in your phone. No data are sent out from smartphone.
+Use your phone sensors to record motion samples from the accelerometer, gyroscope, magnetometer, and other sensors, plus location samples from GPS. Samples are stored in CSV files on your phone. No data is sent away from your phone.
-While measuring, you can make annotations, which you can customize in settings. They can be found in the same folder like CSV files called "extra".
+While recording, you can add annotations and customize them in settings. They are stored with the measurement metadata.
-App is constructed from more modules, which enable to set countdown to stop measurement, types of usage , use only motion sensors with/without GPS or just use all sensors.
+Choose a recording duration and which recording sources to use, including motion sensors with or without GPS.
-More parameters can be found in settings, with which you can modify measurement, like you want.
+More choices for the recording setup are available in settings.
Can use sensors: accelerometer, gyroscope, magnetometer, rotation vector calculation, thermometer, barometer, luxmeter, proximity sensor, humidity meter, pedometer.
-If you are owner of Wear Os device, you can use it to get additional data. Wear motion data are collected by smartphone and stored in individual CSV files. Phone is required to collect data. Also, you can take a look at graphs directly in Wear Os with option to pause recording.
+If you own a Wear OS device, you can use its sensors to record additional samples. Watch measurements can be synced to the recording archive on your phone. You can also view graphs directly on Wear OS.
Recently, the activity recognition client , Google's API, has been added, so all motion changes, which can result in change of motion state, can be recorded. Two files are generated. One with timestamps of significant changes, which resulted in change of motion state. This data are event based. Second file has timestamps with confidence of each possible activity, which can client recognize. This data are recorded periodically with customizable frequency.
Supported activities by activity recognition client: vehicle, bike, running, walking, still, tilting.
-Wear Os app now supports local storage, so you can record data without paired phone. Sync can be done manually through phone app. Meanwhile local storage is in test phase, but available at all versions of app.
+The Wear OS app supports local storage, so you can record without a paired phone. Sync can be started manually from the phone app.
-More will come and if you have some kind of problem / suggestion, contact us via email down below.
\ No newline at end of file
+More will come and if you have some kind of problem / suggestion, contact us via email down below.
diff --git a/recording-core/src/main/kotlin/com/tomasrepcik/sensorbox/recording/RecordingEngine.kt b/recording-core/src/main/kotlin/com/tomasrepcik/sensorbox/recording/RecordingEngine.kt
index 54fe328..d600281 100644
--- a/recording-core/src/main/kotlin/com/tomasrepcik/sensorbox/recording/RecordingEngine.kt
+++ b/recording-core/src/main/kotlin/com/tomasrepcik/sensorbox/recording/RecordingEngine.kt
@@ -28,17 +28,17 @@ class RecordingEngine(
val events: SharedFlow = mutableEvents.asSharedFlow()
@Suppress("ReturnCount")
- suspend fun start(plan: RecordingPlan): AppResult {
- if (activeRecording != null) return conflict("Start recording", plan.sessionId)
+ suspend fun start(request: RecordingRequest): AppResult {
+ if (activeRecording != null) return conflict("Start recording", request.sessionId)
- val validation = validate(plan)
+ val validation = validate(request)
if (validation is AppResult.Failure) return validation
- val recording = ActiveRecording(plan)
+ val recording = ActiveRecording(request)
lastStopResult = null
activeRecording = recording
- for (spec in plan.sources.sortedBy { it.type.ordinal }) {
+ for (spec in request.sources.sortedBy { it.type.ordinal }) {
val source = checkNotNull(sourceByType[spec.type])
recording.startedSources += source
@@ -47,7 +47,7 @@ class RecordingEngine(
if (activeRecording !== recording) return AppResult.success(Unit)
}
- mutableEvents.tryEmit(RecordingEvent.RecordingStarted(plan.sessionId))
+ mutableEvents.tryEmit(RecordingEvent.RecordingStarted(request.sessionId))
scheduleDurationStop(recording)
observeSourceFailures(recording)
return AppResult.success(Unit)
@@ -68,11 +68,11 @@ class RecordingEngine(
context.failures.forEach { add(AppResult.failure(it)) }
addAll(stoppedSources)
}
- val result = results.combineAppResults(AppErrorCode.MEASUREMENT, "Stop recording sources")
+ val result = results.combineAppResults(AppErrorCode.RECORDING, "Stop recording sources")
lastStopResult = result
mutableEvents.tryEmit(
RecordingEvent.RecordingStopped(
- sessionId = recording.plan.sessionId,
+ sessionId = recording.request.sessionId,
reason = context.reason,
result = result,
),
@@ -88,7 +88,7 @@ class RecordingEngine(
RecordingStopContext(RecordingStopReason.SOURCE_FAILURE, listOf(startError)),
)
val result = (listOf(AppResult.failure(startError)) + stoppedSources)
- .combineAppResults(AppErrorCode.MEASUREMENT, "Stop failed recording start")
+ .combineAppResults(AppErrorCode.RECORDING, "Stop failed recording start")
return result
}
@@ -121,16 +121,16 @@ class RecordingEngine(
}
}
- private fun validate(plan: RecordingPlan): AppResult {
+ private fun validate(request: RecordingRequest): AppResult {
val invalidReason = when {
- plan.sources.isEmpty() -> "Recording plan has no sources"
+ request.sources.isEmpty() -> "Recording request has no sources"
- plan.durationMillis < 0L -> "Recording duration is negative"
+ request.durationMillis < 0L -> "Recording duration is negative"
- plan.sources.map(RecordingSourceSpec::type).distinct().size != plan.sources.size ->
- "Recording plan repeats a source type"
+ request.sources.map(RecordingSourceSpec::type).distinct().size != request.sources.size ->
+ "Recording request repeats a source type"
- plan.sources.any { it.type !in sourceByType } -> "Recording source adapter is missing"
+ request.sources.any { it.type !in sourceByType } -> "Recording source adapter is missing"
else -> null
}
@@ -138,15 +138,15 @@ class RecordingEngine(
return if (invalidReason == null) {
AppResult.success(Unit)
} else {
- AppResult.failure(AppError(AppErrorCode.VALIDATION, "Validate recording plan", invalidReason))
+ AppResult.failure(AppError(AppErrorCode.VALIDATION, "Validate recording request", invalidReason))
}
}
private fun scheduleDurationStop(recording: ActiveRecording) {
- val plan = recording.plan
- if (plan.durationMillis <= 0L) return
+ val request = recording.request
+ if (request.durationMillis <= 0L) return
recording.durationStop = scope.launch {
- waitFor(plan.durationMillis)
+ waitFor(request.durationMillis)
stop(RecordingStopReason.DURATION_EXPIRED)
}
}
@@ -162,7 +162,7 @@ class RecordingEngine(
)
private class ActiveRecording(
- val plan: RecordingPlan,
+ val request: RecordingRequest,
val startedSources: MutableList = mutableListOf(),
var durationStop: Job? = null,
var failureMonitor: Job? = null,
diff --git a/recording-core/src/main/kotlin/com/tomasrepcik/sensorbox/recording/RecordingModel.kt b/recording-core/src/main/kotlin/com/tomasrepcik/sensorbox/recording/RecordingModel.kt
index 2373e1f..9addf67 100644
--- a/recording-core/src/main/kotlin/com/tomasrepcik/sensorbox/recording/RecordingModel.kt
+++ b/recording-core/src/main/kotlin/com/tomasrepcik/sensorbox/recording/RecordingModel.kt
@@ -11,7 +11,7 @@ value class RecordingSessionId(val value: String) {
}
enum class RecordingSourceType {
- SESSION,
+ SESSION_METADATA,
SENSOR,
GPS,
ACTIVITY_RECOGNITION,
@@ -21,8 +21,8 @@ enum class RecordingSourceType {
sealed interface RecordingSourceSpec {
val type: RecordingSourceType
- data object Session : RecordingSourceSpec {
- override val type = RecordingSourceType.SESSION
+ data object SessionMetadata : RecordingSourceSpec {
+ override val type = RecordingSourceType.SESSION_METADATA
}
data class Sensors(val sensorTypes: Set, val samplingPeriod: Int) : RecordingSourceSpec {
@@ -42,7 +42,7 @@ sealed interface RecordingSourceSpec {
}
}
-data class RecordingPlan(
+data class RecordingRequest(
val sessionId: RecordingSessionId,
val sources: List,
val durationMillis: Long = 0L,
diff --git a/recording-core/src/test/kotlin/com/tomasrepcik/sensorbox/recording/RecordingEngineTest.kt b/recording-core/src/test/kotlin/com/tomasrepcik/sensorbox/recording/RecordingEngineTest.kt
index 96c158e..cf27d67 100644
--- a/recording-core/src/test/kotlin/com/tomasrepcik/sensorbox/recording/RecordingEngineTest.kt
+++ b/recording-core/src/test/kotlin/com/tomasrepcik/sensorbox/recording/RecordingEngineTest.kt
@@ -35,13 +35,13 @@ class RecordingEngineTest {
FakeSource(RecordingSourceType.GPS, calls),
FakeSource(RecordingSourceType.SENSOR, calls),
)
- val plan = plan(
+ val request = request(
RecordingSourceSpec.Gps(10, 20),
RecordingSourceSpec.Sensors(setOf(1), 0),
)
// When
- val result = engine.start(plan)
+ val result = engine.start(request)
// Then
assertTrue(result.isSuccess)
@@ -56,13 +56,13 @@ class RecordingEngineTest {
FakeSource(RecordingSourceType.GPS, calls, startFails = true),
FakeSource(RecordingSourceType.SENSOR, calls),
)
- val plan = plan(
+ val request = request(
RecordingSourceSpec.Sensors(setOf(1), 0),
RecordingSourceSpec.Gps(10, 20),
)
// When
- val result = engine.start(plan)
+ val result = engine.start(request)
// Then
assertTrue(result.isFailure)
@@ -80,11 +80,11 @@ class RecordingEngineTest {
FakeSource(RecordingSourceType.GPS, calls, stopFails = true),
FakeSource(RecordingSourceType.SENSOR, calls, stopFails = true),
)
- val plan = plan(
+ val request = request(
RecordingSourceSpec.Sensors(setOf(1), 0),
RecordingSourceSpec.Gps(10, 20),
)
- engine.start(plan)
+ engine.start(request)
// When
val result = engine.stop(RecordingStopReason.USER_REQUEST)
@@ -99,14 +99,14 @@ class RecordingEngineTest {
fun `Given runtime and stop failures When session stops Then session receives every failure`() = runTest {
// Given
val calls = mutableListOf()
- val session = FakeSource(RecordingSourceType.SESSION, calls)
+ val session = FakeSource(RecordingSourceType.SESSION_METADATA, calls)
val sensor = FakeSource(RecordingSourceType.SENSOR, calls, stopFails = true)
val engine = RecordingEngine(
sources = listOf(session, sensor),
scope = backgroundScope,
waitFor = { kotlinx.coroutines.delay(it) },
)
- engine.start(plan(RecordingSourceSpec.Session, RecordingSourceSpec.Sensors(setOf(1), 0)))
+ engine.start(request(RecordingSourceSpec.SessionMetadata, RecordingSourceSpec.Sensors(setOf(1), 0)))
// When
sensor.reportFailure()
@@ -124,8 +124,8 @@ class RecordingEngineTest {
// Given
val calls = mutableListOf()
val engine = engine(FakeSource(RecordingSourceType.SENSOR, calls))
- val plan = plan(RecordingSourceSpec.Sensors(setOf(1), 0))
- engine.start(plan)
+ val request = request(RecordingSourceSpec.Sensors(setOf(1), 0))
+ engine.start(request)
// When
val first = engine.stop(RecordingStopReason.USER_REQUEST)
@@ -145,10 +145,10 @@ class RecordingEngineTest {
scope = backgroundScope,
waitFor = { kotlinx.coroutines.delay(it) },
)
- val plan = plan(RecordingSourceSpec.Sensors(setOf(1), 0), durationMillis = 500L)
+ val request = request(RecordingSourceSpec.Sensors(setOf(1), 0), durationMillis = 500L)
// When
- engine.start(plan)
+ engine.start(request)
advanceTimeBy(500L)
runCurrent()
@@ -166,8 +166,8 @@ class RecordingEngineTest {
scope = backgroundScope,
waitFor = { kotlinx.coroutines.delay(it) },
)
- val plan = plan(RecordingSourceSpec.Sensors(setOf(1), 0))
- engine.start(plan)
+ val request = request(RecordingSourceSpec.Sensors(setOf(1), 0))
+ engine.start(request)
// When
source.reportFailure()
@@ -184,7 +184,7 @@ class RecordingEngineTest {
waitFor = { },
)
- private fun plan(vararg sources: RecordingSourceSpec, durationMillis: Long = 0L) = RecordingPlan(
+ private fun request(vararg sources: RecordingSourceSpec, durationMillis: Long = 0L) = RecordingRequest(
sessionId = RecordingSessionId("session"),
sources = sources.toList(),
durationMillis = durationMillis,
@@ -214,10 +214,10 @@ private class FakeSource(
}
fun reportFailure() {
- mutableFailures.tryEmit(AppError(AppErrorCode.MEASUREMENT, "Record $type"))
+ mutableFailures.tryEmit(AppError(AppErrorCode.RECORDING, "Record $type"))
}
private fun failure(operation: String): AppResult = AppResult.failure(
- AppError(AppErrorCode.MEASUREMENT, operation),
+ AppError(AppErrorCode.RECORDING, operation),
)
}
diff --git a/sensorservices/src/main/AndroidManifest.xml b/sensorservices/src/main/AndroidManifest.xml
index 87d7afd..c291b4e 100644
--- a/sensorservices/src/main/AndroidManifest.xml
+++ b/sensorservices/src/main/AndroidManifest.xml
@@ -12,7 +12,7 @@
tools:ignore="HighSamplingRate" />
= appResult(AppErrorCode.MEASUREMENT, "Stop GPS updates") {
+ fun gpsOff(): AppResult = appResult(AppErrorCode.RECORDING, "Stop GPS updates") {
if (callback != null) {
locationClient?.flushLocations()?.addOnFailureListener { error ->
Log.e(TAG, "Cannot flush GPS updates", error)
@@ -73,7 +73,7 @@ class GPSHandler(private val locationClient: FusedLocationProviderClient? = null
intervalSeconds: Int = DEFAULT_INTERVAL_SECONDS,
minDistanceMeters: Int = DEFAULT_DISTANCE_METERS,
): AppResult = (if (callback != null) gpsOff() else AppResult.success(Unit)).flatMap {
- appResult(AppErrorCode.MEASUREMENT, "Register GPS callback") {
+ appResult(AppErrorCode.RECORDING, "Register GPS callback") {
val client = checkNotNull(locationClient) { "GPS client is unavailable" }
callback = gpsCallback
initialize(client, intervalSeconds, minDistanceMeters)
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ActivityRecognitionPlatform.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ActivityRecognitionPlatform.kt
index a569e50..e18086d 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ActivityRecognitionPlatform.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ActivityRecognitionPlatform.kt
@@ -89,7 +89,7 @@ internal class AndroidActivityRecognitionPlatform(private val context: Context)
}
val prepared = prepareResources(onUpdate, onTransitions)
if (prepared.isFailure) return prepared
- return suspendAppResult(AppErrorCode.MEASUREMENT, "Start activity recognition") {
+ return suspendAppResult(AppErrorCode.RECORDING, "Start activity recognition") {
val activeClient = checkNotNull(client) { "Activity recognition client is missing" }
val updates = checkNotNull(updatesPendingIntent) { "Activity update request is missing" }
val transitions = checkNotNull(transitionsPendingIntent) { "Activity transition request is missing" }
@@ -104,7 +104,7 @@ internal class AndroidActivityRecognitionPlatform(private val context: Context)
private fun prepareResources(
onUpdate: (ActivityUpdate) -> Unit,
onTransitions: (List) -> Unit,
- ): AppResult = appResult(AppErrorCode.MEASUREMENT, "Initialize activity recognition resources") {
+ ): AppResult = appResult(AppErrorCode.RECORDING, "Initialize activity recognition resources") {
updateCallback = onUpdate
transitionCallback = onTransitions
val filter = IntentFilter(ACTION_UPDATE).apply { addAction(ACTION_TRANSITION) }
@@ -129,16 +129,16 @@ internal class AndroidActivityRecognitionPlatform(private val context: Context)
override suspend fun stop(): AppResult {
val results = mutableListOf>()
updatesPendingIntent?.let { pendingIntent ->
- results += suspendAppResult(AppErrorCode.MEASUREMENT, "Remove activity updates") {
+ results += suspendAppResult(AppErrorCode.RECORDING, "Remove activity updates") {
client?.removeActivityUpdates(pendingIntent)?.await()
}
}
transitionsPendingIntent?.let { pendingIntent ->
- results += suspendAppResult(AppErrorCode.MEASUREMENT, "Remove activity transitions") {
+ results += suspendAppResult(AppErrorCode.RECORDING, "Remove activity transitions") {
client?.removeActivityTransitionUpdates(pendingIntent)?.await()
}
}
- results += appResult(AppErrorCode.MEASUREMENT, "Release activity recognition resources") {
+ results += appResult(AppErrorCode.RECORDING, "Release activity recognition resources") {
if (receiverRegistered) context.unregisterReceiver(receiver)
receiverRegistered = false
client = null
@@ -147,7 +147,7 @@ internal class AndroidActivityRecognitionPlatform(private val context: Context)
updateCallback = null
transitionCallback = null
}
- return results.combineAppResults(AppErrorCode.MEASUREMENT, "Pause activity recognition")
+ return results.combineAppResults(AppErrorCode.RECORDING, "Pause activity recognition")
}
private fun hasPermission(): Boolean = Build.VERSION.SDK_INT < 29 ||
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ActivityRecognitionMeasurement.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ActivityRecognitionRecording.kt
similarity index 87%
rename from sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ActivityRecognitionMeasurement.kt
rename to sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ActivityRecognitionRecording.kt
index 667f08f..5aea36c 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ActivityRecognitionMeasurement.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ActivityRecognitionRecording.kt
@@ -13,7 +13,7 @@ import kotlinx.coroutines.flow.asSharedFlow
import java.io.OutputStream
/** Records periodic activity-recognition confidence values. */
-internal class ActivityRecognitionMeasurement(
+internal class ActivityRecognitionRecording(
private val storage: MeasurementStorage,
private val platform: ActivityRecognitionPlatform,
) {
@@ -28,7 +28,7 @@ internal class ActivityRecognitionMeasurement(
val opened = openOutputs(folderName, useInternalStorage)
if (opened.isFailure) return opened
return platform.start(periodSeconds, ::writeActivityUpdate, ::writeActivityTransitions)
- .withAppError(AppErrorCode.MEASUREMENT, "Start activity recognition")
+ .withAppError(AppErrorCode.RECORDING, "Start activity recognition")
}
private fun openOutputs(folderName: String, useInternalStorage: Boolean): AppResult {
@@ -40,7 +40,7 @@ internal class ActivityRecognitionMeasurement(
)
val updates = updatesResult.getOrNull()
?: return AppResult.failure(checkNotNull(updatesResult.errorOrNull()))
- .withAppError(AppErrorCode.MEASUREMENT, "Initialize activity recognition")
+ .withAppError(AppErrorCode.RECORDING, "Initialize activity recognition")
updatesOutput = updates
val transitionsResult = storage.openMeasurementFile(
@@ -51,10 +51,10 @@ internal class ActivityRecognitionMeasurement(
)
val transitions = transitionsResult.getOrNull()
if (transitions == null) {
- appResult(AppErrorCode.STORAGE, "Close incomplete activity measurement") { updates.close() }
+ appResult(AppErrorCode.STORAGE, "Close incomplete activity measurement file") { updates.close() }
updatesOutput = null
return AppResult.failure(checkNotNull(transitionsResult.errorOrNull()))
- .withAppError(AppErrorCode.MEASUREMENT, "Initialize activity recognition")
+ .withAppError(AppErrorCode.RECORDING, "Initialize activity recognition")
}
transitionsOutput = transitions
@@ -62,12 +62,12 @@ internal class ActivityRecognitionMeasurement(
updates.write(UPDATES_HEADER.toByteArray())
transitions.write(TRANSITIONS_HEADER.toByteArray())
}
- return headers.withAppError(AppErrorCode.MEASUREMENT, "Initialize activity recognition")
+ return headers.withAppError(AppErrorCode.RECORDING, "Initialize activity recognition")
}
suspend fun stop(): AppResult {
val results = listOf(platform.stop(), save())
- return results.combineAppResults(AppErrorCode.MEASUREMENT, "Stop activity recognition")
+ return results.combineAppResults(AppErrorCode.RECORDING, "Stop activity recognition")
}
private fun writeActivityUpdate(update: ActivityUpdate) {
@@ -96,7 +96,7 @@ internal class ActivityRecognitionMeasurement(
updatesOutput = null
transitionsOutput = null
writeFailure = null
- return results.combineAppResults(AppErrorCode.MEASUREMENT, "Save activity recognition")
+ return results.combineAppResults(AppErrorCode.RECORDING, "Save activity recognition")
}
private inline fun recordWriteFailure(operation: String, block: () -> Unit) {
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/GPSMeasurement.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/GpsRecording.kt
similarity index 86%
rename from sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/GPSMeasurement.kt
rename to sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/GpsRecording.kt
index dcb36f6..83d783e 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/GPSMeasurement.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/GpsRecording.kt
@@ -18,7 +18,7 @@ import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.withContext
import java.io.OutputStream
-internal class GPSMeasurement(
+internal class GpsRecording(
private val gpsHandler: GPSHandler,
private val storage: MeasurementStorage,
private val clock: EpochClock,
@@ -37,7 +37,7 @@ internal class GPSMeasurement(
minimumDistanceMeters: Int,
): AppResult = openOutput(folderName, useInternalStorage).flatMap {
gpsHandler.addCallback(this, intervalSeconds, minimumDistanceMeters)
- .withAppError(AppErrorCode.MEASUREMENT, "Start GPS")
+ .withAppError(AppErrorCode.RECORDING, "Start GPS")
}
private fun openOutput(folderName: String, useInternalStorage: Boolean): AppResult {
@@ -49,12 +49,12 @@ internal class GPSMeasurement(
)
val output = streamResult.getOrNull()
?: return AppResult.failure(checkNotNull(streamResult.errorOrNull()))
- .withAppError(AppErrorCode.MEASUREMENT, "Initialize GPS measurement")
+ .withAppError(AppErrorCode.RECORDING, "Initialize GPS recording")
outputStream = output
val headerResult = appResult(AppErrorCode.STORAGE, "Write GPS header") {
output.write(HEADER.toByteArray())
}
- return headerResult.withAppError(AppErrorCode.MEASUREMENT, "Initialize GPS measurement")
+ return headerResult.withAppError(AppErrorCode.RECORDING, "Initialize GPS recording")
}
private fun locationRow(location: Location): String = "${clock.nowMillis()};" +
@@ -67,23 +67,23 @@ internal class GPSMeasurement(
location.provider + "\n"
private fun pause(): AppResult = gpsHandler.gpsOff()
- .withAppError(AppErrorCode.MEASUREMENT, "Pause GPS")
+ .withAppError(AppErrorCode.RECORDING, "Pause GPS")
private fun save(): AppResult {
val stream = outputStream
val results = mutableListOf>()
- results += appResult(AppErrorCode.STORAGE, "Flush GPS measurement") { stream?.flush() }
+ results += appResult(AppErrorCode.STORAGE, "Flush GPS measurement file") { stream?.flush() }
writeFailure?.let { results += AppResult.failure(it) }
- results += appResult(AppErrorCode.STORAGE, "Close GPS measurement") { stream?.close() }
+ results += appResult(AppErrorCode.STORAGE, "Close GPS measurement file") { stream?.close() }
outputStream = null
writeFailure = null
- return results.combineAppResults(AppErrorCode.MEASUREMENT, "Save GPS measurement")
+ return results.combineAppResults(AppErrorCode.RECORDING, "Stop GPS recording")
}
suspend fun stop(): AppResult = listOf(
withContext(Dispatchers.Main) { pause() },
withContext(Dispatchers.IO) { save() },
- ).combineAppResults(AppErrorCode.MEASUREMENT, "Stop GPS measurement")
+ ).combineAppResults(AppErrorCode.RECORDING, "Stop GPS recording")
override fun onLocationChanged(location: Location?) {
val sample = location ?: return
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ExtraInfoHandler.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/MeasurementMetadataWriter.kt
similarity index 93%
rename from sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ExtraInfoHandler.kt
rename to sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/MeasurementMetadataWriter.kt
index 6225cfd..15084e9 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ExtraInfoHandler.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/MeasurementMetadataWriter.kt
@@ -13,26 +13,26 @@ import com.tomasrepcik.sensorbox.core.time.ClockFormats
import com.tomasrepcik.sensorbox.core.time.EpochClock
import com.tomasrepcik.sensorbox.recording.RecordingStopContext
import com.tomasrepcik.sensorbox.sensorservices.handlers.MeasurementStorage
-import com.tomasrepcik.sensorbox.sensorservices.intent.MeasurementLaunchRequest
+import com.tomasrepcik.sensorbox.sensorservices.intent.RecordingRequest
import com.tomasrepcik.sensorbox.sensorservices.types.SensorFileStats
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
/** Collects session metadata and writes it once when the recording is closed. */
-internal class ExtraInfoHandler(
+internal class MeasurementMetadataWriter(
private val storage: MeasurementStorage,
private val clock: EpochClock,
private val sensorManager: SensorManager,
) {
- private var request: MeasurementLaunchRequest? = null
+ private var request: RecordingRequest? = null
private var startedAtMillis: Long = 0L
private var startedAtNanos: Long = 0L
private var sensorStats: List = emptyList()
private val annotations = mutableListOf()
private val triggeredAlarms = mutableListOf()
- fun start(request: MeasurementLaunchRequest) {
+ fun start(request: RecordingRequest) {
this.request = request
val startedAt = capturePhoneTime()
startedAtMillis = startedAt.unixMillis
@@ -58,7 +58,7 @@ internal class ExtraInfoHandler(
val active = request ?: return AppResult.success(Unit)
request = null
val endedAt = capturePhoneTime()
- val metadataResult = appResult(AppErrorCode.MEASUREMENT, "Build measurement metadata") {
+ val metadataResult = appResult(AppErrorCode.RECORDING, "Build measurement metadata") {
MeasurementMetadata(
millis = startedAtMillis,
nanos = startedAtNanos,
@@ -96,10 +96,10 @@ internal class ExtraInfoHandler(
output.use { it.write(JSON.encodeToString(metadata).toByteArray()) }
}
}
- }.withAppError(AppErrorCode.MEASUREMENT, "Write measurement metadata")
+ }.withAppError(AppErrorCode.RECORDING, "Write measurement metadata")
}
- private fun sensorRanges(active: MeasurementLaunchRequest): List {
+ private fun sensorRanges(active: RecordingRequest): List {
val rangeIds = if (active.significantMotion) {
active.sensorIds + Sensor.TYPE_SIGNIFICANT_MOTION
} else {
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/SensorMeasurement.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/SensorRecording.kt
similarity index 83%
rename from sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/SensorMeasurement.kt
rename to sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/SensorRecording.kt
index e75015f..6294e1b 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/SensorMeasurement.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/SensorRecording.kt
@@ -17,7 +17,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
-internal class SensorMeasurement(
+internal class SensorRecording(
private val storage: MeasurementStorage,
private val diagnosticLogger: DiagnosticLogger,
private val sensorManager: SensorManager? = null,
@@ -42,7 +42,7 @@ internal class SensorMeasurement(
val holderResult = createHolder(folderName, useInternalStorage, sensorType)
if (holderResult.isFailure) {
return AppResult.failure(checkNotNull(holderResult.errorOrNull()))
- .withAppError(AppErrorCode.MEASUREMENT, "Initialize sensors")
+ .withAppError(AppErrorCode.RECORDING, "Initialize sensors")
}
holderResult.getOrNull()?.let(holders::add)
}
@@ -67,11 +67,11 @@ internal class SensorMeasurement(
private fun registerSensors(samplingPeriod: Int): AppResult {
val sensorManager = sensorManager
- ?: return AppResult.failure(AppError(AppErrorCode.MEASUREMENT, "Access sensor manager"))
+ ?: return AppResult.failure(AppError(AppErrorCode.RECORDING, "Access sensor manager"))
for (holder in holders) {
val registration = registerHolder(sensorManager, holder, samplingPeriod)
if (registration.isFailure) {
- return registration.withAppError(AppErrorCode.MEASUREMENT, "Start sensors")
+ return registration.withAppError(AppErrorCode.RECORDING, "Start sensors")
}
}
return AppResult.success(Unit)
@@ -83,8 +83,8 @@ internal class SensorMeasurement(
samplingPeriod: Int,
): AppResult {
val sensor = sensorManager.getDefaultSensor(holder.spec.type)
- ?: return AppResult.failure(AppError(AppErrorCode.MEASUREMENT, "Find sensor ${holder.spec.type}"))
- val registration = appResult(AppErrorCode.MEASUREMENT, "Register sensor ${holder.spec.type}") {
+ ?: return AppResult.failure(AppError(AppErrorCode.RECORDING, "Find sensor ${holder.spec.type}"))
+ val registration = appResult(AppErrorCode.RECORDING, "Register sensor ${holder.spec.type}") {
sensorManager.registerListener(holder, sensor, samplingPeriod)
}
return if (registration.getOrNull() == true) {
@@ -92,12 +92,12 @@ internal class SensorMeasurement(
} else {
AppResult.failure(
registration.errorOrNull()
- ?: AppError(AppErrorCode.MEASUREMENT, "Register sensor ${holder.spec.type}"),
+ ?: AppError(AppErrorCode.RECORDING, "Register sensor ${holder.spec.type}"),
)
}
}
- private fun pause(): AppResult = appResult(AppErrorCode.MEASUREMENT, "Pause sensors") {
+ private fun pause(): AppResult = appResult(AppErrorCode.RECORDING, "Pause sensors") {
sensorManager?.let { manager -> holders.forEach(manager::unregisterListener) }
}
@@ -105,11 +105,11 @@ internal class SensorMeasurement(
val results = holders.map { it.close() }
onStopped(holders.map(SensorHolder::stats))
holders.clear()
- return results.combineAppResults(AppErrorCode.MEASUREMENT, "Save sensors")
+ return results.combineAppResults(AppErrorCode.RECORDING, "Save sensors")
}
suspend fun stop(): AppResult = listOf(
pause(),
save(),
- ).combineAppResults(AppErrorCode.MEASUREMENT, "Stop sensors")
+ ).combineAppResults(AppErrorCode.RECORDING, "Stop sensors")
}
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/SignificantMotion.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/SignificantMotionRecording.kt
similarity index 83%
rename from sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/SignificantMotion.kt
rename to sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/SignificantMotionRecording.kt
index 320e3b5..307c558 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/SignificantMotion.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/SignificantMotionRecording.kt
@@ -18,8 +18,10 @@ import kotlinx.coroutines.flow.asSharedFlow
import java.io.OutputStream
/** Handles Android's one-shot significant-motion trigger and re-arms it after every event. */
-internal class SignificantMotion(private val storage: MeasurementStorage, private val sensorManager: SensorManager) :
- TriggerEventListener() {
+internal class SignificantMotionRecording(
+ private val storage: MeasurementStorage,
+ private val sensorManager: SensorManager,
+) : TriggerEventListener() {
private var sensor: Sensor? = null
private var output: OutputStream? = null
private var writeFailure: AppError? = null
@@ -32,7 +34,7 @@ internal class SignificantMotion(private val storage: MeasurementStorage, privat
if (arm()) {
AppResult.success(Unit)
} else {
- AppResult.failure(AppError(AppErrorCode.MEASUREMENT, "Start significant motion"))
+ AppResult.failure(AppError(AppErrorCode.RECORDING, "Start significant motion"))
}
}
@@ -49,11 +51,11 @@ internal class SignificantMotion(private val storage: MeasurementStorage, privat
appResult(AppErrorCode.STORAGE, "Write significant motion header") {
opened.write("t_sensor;event\n".toByteArray())
}
- }.withAppError(AppErrorCode.MEASUREMENT, "Initialize significant motion")
+ }.withAppError(AppErrorCode.RECORDING, "Initialize significant motion")
}
private fun pause(): AppResult = appResult(
- AppErrorCode.MEASUREMENT,
+ AppErrorCode.RECORDING,
"Pause significant motion",
) {
sensor?.let { sensorManager.cancelTriggerSensor(this, it) }
@@ -65,13 +67,13 @@ internal class SignificantMotion(private val storage: MeasurementStorage, privat
writeFailure?.let { results += AppResult.failure(it) }
output = null
writeFailure = null
- return results.combineAppResults(AppErrorCode.MEASUREMENT, "Save significant motion")
+ return results.combineAppResults(AppErrorCode.RECORDING, "Save significant motion")
}
suspend fun stop(): AppResult {
val results = listOf(pause(), save())
sensor = null
- return results.combineAppResults(AppErrorCode.MEASUREMENT, "Stop significant motion")
+ return results.combineAppResults(AppErrorCode.RECORDING, "Stop significant motion")
}
override fun onTrigger(event: TriggerEvent?) {
@@ -80,7 +82,7 @@ internal class SignificantMotion(private val storage: MeasurementStorage, privat
output?.write("${event.timestamp};$value\n".toByteArray())
}.onFailure(::reportFailure)
}
- if (!arm()) reportFailure(AppError(AppErrorCode.MEASUREMENT, "Re-arm significant motion"))
+ if (!arm()) reportFailure(AppError(AppErrorCode.RECORDING, "Re-arm significant motion"))
}
private fun reportFailure(error: AppError) {
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/intent/MeasurementIntentFactory.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/intent/MeasurementIntentFactory.kt
deleted file mode 100644
index 924e37d..0000000
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/intent/MeasurementIntentFactory.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.tomasrepcik.sensorbox.sensorservices.intent
-
-import android.content.Context
-import android.content.Intent
-import com.tomasrepcik.sensorbox.core.time.ClockFormats
-import com.tomasrepcik.sensorbox.core.time.EpochClock
-import com.tomasrepcik.sensorbox.sensorservices.services.MeasurementService
-import dagger.hilt.android.qualifiers.ApplicationContext
-import javax.inject.Inject
-
-class MeasurementIntentFactory @Inject constructor(
- @ApplicationContext private val context: Context,
- private val clock: EpochClock,
-) {
- fun create(request: MeasurementLaunchRequest): Intent = Intent(context, MeasurementService::class.java).apply {
- putExtra(MeasurementService.SESSION_ID, request.sessionId)
- putExtra(MeasurementService.FOLDER_NAME, request.folderName)
- putExtra(MeasurementService.INTERNAL_STORAGE, request.useInternalStorage)
- putExtra(MeasurementService.ANDROID_SENSORS, request.sensorIds.toIntArray())
- putExtra(MeasurementService.ANDROID_SENSORS_SPEED, request.sensorSamplingPeriod)
- putExtra(MeasurementService.GPS, request.includesGps)
- putExtra(MeasurementService.STOP_ON_LOW_BATTERY, request.stopOnLowBattery)
- putExtra(MeasurementService.USE_WAKE_LOCK, request.useWakeLock)
- putExtra(MeasurementService.GPS_INTERVAL_SECONDS, request.gpsIntervalSeconds)
- putExtra(MeasurementService.GPS_DISTANCE_METERS, request.gpsMinDistanceMeters)
- putExtra(MeasurementService.DURATION_MILLIS, request.durationMillis)
- putStringArrayListExtra(MeasurementService.NOTES, ArrayList(request.notes))
- putExtra(MeasurementService.ALARM_OFFSETS_SECONDS, request.alarmOffsetsSeconds.toIntArray())
- putExtra(MeasurementService.ACTIVITY_RECOGNITION, request.activityRecognition)
- putExtra(MeasurementService.ACTIVITY_RECOGNITION_PERIOD_SECONDS, request.activityRecognitionPeriodSeconds)
- putExtra(MeasurementService.SIGNIFICANT_MOTION, request.significantMotion)
- }
-
- fun newFolderName(customName: String = ""): String {
- val prefix = customName.trim().replace(INVALID_NAME_CHARS, "_").trim('_').take(MAX_PREFIX_LENGTH)
- .ifBlank { "recording" }
- val timestamp = ClockFormats.folderTimestamp(clock.nowMillis())
- return "${prefix}_$timestamp"
- }
-
- private companion object {
- const val MAX_PREFIX_LENGTH = 60
- val INVALID_NAME_CHARS = Regex("[^A-Za-z0-9._-]+")
- }
-}
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/intent/MeasurementLaunchRequest.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/intent/MeasurementLaunchRequest.kt
deleted file mode 100644
index 6089514..0000000
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/intent/MeasurementLaunchRequest.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.tomasrepcik.sensorbox.sensorservices.intent
-
-import android.content.Intent
-import android.hardware.SensorManager
-import com.tomasrepcik.sensorbox.sensorservices.services.MeasurementService
-import java.util.UUID
-
-data class MeasurementLaunchRequest(
- val sessionId: String = UUID.randomUUID().toString(),
- val folderName: String,
- val useInternalStorage: Boolean,
- val sensorIds: Set,
- val sensorSamplingPeriod: Int,
- val includesGps: Boolean,
- val stopOnLowBattery: Boolean,
- val useWakeLock: Boolean,
- val gpsIntervalSeconds: Int,
- val gpsMinDistanceMeters: Int,
- val durationMillis: Long = 0L,
- val notes: List = emptyList(),
- val alarmOffsetsSeconds: List = emptyList(),
- val activityRecognition: Boolean = false,
- val activityRecognitionPeriodSeconds: Int = 30,
- val significantMotion: Boolean = false,
-) {
- val requiresWakeLock: Boolean
- get() = useWakeLock || sensorIds.isNotEmpty()
-
- companion object {
- fun from(intent: Intent): MeasurementLaunchRequest = MeasurementLaunchRequest(
- sessionId = intent.getStringExtra(MeasurementService.SESSION_ID).orEmpty(),
- folderName = intent.getStringExtra(MeasurementService.FOLDER_NAME).orEmpty(),
- useInternalStorage = intent.getBooleanExtra(MeasurementService.INTERNAL_STORAGE, false),
- sensorIds = intent.getIntArrayExtra(MeasurementService.ANDROID_SENSORS)?.toSet().orEmpty(),
- sensorSamplingPeriod = intent.getIntExtra(
- MeasurementService.ANDROID_SENSORS_SPEED,
- SensorManager.SENSOR_DELAY_FASTEST,
- ),
- includesGps = intent.getBooleanExtra(MeasurementService.GPS, false),
- stopOnLowBattery = intent.getBooleanExtra(MeasurementService.STOP_ON_LOW_BATTERY, false),
- useWakeLock = intent.getBooleanExtra(MeasurementService.USE_WAKE_LOCK, false),
- gpsIntervalSeconds = intent.getIntExtra(MeasurementService.GPS_INTERVAL_SECONDS, 10),
- gpsMinDistanceMeters = intent.getIntExtra(MeasurementService.GPS_DISTANCE_METERS, 20),
- durationMillis = intent.getLongExtra(MeasurementService.DURATION_MILLIS, 0L).coerceAtLeast(0L),
- notes = intent.getStringArrayListExtra(MeasurementService.NOTES).orEmpty(),
- alarmOffsetsSeconds = intent.getIntArrayExtra(MeasurementService.ALARM_OFFSETS_SECONDS)
- ?.filter { it >= 0 }.orEmpty(),
- activityRecognition = intent.getBooleanExtra(MeasurementService.ACTIVITY_RECOGNITION, false),
- activityRecognitionPeriodSeconds = intent.getIntExtra(
- MeasurementService.ACTIVITY_RECOGNITION_PERIOD_SECONDS,
- 30,
- ).coerceAtLeast(1),
- significantMotion = intent.getBooleanExtra(MeasurementService.SIGNIFICANT_MOTION, false),
- )
- }
-}
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/intent/RecordingIntentFactory.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/intent/RecordingIntentFactory.kt
new file mode 100644
index 0000000..c20e461
--- /dev/null
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/intent/RecordingIntentFactory.kt
@@ -0,0 +1,45 @@
+package com.tomasrepcik.sensorbox.sensorservices.intent
+
+import android.content.Context
+import android.content.Intent
+import com.tomasrepcik.sensorbox.core.time.ClockFormats
+import com.tomasrepcik.sensorbox.core.time.EpochClock
+import com.tomasrepcik.sensorbox.sensorservices.services.RecordingService
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+
+class RecordingIntentFactory @Inject constructor(
+ @ApplicationContext private val context: Context,
+ private val clock: EpochClock,
+) {
+ fun create(request: RecordingRequest): Intent = Intent(context, RecordingService::class.java).apply {
+ putExtra(RecordingService.SESSION_ID, request.sessionId)
+ putExtra(RecordingService.FOLDER_NAME, request.folderName)
+ putExtra(RecordingService.INTERNAL_STORAGE, request.useInternalStorage)
+ putExtra(RecordingService.ANDROID_SENSORS, request.sensorIds.toIntArray())
+ putExtra(RecordingService.ANDROID_SENSORS_SPEED, request.sensorSamplingPeriod)
+ putExtra(RecordingService.GPS, request.includesGps)
+ putExtra(RecordingService.STOP_ON_LOW_BATTERY, request.stopOnLowBattery)
+ putExtra(RecordingService.USE_WAKE_LOCK, request.useWakeLock)
+ putExtra(RecordingService.GPS_INTERVAL_SECONDS, request.gpsIntervalSeconds)
+ putExtra(RecordingService.GPS_DISTANCE_METERS, request.gpsMinDistanceMeters)
+ putExtra(RecordingService.DURATION_MILLIS, request.durationMillis)
+ putStringArrayListExtra(RecordingService.NOTES, ArrayList(request.notes))
+ putExtra(RecordingService.ALARM_OFFSETS_SECONDS, request.alarmOffsetsSeconds.toIntArray())
+ putExtra(RecordingService.ACTIVITY_RECOGNITION, request.activityRecognition)
+ putExtra(RecordingService.ACTIVITY_RECOGNITION_PERIOD_SECONDS, request.activityRecognitionPeriodSeconds)
+ putExtra(RecordingService.SIGNIFICANT_MOTION, request.significantMotion)
+ }
+
+ fun newFolderName(customName: String = ""): String {
+ val prefix = customName.trim().replace(INVALID_NAME_CHARS, "_").trim('_').take(MAX_PREFIX_LENGTH)
+ .ifBlank { "recording" }
+ val timestamp = ClockFormats.folderTimestamp(clock.nowMillis())
+ return "${prefix}_$timestamp"
+ }
+
+ private companion object {
+ const val MAX_PREFIX_LENGTH = 60
+ val INVALID_NAME_CHARS = Regex("[^A-Za-z0-9._-]+")
+ }
+}
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/intent/RecordingRequest.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/intent/RecordingRequest.kt
new file mode 100644
index 0000000..c8fb454
--- /dev/null
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/intent/RecordingRequest.kt
@@ -0,0 +1,56 @@
+package com.tomasrepcik.sensorbox.sensorservices.intent
+
+import android.content.Intent
+import android.hardware.SensorManager
+import com.tomasrepcik.sensorbox.sensorservices.services.RecordingService
+import java.util.UUID
+
+data class RecordingRequest(
+ val sessionId: String = UUID.randomUUID().toString(),
+ val folderName: String,
+ val useInternalStorage: Boolean,
+ val sensorIds: Set,
+ val sensorSamplingPeriod: Int,
+ val includesGps: Boolean,
+ val stopOnLowBattery: Boolean,
+ val useWakeLock: Boolean,
+ val gpsIntervalSeconds: Int,
+ val gpsMinDistanceMeters: Int,
+ val durationMillis: Long = 0L,
+ val notes: List = emptyList(),
+ val alarmOffsetsSeconds: List = emptyList(),
+ val activityRecognition: Boolean = false,
+ val activityRecognitionPeriodSeconds: Int = 30,
+ val significantMotion: Boolean = false,
+) {
+ val requiresWakeLock: Boolean
+ get() = useWakeLock || sensorIds.isNotEmpty()
+
+ companion object {
+ fun from(intent: Intent): RecordingRequest = RecordingRequest(
+ sessionId = intent.getStringExtra(RecordingService.SESSION_ID).orEmpty(),
+ folderName = intent.getStringExtra(RecordingService.FOLDER_NAME).orEmpty(),
+ useInternalStorage = intent.getBooleanExtra(RecordingService.INTERNAL_STORAGE, false),
+ sensorIds = intent.getIntArrayExtra(RecordingService.ANDROID_SENSORS)?.toSet().orEmpty(),
+ sensorSamplingPeriod = intent.getIntExtra(
+ RecordingService.ANDROID_SENSORS_SPEED,
+ SensorManager.SENSOR_DELAY_FASTEST,
+ ),
+ includesGps = intent.getBooleanExtra(RecordingService.GPS, false),
+ stopOnLowBattery = intent.getBooleanExtra(RecordingService.STOP_ON_LOW_BATTERY, false),
+ useWakeLock = intent.getBooleanExtra(RecordingService.USE_WAKE_LOCK, false),
+ gpsIntervalSeconds = intent.getIntExtra(RecordingService.GPS_INTERVAL_SECONDS, 10),
+ gpsMinDistanceMeters = intent.getIntExtra(RecordingService.GPS_DISTANCE_METERS, 20),
+ durationMillis = intent.getLongExtra(RecordingService.DURATION_MILLIS, 0L).coerceAtLeast(0L),
+ notes = intent.getStringArrayListExtra(RecordingService.NOTES).orEmpty(),
+ alarmOffsetsSeconds = intent.getIntArrayExtra(RecordingService.ALARM_OFFSETS_SECONDS)
+ ?.filter { it >= 0 }.orEmpty(),
+ activityRecognition = intent.getBooleanExtra(RecordingService.ACTIVITY_RECOGNITION, false),
+ activityRecognitionPeriodSeconds = intent.getIntExtra(
+ RecordingService.ACTIVITY_RECOGNITION_PERIOD_SECONDS,
+ 30,
+ ).coerceAtLeast(1),
+ significantMotion = intent.getBooleanExtra(RecordingService.SIGNIFICANT_MOTION, false),
+ )
+ }
+}
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/ActivityRecordingSource.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/ActivityRecordingSource.kt
index 0c57781..2becec0 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/ActivityRecordingSource.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/ActivityRecordingSource.kt
@@ -6,22 +6,22 @@ import com.tomasrepcik.sensorbox.recording.RecordingSourceSpec
import com.tomasrepcik.sensorbox.recording.RecordingSourceType
import com.tomasrepcik.sensorbox.recording.RecordingStopContext
import com.tomasrepcik.sensorbox.sensorservices.handlers.MeasurementStorage
-import com.tomasrepcik.sensorbox.sensorservices.handlers.measurements.ActivityRecognitionMeasurement
import com.tomasrepcik.sensorbox.sensorservices.handlers.measurements.ActivityRecognitionPlatform
-import com.tomasrepcik.sensorbox.sensorservices.intent.MeasurementLaunchRequest
+import com.tomasrepcik.sensorbox.sensorservices.handlers.measurements.ActivityRecognitionRecording
+import com.tomasrepcik.sensorbox.sensorservices.intent.RecordingRequest
internal class ActivityRecordingSource(
- private val request: MeasurementLaunchRequest,
+ private val request: RecordingRequest,
private val storage: MeasurementStorage,
private val platform: ActivityRecognitionPlatform,
) : RecordingSource {
- private val measurement = ActivityRecognitionMeasurement(storage, platform)
+ private val recording = ActivityRecognitionRecording(storage, platform)
override val type = RecordingSourceType.ACTIVITY_RECOGNITION
- override val failures = measurement.failures
+ override val failures = recording.failures
override suspend fun start(spec: RecordingSourceSpec): AppResult =
if (spec is RecordingSourceSpec.ActivityRecognition) {
- measurement.start(
+ recording.start(
folderName = request.folderName,
useInternalStorage = request.useInternalStorage,
periodSeconds = spec.periodSeconds,
@@ -30,5 +30,5 @@ internal class ActivityRecordingSource(
invalidSpec(type)
}
- override suspend fun stop(context: RecordingStopContext): AppResult = measurement.stop()
+ override suspend fun stop(context: RecordingStopContext): AppResult = recording.stop()
}
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/AndroidRecordingSources.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/AndroidRecordingSources.kt
index 1e3e3ed..351082b 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/AndroidRecordingSources.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/AndroidRecordingSources.kt
@@ -10,35 +10,35 @@ import com.tomasrepcik.sensorbox.recording.RecordingSource
import com.tomasrepcik.sensorbox.sensorservices.handlers.GPSHandler
import com.tomasrepcik.sensorbox.sensorservices.handlers.MeasurementStorage
import com.tomasrepcik.sensorbox.sensorservices.handlers.measurements.AndroidActivityRecognitionPlatform
-import com.tomasrepcik.sensorbox.sensorservices.handlers.measurements.GPSMeasurement
-import com.tomasrepcik.sensorbox.sensorservices.handlers.measurements.SensorMeasurement
-import com.tomasrepcik.sensorbox.sensorservices.handlers.measurements.SignificantMotion
-import com.tomasrepcik.sensorbox.sensorservices.intent.MeasurementLaunchRequest
+import com.tomasrepcik.sensorbox.sensorservices.handlers.measurements.GpsRecording
+import com.tomasrepcik.sensorbox.sensorservices.handlers.measurements.SensorRecording
+import com.tomasrepcik.sensorbox.sensorservices.handlers.measurements.SignificantMotionRecording
+import com.tomasrepcik.sensorbox.sensorservices.intent.RecordingRequest
internal class AndroidRecordingSources(
context: Context,
- request: MeasurementLaunchRequest,
+ request: RecordingRequest,
storage: MeasurementStorage,
diagnosticLogger: DiagnosticLogger,
clock: EpochClock,
) {
private val sensorManager = context.getSystemService(SensorManager::class.java)
- private val session = SessionRecordingSource(request, storage, clock, sensorManager)
+ private val sessionMetadata = SessionMetadataRecordingSource(request, storage, clock, sensorManager)
val sources: List = listOf(
- session,
+ sessionMetadata,
SensorRecordingSource(
request,
- SensorMeasurement(
+ SensorRecording(
storage = storage,
diagnosticLogger = diagnosticLogger,
sensorManager = sensorManager,
- onStopped = session::recordSensorStats,
+ onStopped = sessionMetadata::recordSensorStats,
),
),
GpsRecordingSource(
request,
- GPSMeasurement(GPSHandler(LocationServices.getFusedLocationProviderClient(context)), storage, clock),
+ GpsRecording(GPSHandler(LocationServices.getFusedLocationProviderClient(context)), storage, clock),
),
ActivityRecordingSource(
request,
@@ -47,11 +47,11 @@ internal class AndroidRecordingSources(
),
SignificantMotionRecordingSource(
request,
- SignificantMotion(storage, sensorManager),
+ SignificantMotionRecording(storage, sensorManager),
),
)
- fun annotate(timestampMillis: Long, text: String): AppResult = session.annotate(timestampMillis, text)
+ fun annotate(timestampMillis: Long, text: String): AppResult = sessionMetadata.annotate(timestampMillis, text)
- fun playAlarm(): AppResult = session.playAlarm()
+ fun playAlarm(): AppResult = sessionMetadata.playAlarm()
}
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/GpsRecordingSource.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/GpsRecordingSource.kt
index e031e90..f5bb233 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/GpsRecordingSource.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/GpsRecordingSource.kt
@@ -5,18 +5,16 @@ import com.tomasrepcik.sensorbox.recording.RecordingSource
import com.tomasrepcik.sensorbox.recording.RecordingSourceSpec
import com.tomasrepcik.sensorbox.recording.RecordingSourceType
import com.tomasrepcik.sensorbox.recording.RecordingStopContext
-import com.tomasrepcik.sensorbox.sensorservices.handlers.measurements.GPSMeasurement
-import com.tomasrepcik.sensorbox.sensorservices.intent.MeasurementLaunchRequest
+import com.tomasrepcik.sensorbox.sensorservices.handlers.measurements.GpsRecording
+import com.tomasrepcik.sensorbox.sensorservices.intent.RecordingRequest
-internal class GpsRecordingSource(
- private val request: MeasurementLaunchRequest,
- private val measurement: GPSMeasurement,
-) : RecordingSource {
+internal class GpsRecordingSource(private val request: RecordingRequest, private val recording: GpsRecording) :
+ RecordingSource {
override val type = RecordingSourceType.GPS
- override val failures = measurement.failures
+ override val failures = recording.failures
override suspend fun start(spec: RecordingSourceSpec): AppResult = if (spec is RecordingSourceSpec.Gps) {
- measurement.start(
+ recording.start(
folderName = request.folderName,
useInternalStorage = request.useInternalStorage,
intervalSeconds = spec.intervalSeconds,
@@ -26,5 +24,5 @@ internal class GpsRecordingSource(
invalidSpec(type)
}
- override suspend fun stop(context: RecordingStopContext): AppResult = measurement.stop()
+ override suspend fun stop(context: RecordingStopContext): AppResult = recording.stop()
}
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/SensorRecordingSource.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/SensorRecordingSource.kt
index 3cadc8c..42bd070 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/SensorRecordingSource.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/SensorRecordingSource.kt
@@ -5,18 +5,16 @@ import com.tomasrepcik.sensorbox.recording.RecordingSource
import com.tomasrepcik.sensorbox.recording.RecordingSourceSpec
import com.tomasrepcik.sensorbox.recording.RecordingSourceType
import com.tomasrepcik.sensorbox.recording.RecordingStopContext
-import com.tomasrepcik.sensorbox.sensorservices.handlers.measurements.SensorMeasurement
-import com.tomasrepcik.sensorbox.sensorservices.intent.MeasurementLaunchRequest
+import com.tomasrepcik.sensorbox.sensorservices.handlers.measurements.SensorRecording
+import com.tomasrepcik.sensorbox.sensorservices.intent.RecordingRequest
-internal class SensorRecordingSource(
- private val request: MeasurementLaunchRequest,
- private val measurement: SensorMeasurement,
-) : RecordingSource {
+internal class SensorRecordingSource(private val request: RecordingRequest, private val recording: SensorRecording) :
+ RecordingSource {
override val type = RecordingSourceType.SENSOR
- override val failures = measurement.failures
+ override val failures = recording.failures
override suspend fun start(spec: RecordingSourceSpec): AppResult = if (spec is RecordingSourceSpec.Sensors) {
- measurement.start(
+ recording.start(
folderName = request.folderName,
useInternalStorage = request.useInternalStorage,
sensorTypes = spec.sensorTypes,
@@ -26,5 +24,5 @@ internal class SensorRecordingSource(
invalidSpec(type)
}
- override suspend fun stop(context: RecordingStopContext): AppResult = measurement.stop()
+ override suspend fun stop(context: RecordingStopContext): AppResult = recording.stop()
}
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/ServiceController.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/ServiceController.kt
index 15d4dcd..4a28f95 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/ServiceController.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/ServiceController.kt
@@ -6,18 +6,18 @@ import com.tomasrepcik.sensorbox.core.error.DiagnosticLogger
import com.tomasrepcik.sensorbox.core.time.EpochClock
import com.tomasrepcik.sensorbox.recording.RecordingEngine
import com.tomasrepcik.sensorbox.recording.RecordingEvent
-import com.tomasrepcik.sensorbox.recording.RecordingPlan
import com.tomasrepcik.sensorbox.recording.RecordingSessionId
import com.tomasrepcik.sensorbox.recording.RecordingSourceSpec
import com.tomasrepcik.sensorbox.recording.RecordingStopReason
import com.tomasrepcik.sensorbox.sensorservices.handlers.MeasurementStorage
-import com.tomasrepcik.sensorbox.sensorservices.intent.MeasurementLaunchRequest
+import com.tomasrepcik.sensorbox.sensorservices.intent.RecordingRequest
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharedFlow
+import com.tomasrepcik.sensorbox.recording.RecordingRequest as EngineRecordingRequest
internal class ServiceController(
context: Context,
- private val request: MeasurementLaunchRequest,
+ private val request: RecordingRequest,
scope: CoroutineScope,
storage: MeasurementStorage,
diagnosticLogger: DiagnosticLogger,
@@ -32,7 +32,7 @@ internal class ServiceController(
val events: SharedFlow = engine.events
- suspend fun start(): AppResult = engine.start(request.toRecordingPlan(sessionId))
+ suspend fun start(): AppResult = engine.start(request.toEngineRequest(sessionId))
suspend fun stop(reason: RecordingStopReason): AppResult = engine.stop(reason)
@@ -40,15 +40,15 @@ internal class ServiceController(
fun playAlarm(): AppResult = androidSources.playAlarm()
- private fun MeasurementLaunchRequest.toRecordingPlan(sessionId: RecordingSessionId): RecordingPlan {
+ private fun RecordingRequest.toEngineRequest(sessionId: RecordingSessionId): EngineRecordingRequest {
val specs = buildList {
- add(RecordingSourceSpec.Session)
+ add(RecordingSourceSpec.SessionMetadata)
if (sensorIds.isNotEmpty()) add(RecordingSourceSpec.Sensors(sensorIds, sensorSamplingPeriod))
if (includesGps) add(RecordingSourceSpec.Gps(gpsIntervalSeconds, gpsMinDistanceMeters))
if (activityRecognition) add(RecordingSourceSpec.ActivityRecognition(activityRecognitionPeriodSeconds))
if (significantMotion) add(RecordingSourceSpec.SignificantMotion)
}
- return RecordingPlan(
+ return EngineRecordingRequest(
sessionId = sessionId,
sources = specs,
durationMillis = durationMillis,
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/ServiceControllerFactory.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/ServiceControllerFactory.kt
index b8855e1..4e2b6b2 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/ServiceControllerFactory.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/ServiceControllerFactory.kt
@@ -4,7 +4,7 @@ import android.content.Context
import com.tomasrepcik.sensorbox.core.error.DiagnosticLogger
import com.tomasrepcik.sensorbox.core.time.EpochClock
import com.tomasrepcik.sensorbox.sensorservices.handlers.StorageHandler
-import com.tomasrepcik.sensorbox.sensorservices.intent.MeasurementLaunchRequest
+import com.tomasrepcik.sensorbox.sensorservices.intent.RecordingRequest
import kotlinx.coroutines.CoroutineScope
import javax.inject.Inject
@@ -13,7 +13,7 @@ internal class ServiceControllerFactory @Inject constructor(
private val diagnosticLogger: DiagnosticLogger,
private val clock: EpochClock,
) {
- fun create(context: Context, request: MeasurementLaunchRequest, scope: CoroutineScope) = ServiceController(
+ fun create(context: Context, request: RecordingRequest, scope: CoroutineScope) = ServiceController(
context = context,
request = request,
scope = scope,
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/SessionRecordingSource.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/SessionMetadataRecordingSource.kt
similarity index 62%
rename from sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/SessionRecordingSource.kt
rename to sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/SessionMetadataRecordingSource.kt
index 9cc31c0..831109d 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/SessionRecordingSource.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/SessionMetadataRecordingSource.kt
@@ -13,55 +13,56 @@ import com.tomasrepcik.sensorbox.recording.RecordingSourceSpec
import com.tomasrepcik.sensorbox.recording.RecordingSourceType
import com.tomasrepcik.sensorbox.recording.RecordingStopContext
import com.tomasrepcik.sensorbox.sensorservices.handlers.MeasurementStorage
-import com.tomasrepcik.sensorbox.sensorservices.handlers.measurements.ExtraInfoHandler
-import com.tomasrepcik.sensorbox.sensorservices.intent.MeasurementLaunchRequest
+import com.tomasrepcik.sensorbox.sensorservices.handlers.measurements.MeasurementMetadataWriter
+import com.tomasrepcik.sensorbox.sensorservices.intent.RecordingRequest
import com.tomasrepcik.sensorbox.sensorservices.types.SensorFileStats
-internal class SessionRecordingSource(
- private val request: MeasurementLaunchRequest,
+internal class SessionMetadataRecordingSource(
+ private val request: RecordingRequest,
private val storage: MeasurementStorage,
clock: EpochClock,
sensorManager: SensorManager,
) : RecordingSource {
- private val extraInfo = ExtraInfoHandler(storage, clock, sensorManager)
+ private val metadataWriter = MeasurementMetadataWriter(storage, clock, sensorManager)
private var toneGenerator: ToneGenerator? = null
- override val type = RecordingSourceType.SESSION
+ override val type = RecordingSourceType.SESSION_METADATA
- override suspend fun start(spec: RecordingSourceSpec): AppResult = if (spec is RecordingSourceSpec.Session) {
- storage.createMeasurementDirectory(request.folderName, request.useInternalStorage)
- .onSuccess { extraInfo.start(request) }
- } else {
- invalidSpec(type)
- }
+ override suspend fun start(spec: RecordingSourceSpec): AppResult =
+ if (spec is RecordingSourceSpec.SessionMetadata) {
+ storage.createMeasurementDirectory(request.folderName, request.useInternalStorage)
+ .onSuccess { metadataWriter.start(request) }
+ } else {
+ invalidSpec(type)
+ }
fun annotate(timestampMillis: Long, text: String): AppResult = appResult(
- AppErrorCode.MEASUREMENT,
+ AppErrorCode.RECORDING,
"Add measurement annotation",
) {
- extraInfo.annotate(timestampMillis, text)
+ metadataWriter.annotate(timestampMillis, text)
}
- fun playAlarm(): AppResult = appResult(AppErrorCode.MEASUREMENT, "Play measurement alarm") {
- extraInfo.alarmTriggered()
+ fun playAlarm(): AppResult = appResult(AppErrorCode.RECORDING, "Play recording alarm") {
+ metadataWriter.alarmTriggered()
val tone = toneGenerator ?: ToneGenerator(AudioManager.STREAM_ALARM, 100).also { toneGenerator = it }
tone.startTone(ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD, ALARM_DURATION_MILLIS)
}
fun recordSensorStats(stats: List) {
- extraInfo.recordSensorStats(stats)
+ metadataWriter.recordSensorStats(stats)
}
override suspend fun stop(context: RecordingStopContext): AppResult {
- val releaseTone = appResult(AppErrorCode.MEASUREMENT, "Release measurement alarm") {
+ val releaseTone = appResult(AppErrorCode.RECORDING, "Release recording alarm") {
toneGenerator?.release()
toneGenerator = null
}
val metadataContext = releaseTone.errorOrNull()
?.let(context::withFailure)
?: context
- return listOf(releaseTone, extraInfo.write(metadataContext))
- .combineAppResults(AppErrorCode.MEASUREMENT, "Close measurement session artifacts")
+ return listOf(releaseTone, metadataWriter.write(metadataContext))
+ .combineAppResults(AppErrorCode.RECORDING, "Close recording session metadata")
}
private companion object {
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/SignificantMotionRecordingSource.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/SignificantMotionRecordingSource.kt
index 59c3aca..af200fe 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/SignificantMotionRecordingSource.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/serviceController/SignificantMotionRecordingSource.kt
@@ -5,22 +5,22 @@ import com.tomasrepcik.sensorbox.recording.RecordingSource
import com.tomasrepcik.sensorbox.recording.RecordingSourceSpec
import com.tomasrepcik.sensorbox.recording.RecordingSourceType
import com.tomasrepcik.sensorbox.recording.RecordingStopContext
-import com.tomasrepcik.sensorbox.sensorservices.handlers.measurements.SignificantMotion
-import com.tomasrepcik.sensorbox.sensorservices.intent.MeasurementLaunchRequest
+import com.tomasrepcik.sensorbox.sensorservices.handlers.measurements.SignificantMotionRecording
+import com.tomasrepcik.sensorbox.sensorservices.intent.RecordingRequest
internal class SignificantMotionRecordingSource(
- private val request: MeasurementLaunchRequest,
- private val measurement: SignificantMotion,
+ private val request: RecordingRequest,
+ private val recording: SignificantMotionRecording,
) : RecordingSource {
override val type = RecordingSourceType.SIGNIFICANT_MOTION
- override val failures = measurement.failures
+ override val failures = recording.failures
override suspend fun start(spec: RecordingSourceSpec): AppResult =
if (spec is RecordingSourceSpec.SignificantMotion) {
- measurement.start(request.folderName, request.useInternalStorage)
+ recording.start(request.folderName, request.useInternalStorage)
} else {
invalidSpec(type)
}
- override suspend fun stop(context: RecordingStopContext): AppResult = measurement.stop()
+ override suspend fun stop(context: RecordingStopContext): AppResult = recording.stop()
}
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/Notify.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/Notify.kt
index 15a5440..5377fdd 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/Notify.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/Notify.kt
@@ -13,8 +13,8 @@ import com.tomasrepcik.sensorbox.sensorservices.R
object Notify {
fun createRecordingNotification(context: Context): Notification {
createChannel(context)
- val stopIntent = Intent(context, MeasurementService::class.java)
- .setAction(MeasurementService.ACTION_STOP)
+ val stopIntent = Intent(context, RecordingService::class.java)
+ .setAction(RecordingService.ACTION_STOP_RECORDING)
val stopAction = PendingIntent.getService(
context,
STOP_REQUEST_CODE,
@@ -52,7 +52,7 @@ object Notify {
context.getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
}
- private const val CHANNEL_ID = "measurement"
+ private const val CHANNEL_ID = "recording"
private const val OPEN_APP_REQUEST_CODE = 10
private const val STOP_REQUEST_CODE = 20
}
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/MeasurementHostResources.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/RecordingHostResources.kt
similarity index 85%
rename from sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/MeasurementHostResources.kt
rename to sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/RecordingHostResources.kt
index dfe96d2..2c8a024 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/MeasurementHostResources.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/RecordingHostResources.kt
@@ -14,13 +14,13 @@ import com.tomasrepcik.sensorbox.core.error.AppResult
import com.tomasrepcik.sensorbox.core.error.appResult
import com.tomasrepcik.sensorbox.core.error.combineAppResults
import com.tomasrepcik.sensorbox.recording.RecordingStopReason
-import com.tomasrepcik.sensorbox.sensorservices.intent.MeasurementLaunchRequest
+import com.tomasrepcik.sensorbox.sensorservices.intent.RecordingRequest
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
-internal class MeasurementHostResources(
+internal class RecordingHostResources(
private val service: Service,
private val scope: CoroutineScope,
private val requestStop: (RecordingStopReason) -> Unit,
@@ -36,7 +36,7 @@ internal class MeasurementHostResources(
}
}
- fun start(request: MeasurementLaunchRequest) {
+ fun start(request: RecordingRequest) {
promoteToForeground(request)
if (request.requiresWakeLock) acquireWakeLock()
if (request.stopOnLowBattery) registerLowBatteryReceiver()
@@ -55,14 +55,14 @@ internal class MeasurementHostResources(
alarmJobs.forEach(Job::cancel)
alarmJobs = emptyList()
return listOf(releaseWakeLock(), unregisterLowBatteryReceiver())
- .combineAppResults(AppErrorCode.MEASUREMENT, "Release recording host resources")
+ .combineAppResults(AppErrorCode.RECORDING, "Release recording host resources")
}
fun removeNotification() {
service.stopForeground(Service.STOP_FOREGROUND_REMOVE)
}
- private fun promoteToForeground(request: MeasurementLaunchRequest) {
+ private fun promoteToForeground(request: RecordingRequest) {
val notification = Notify.createRecordingNotification(service)
if (Build.VERSION.SDK_INT >= 34) {
service.startForeground(NOTIFICATION_ID, notification, foregroundTypes(request))
@@ -71,7 +71,7 @@ internal class MeasurementHostResources(
}
}
- private fun foregroundTypes(request: MeasurementLaunchRequest): Int = if (request.includesGps) {
+ private fun foregroundTypes(request: RecordingRequest): Int = if (request.includesGps) {
FOREGROUND_SERVICE_TYPE_HEALTH or FOREGROUND_SERVICE_TYPE_LOCATION
} else {
FOREGROUND_SERVICE_TYPE_HEALTH
@@ -93,15 +93,15 @@ internal class MeasurementHostResources(
}
private fun releaseWakeLock(): AppResult = appResult(
- AppErrorCode.MEASUREMENT,
- "Release measurement wake lock",
+ AppErrorCode.RECORDING,
+ "Release recording wake lock",
) {
wakeLock?.takeIf(PowerManager.WakeLock::isHeld)?.release()
wakeLock = null
}
private fun unregisterLowBatteryReceiver(): AppResult = appResult(
- AppErrorCode.MEASUREMENT,
+ AppErrorCode.RECORDING,
"Unregister low battery receiver",
) {
if (batteryReceiverRegistered) service.unregisterReceiver(lowBatteryReceiver)
@@ -110,6 +110,6 @@ internal class MeasurementHostResources(
private companion object {
const val NOTIFICATION_ID = 729
- const val WAKE_LOCK_TAG = "SensorBox::Measurement"
+ const val WAKE_LOCK_TAG = "SensorBox::Recording"
}
}
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/MeasurementService.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/RecordingService.kt
similarity index 75%
rename from sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/MeasurementService.kt
rename to sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/RecordingService.kt
index 10354ea..908823e 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/MeasurementService.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/services/RecordingService.kt
@@ -11,12 +11,12 @@ import com.tomasrepcik.sensorbox.core.error.combineAppResults
import com.tomasrepcik.sensorbox.core.time.EpochClock
import com.tomasrepcik.sensorbox.recording.RecordingEvent
import com.tomasrepcik.sensorbox.recording.RecordingStopReason
-import com.tomasrepcik.sensorbox.sensorservices.intent.MeasurementLaunchRequest
+import com.tomasrepcik.sensorbox.sensorservices.intent.RecordingRequest
import com.tomasrepcik.sensorbox.sensorservices.serviceController.ServiceController
import com.tomasrepcik.sensorbox.sensorservices.serviceController.ServiceControllerFactory
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementSessionState
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementSessionStore
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementStopReason
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionState
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionStopReason
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionStore
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -28,9 +28,9 @@ import kotlinx.coroutines.runBlocking
import javax.inject.Inject
@AndroidEntryPoint
-class MeasurementService : Service() {
+class RecordingService : Service() {
@Inject
- lateinit var sessionStore: MeasurementSessionStore
+ lateinit var sessionStore: RecordingSessionStore
@Inject
internal lateinit var controllerFactory: ServiceControllerFactory
@@ -40,11 +40,11 @@ class MeasurementService : Service() {
private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
private var controller: ServiceController? = null
- private var activeRequest: MeasurementLaunchRequest? = null
+ private var activeRequest: RecordingRequest? = null
private var eventJob: Job? = null
private var isFinishing = false
private val hostResources by lazy {
- MeasurementHostResources(
+ RecordingHostResources(
service = this,
scope = serviceScope,
requestStop = ::requestStop,
@@ -56,7 +56,7 @@ class MeasurementService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
- ACTION_STOP -> requestStop(RecordingStopReason.USER_REQUEST)
+ ACTION_STOP_RECORDING -> requestStop(RecordingStopReason.USER_REQUEST)
ACTION_ANNOTATE -> controller?.annotate(
intent.getLongExtra(ANNOTATION_TIME, epochClock.nowMillis()),
@@ -69,8 +69,8 @@ class MeasurementService : Service() {
}
private fun startRecordingHost(intent: Intent) {
- appResult(AppErrorCode.MEASUREMENT, "Start recording foreground host") {
- val request = MeasurementLaunchRequest.from(intent)
+ appResult(AppErrorCode.RECORDING, "Start recording foreground host") {
+ val request = RecordingRequest.from(intent)
require(request.sessionId.isNotBlank()) { "Recording session ID is missing" }
activeRequest = request
isFinishing = false
@@ -82,7 +82,7 @@ class MeasurementService : Service() {
val result = serviceController.start()
if (result.isFailure) {
finishRecording(
- MeasurementStopReason.SOURCE_FAILURE,
+ RecordingSessionStopReason.SOURCE_FAILURE,
result,
)
}
@@ -90,7 +90,7 @@ class MeasurementService : Service() {
}.onFailure { error ->
serviceScope.launch {
finishRecording(
- MeasurementStopReason.SOURCE_FAILURE,
+ RecordingSessionStopReason.SOURCE_FAILURE,
AppResult.failure(error),
)
}
@@ -105,7 +105,7 @@ class MeasurementService : Service() {
is RecordingEvent.RecordingStarted -> onRecordingStarted(event)
is RecordingEvent.RecordingStopped -> finishRecording(
- event.reason.toMeasurementReason(),
+ event.reason.toRecordingSessionReason(),
event.result,
)
}
@@ -116,7 +116,7 @@ class MeasurementService : Service() {
private fun onRecordingStarted(event: RecordingEvent.RecordingStarted) {
val request = activeRequest ?: return
sessionStore.markRunning(
- MeasurementSessionState.Running(
+ RecordingSessionState.Running(
sessionId = event.sessionId.value,
folderName = request.folderName,
startedAtElapsedRealtime = SystemClock.elapsedRealtime(),
@@ -135,13 +135,13 @@ class MeasurementService : Service() {
}
}
- private suspend fun finishRecording(reason: MeasurementStopReason, engineResult: AppResult) {
+ private suspend fun finishRecording(reason: RecordingSessionStopReason, engineResult: AppResult) {
if (isFinishing) return
isFinishing = true
val sessionId = activeRequest?.sessionId.orEmpty()
val hostResult = finishHost()
val result = listOf(engineResult, hostResult)
- .combineAppResults(AppErrorCode.MEASUREMENT, "Finish recording foreground host")
+ .combineAppResults(AppErrorCode.RECORDING, "Finish recording foreground host")
sessionStore.publishStopped(sessionId, reason, result)
eventJob?.cancel()
eventJob = null
@@ -152,16 +152,16 @@ class MeasurementService : Service() {
results += hostResources.release()
activeRequest = null
controller = null
- results += appResult(AppErrorCode.MEASUREMENT, "Publish idle measurement state") {
+ results += appResult(AppErrorCode.RECORDING, "Publish idle recording state") {
sessionStore.markIdle()
}
- results += appResult(AppErrorCode.MEASUREMENT, "Remove measurement notification") {
+ results += appResult(AppErrorCode.RECORDING, "Remove recording notification") {
hostResources.removeNotification()
}
- results += appResult(AppErrorCode.MEASUREMENT, "Stop measurement service instance") {
+ results += appResult(AppErrorCode.RECORDING, "Stop recording service instance") {
stopSelf()
}
- return results.combineAppResults(AppErrorCode.MEASUREMENT, "Finish recording host resources")
+ return results.combineAppResults(AppErrorCode.RECORDING, "Finish recording host resources")
}
override fun onDestroy() {
@@ -175,9 +175,9 @@ class MeasurementService : Service() {
sessionStore.markIdle()
sessionStore.publishStopped(
request.sessionId,
- MeasurementStopReason.SERVICE_DESTROYED,
+ RecordingSessionStopReason.SERVICE_DESTROYED,
listOf(cleanup, resources).combineAppResults(
- AppErrorCode.MEASUREMENT,
+ AppErrorCode.RECORDING,
"Destroy recording foreground host",
),
)
@@ -187,12 +187,12 @@ class MeasurementService : Service() {
super.onDestroy()
}
- private fun RecordingStopReason.toMeasurementReason(): MeasurementStopReason = when (this) {
- RecordingStopReason.USER_REQUEST -> MeasurementStopReason.USER_REQUEST
- RecordingStopReason.DURATION_EXPIRED -> MeasurementStopReason.DURATION_EXPIRED
- RecordingStopReason.LOW_BATTERY -> MeasurementStopReason.LOW_BATTERY
- RecordingStopReason.SOURCE_FAILURE -> MeasurementStopReason.SOURCE_FAILURE
- RecordingStopReason.PLATFORM_DESTROYED -> MeasurementStopReason.SERVICE_DESTROYED
+ private fun RecordingStopReason.toRecordingSessionReason(): RecordingSessionStopReason = when (this) {
+ RecordingStopReason.USER_REQUEST -> RecordingSessionStopReason.USER_REQUEST
+ RecordingStopReason.DURATION_EXPIRED -> RecordingSessionStopReason.DURATION_EXPIRED
+ RecordingStopReason.LOW_BATTERY -> RecordingSessionStopReason.LOW_BATTERY
+ RecordingStopReason.SOURCE_FAILURE -> RecordingSessionStopReason.SOURCE_FAILURE
+ RecordingStopReason.PLATFORM_DESTROYED -> RecordingSessionStopReason.SERVICE_DESTROYED
}
companion object {
@@ -215,6 +215,6 @@ class MeasurementService : Service() {
const val ACTION_ANNOTATE = "com.tomasrepcik.sensorbox.action.ANNOTATE_MEASUREMENT"
const val ANNOTATION_TIME = "ANNOTATION_TIME"
const val ANNOTATION_TEXT = "ANNOTATION_TEXT"
- const val ACTION_STOP = "com.tomasrepcik.sensorbox.action.STOP_MEASUREMENT"
+ const val ACTION_STOP_RECORDING = "com.tomasrepcik.sensorbox.action.STOP_RECORDING"
}
}
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/session/MeasurementSessionStore.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/session/MeasurementSessionStore.kt
deleted file mode 100644
index f93fbb5..0000000
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/session/MeasurementSessionStore.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.tomasrepcik.sensorbox.sensorservices.session
-
-import com.tomasrepcik.sensorbox.core.error.AppResult
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import javax.inject.Inject
-import javax.inject.Singleton
-
-@Singleton
-class MeasurementSessionStore @Inject constructor() {
- private val mutableState = MutableStateFlow(MeasurementSessionState.Idle)
- private val mutableEvents = MutableSharedFlow(extraBufferCapacity = EVENT_BUFFER_SIZE)
- val state: StateFlow = mutableState.asStateFlow()
- val events: SharedFlow = mutableEvents
-
- fun markRunning(state: MeasurementSessionState.Running) {
- mutableState.value = state
- }
-
- fun markStopping() {
- mutableState.value = MeasurementSessionState.Stopping
- }
-
- fun markIdle() {
- mutableState.value = MeasurementSessionState.Idle
- }
-
- fun publishStopped(sessionId: String, reason: MeasurementStopReason, result: AppResult) {
- mutableEvents.tryEmit(MeasurementStopped(sessionId, reason, result))
- }
-
- private companion object {
- const val EVENT_BUFFER_SIZE = 16
- }
-}
-
-enum class MeasurementStopReason {
- USER_REQUEST,
- DURATION_EXPIRED,
- LOW_BATTERY,
- SOURCE_FAILURE,
- SERVICE_DESTROYED,
-}
-
-data class MeasurementStopped(val sessionId: String, val reason: MeasurementStopReason, val result: AppResult)
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/session/MeasurementSessionState.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/session/RecordingSessionState.kt
similarity index 60%
rename from sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/session/MeasurementSessionState.kt
rename to sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/session/RecordingSessionState.kt
index 3c0b7c3..fa3b3c7 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/session/MeasurementSessionState.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/session/RecordingSessionState.kt
@@ -1,7 +1,7 @@
package com.tomasrepcik.sensorbox.sensorservices.session
-sealed interface MeasurementSessionState {
- data object Idle : MeasurementSessionState
+sealed interface RecordingSessionState {
+ data object Idle : RecordingSessionState
data class Running(
val sessionId: String,
@@ -9,7 +9,7 @@ sealed interface MeasurementSessionState {
val startedAtElapsedRealtime: Long,
val sensorIds: List,
val includesGps: Boolean,
- ) : MeasurementSessionState
+ ) : RecordingSessionState
- data object Stopping : MeasurementSessionState
+ data object Stopping : RecordingSessionState
}
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/session/RecordingSessionStore.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/session/RecordingSessionStore.kt
new file mode 100644
index 0000000..752b71f
--- /dev/null
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/session/RecordingSessionStore.kt
@@ -0,0 +1,52 @@
+package com.tomasrepcik.sensorbox.sensorservices.session
+
+import com.tomasrepcik.sensorbox.core.error.AppResult
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class RecordingSessionStore @Inject constructor() {
+ private val mutableState = MutableStateFlow(RecordingSessionState.Idle)
+ private val mutableEvents = MutableSharedFlow(extraBufferCapacity = EVENT_BUFFER_SIZE)
+ val state: StateFlow = mutableState.asStateFlow()
+ val events: SharedFlow = mutableEvents
+
+ fun markRunning(state: RecordingSessionState.Running) {
+ mutableState.value = state
+ }
+
+ fun markStopping() {
+ mutableState.value = RecordingSessionState.Stopping
+ }
+
+ fun markIdle() {
+ mutableState.value = RecordingSessionState.Idle
+ }
+
+ fun publishStopped(sessionId: String, reason: RecordingSessionStopReason, result: AppResult) {
+ mutableEvents.tryEmit(RecordingSessionStopped(sessionId, reason, result))
+ }
+
+ private companion object {
+ const val EVENT_BUFFER_SIZE = 16
+ }
+}
+
+enum class RecordingSessionStopReason {
+ USER_REQUEST,
+ DURATION_EXPIRED,
+ LOW_BATTERY,
+ SOURCE_FAILURE,
+ SERVICE_DESTROYED,
+}
+
+data class RecordingSessionStopped(
+ val sessionId: String,
+ val reason: RecordingSessionStopReason,
+ val result: AppResult,
+)
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/types/SensorHolder.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/types/SensorHolder.kt
index 8e2a419..05c9322 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/types/SensorHolder.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/types/SensorHolder.kt
@@ -55,7 +55,7 @@ internal class SensorHolder(
} else if (result.exceptionOrNull() == null) {
droppedSamples.incrementAndGet()
val cause = IllegalStateException("Sensor sample buffer is full")
- val error = AppError.from(AppErrorCode.MEASUREMENT, "Buffer ${spec.fileName}", cause)
+ val error = AppError.from(AppErrorCode.RECORDING, "Buffer ${spec.fileName}", cause)
reportFailure(error)
samples.close(cause)
} else if (failure.get() != null) {
diff --git a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/types/SensorSpec.kt b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/types/SensorSpec.kt
index 1488b85..f1150d5 100644
--- a/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/types/SensorSpec.kt
+++ b/sensorservices/src/main/java/com/tomasrepcik/sensorbox/sensorservices/types/SensorSpec.kt
@@ -32,7 +32,7 @@ enum class SensorSpec(val type: Int, val axisCount: Int, val fileName: String, v
STEP_COUNTER(Sensor.TYPE_STEP_COUNTER, 1, "step_counter.csv", "t_sensor;steps;accuracy\n"),
STEP_DETECTOR(Sensor.TYPE_STEP_DETECTOR, 1, "step_detector.csv", "t_sensor;step;accuracy\n"),
- // Kept discoverable in the sensor catalogue, but recorded by SignificantMotion's trigger listener.
+ // Kept discoverable in the sensor catalogue, but recorded by SignificantMotionRecording's trigger listener.
SIGNIFICANT_MOTION(
Sensor.TYPE_SIGNIFICANT_MOTION,
1,
diff --git a/sensorservices/src/main/res/values/strings.xml b/sensorservices/src/main/res/values/strings.xml
index 2acf622..c156a13 100644
--- a/sensorservices/src/main/res/values/strings.xml
+++ b/sensorservices/src/main/res/values/strings.xml
@@ -4,6 +4,6 @@
No storage path
Stop
Recording sensors
- SensorBox is writing measurement data
+ SensorBox is recording sensor data
Sensor recording
diff --git a/sensorservices/src/test/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ActivityRecognitionMeasurementTest.kt b/sensorservices/src/test/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ActivityRecognitionRecordingTest.kt
similarity index 90%
rename from sensorservices/src/test/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ActivityRecognitionMeasurementTest.kt
rename to sensorservices/src/test/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ActivityRecognitionRecordingTest.kt
index 3520139..6e7424a 100644
--- a/sensorservices/src/test/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ActivityRecognitionMeasurementTest.kt
+++ b/sensorservices/src/test/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/ActivityRecognitionRecordingTest.kt
@@ -11,16 +11,16 @@ import org.junit.Test
import java.io.ByteArrayOutputStream
import java.io.OutputStream
-class ActivityRecognitionMeasurementTest {
+class ActivityRecognitionRecordingTest {
@Test
- fun `Given fake storage and platform When activity measurement runs Then lifecycle has no Android types`() =
+ fun `Given fake storage and platform When activity recording runs Then lifecycle has no Android types`() =
runBlocking {
val storage = FakeMeasurementStorage()
val platform = FakeActivityRecognitionPlatform()
- val measurement = ActivityRecognitionMeasurement(storage, platform)
+ val recording = ActivityRecognitionRecording(storage, platform)
assertTrue(
- measurement.start(
+ recording.start(
folderName = "session",
useInternalStorage = true,
periodSeconds = 15,
@@ -28,7 +28,7 @@ class ActivityRecognitionMeasurementTest {
)
platform.emitUpdate(ActivityUpdate(123L, listOf(1, 2, 3, 4, 5, 6, 7, 8)))
platform.emitTransitions(listOf(ActivityTransitionSample(456L, 2, 1)))
- val stopped = measurement.stop()
+ val stopped = recording.stop()
assertTrue(stopped.isSuccess)
assertEquals(15, platform.startedPeriodSeconds)
@@ -41,9 +41,9 @@ class ActivityRecognitionMeasurementTest {
fun `Given transition storage failure When started Then platform initialization does not run`() = runBlocking {
val storage = FakeMeasurementStorage(failOnCall = 2)
val platform = FakeActivityRecognitionPlatform()
- val measurement = ActivityRecognitionMeasurement(storage, platform)
+ val recording = ActivityRecognitionRecording(storage, platform)
- val result = measurement.start(
+ val result = recording.start(
folderName = "session",
useInternalStorage = false,
periodSeconds = 15,
diff --git a/sensorservices/src/test/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/MeasurementPreparationTest.kt b/sensorservices/src/test/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/RecordingSourceStartTest.kt
similarity index 88%
rename from sensorservices/src/test/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/MeasurementPreparationTest.kt
rename to sensorservices/src/test/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/RecordingSourceStartTest.kt
index 1f5eadd..4cc244d 100644
--- a/sensorservices/src/test/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/MeasurementPreparationTest.kt
+++ b/sensorservices/src/test/java/com/tomasrepcik/sensorbox/sensorservices/handlers/measurements/RecordingSourceStartTest.kt
@@ -13,13 +13,13 @@ import org.junit.Assert.assertTrue
import org.junit.Test
import java.io.OutputStream
-class MeasurementPreparationTest {
+class RecordingSourceStartTest {
@Test
fun `Given first sensor file failure When sensors start Then later sensor files are not opened`() {
val storage = FailingMeasurementStorage()
- val measurement = SensorMeasurement(storage, DiagnosticLogger { })
+ val recording = SensorRecording(storage, DiagnosticLogger { })
- val result = measurement.start(
+ val result = recording.start(
folderName = "session",
useInternalStorage = true,
sensorTypes = setOf(Sensor.TYPE_ACCELEROMETER, Sensor.TYPE_GYROSCOPE),
@@ -33,9 +33,9 @@ class MeasurementPreparationTest {
@Test
fun `Given GPS file failure When GPS starts Then no output work follows`() {
val storage = FailingMeasurementStorage()
- val measurement = GPSMeasurement(GPSHandler(), storage, EpochClock { 1L })
+ val recording = GpsRecording(GPSHandler(), storage, EpochClock { 1L })
- val result = measurement.start(
+ val result = recording.start(
folderName = "session",
useInternalStorage = true,
intervalSeconds = 10,
diff --git a/wear/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/WearRecordingEmulatorFixture.kt b/wear/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/WearRecordingEmulatorFixture.kt
index 26749f0..0925a67 100644
--- a/wear/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/WearRecordingEmulatorFixture.kt
+++ b/wear/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/WearRecordingEmulatorFixture.kt
@@ -9,8 +9,8 @@ import androidx.test.platform.app.InstrumentationRegistry
import com.tomasrepcik.sensorbox.core.preferences.AppPreferences
import com.tomasrepcik.sensorbox.core.preferences.RecordingPreferences
import com.tomasrepcik.sensorbox.core.time.SystemEpochClock
-import com.tomasrepcik.sensorbox.domain.measurement.WearMeasurementControlUseCase
-import com.tomasrepcik.sensorbox.sensorservices.intent.MeasurementIntentFactory
+import com.tomasrepcik.sensorbox.domain.recording.DefaultWatchRecordingControlUseCase
+import com.tomasrepcik.sensorbox.sensorservices.intent.RecordingIntentFactory
import org.json.JSONObject
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
@@ -46,15 +46,15 @@ internal data class WearTestLocation(
internal class WearRecordingEmulatorFixture(private val context: Context) {
private val instrumentation = InstrumentationRegistry.getInstrumentation()
private val sensorManager = context.getSystemService(SensorManager::class.java)
- private val controller = WearMeasurementControlUseCase(
+ private val controller = DefaultWatchRecordingControlUseCase(
context = context,
- intentFactory = MeasurementIntentFactory(context, SystemEpochClock),
+ intentFactory = RecordingIntentFactory(context, SystemEpochClock),
)
private var mockGpsActive = false
fun prepareDevice() {
assumeTrue(
- "Wear recording scenarios require Wear OS",
+ "watch recording scenarios require Wear OS",
context.packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH),
)
grantRuntimePermissions()
@@ -86,7 +86,7 @@ internal class WearRecordingEmulatorFixture(private val context: Context) {
gpsIntervalSeconds = scenario.gpsIntervalSeconds,
gpsMinDistanceMeters = scenario.gpsMinDistanceMeters,
sensorSamplingPeriod = scenario.samplingIndex,
- restrictMeasurementOnLowBattery = scenario.stopOnLowBattery,
+ stopRecordingOnLowBattery = scenario.stopOnLowBattery,
useWakeLock = scenario.useWakeLock,
),
)
@@ -184,7 +184,7 @@ internal class WearRecordingEmulatorFixture(private val context: Context) {
output.takeIf { it.isFile && it.length() > 0L }?.let { return JSONObject(it.readText()) }
Thread.sleep(FILE_WAIT_INTERVAL_MILLIS)
}
- assertTrue("Wear recording did not finalize $output", output.isFile)
+ assertTrue("watch recording did not finalize $output", output.isFile)
return JSONObject(output.readText())
}
diff --git a/wear/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/WearToPhoneSyncSenderEmulatorTest.kt b/wear/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/WearToPhoneSyncSenderEmulatorTest.kt
index 8a017b8..4de612d 100644
--- a/wear/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/WearToPhoneSyncSenderEmulatorTest.kt
+++ b/wear/src/androidTest/java/com/tomasrepcik/sensorbox/emulator/WearToPhoneSyncSenderEmulatorTest.kt
@@ -9,7 +9,7 @@ import com.google.android.gms.wearable.CapabilityClient
import com.google.android.gms.wearable.Wearable
import com.tomasrepcik.sensorbox.core.error.AppError
import com.tomasrepcik.sensorbox.core.error.AppResult
-import com.tomasrepcik.sensorbox.domain.sync.SyncWearMeasurementsUseCase
+import com.tomasrepcik.sensorbox.domain.sync.SyncWatchMeasurementsUseCase
import com.tomasrepcik.sensorbox.wearoslib.connectivity.GooglePlayWearConnectionRepository
import com.tomasrepcik.sensorbox.wearoslib.files.GooglePlayWearFileTransferClient
import com.tomasrepcik.sensorbox.wearoslib.files.WearSyncEmulatorFixture
@@ -44,7 +44,7 @@ class WearToPhoneSyncSenderEmulatorTest {
@Test
fun givenPairedEmulatorsWhenWearSyncsThenEverySupportedFileIsTransferred() = runBlocking {
- val sync = SyncWearMeasurementsUseCase(
+ val sync = SyncWatchMeasurementsUseCase(
context = context,
connectionRepository = GooglePlayWearConnectionRepository(context),
transferClient = GooglePlayWearFileTransferClient(context),
@@ -53,7 +53,7 @@ class WearToPhoneSyncSenderEmulatorTest {
assertEquals(scenario.transferredFiles.size, syncWhenPhoneBecomesReachable(sync))
}
- private suspend fun syncWhenPhoneBecomesReachable(sync: SyncWearMeasurementsUseCase): Int {
+ private suspend fun syncWhenPhoneBecomesReachable(sync: SyncWatchMeasurementsUseCase): Int {
var lastFailure: AppError? = null
repeat(MAX_ATTEMPTS) {
when (val result = sync()) {
diff --git a/wear/src/main/java/com/tomasrepcik/sensorbox/communication/PhoneRecordingResultInbox.kt b/wear/src/main/java/com/tomasrepcik/sensorbox/communication/PhoneRecordingResultInbox.kt
index b642275..152ffe8 100644
--- a/wear/src/main/java/com/tomasrepcik/sensorbox/communication/PhoneRecordingResultInbox.kt
+++ b/wear/src/main/java/com/tomasrepcik/sensorbox/communication/PhoneRecordingResultInbox.kt
@@ -1,7 +1,7 @@
package com.tomasrepcik.sensorbox.communication
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearCommand
-import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingAction
+import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingOperation
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.first
import java.util.concurrent.ConcurrentHashMap
@@ -18,19 +18,19 @@ class PhoneRecordingResultInbox @Inject constructor() {
updates.tryEmit(result)
}
- fun clear(sessionId: String, action: WearRecordingAction) {
- results.remove(Key(sessionId, action))
+ fun clear(sessionId: String, operation: WearRecordingOperation) {
+ results.remove(Key(sessionId, operation))
}
- suspend fun await(sessionId: String, action: WearRecordingAction): WearCommand.RecordingResult {
- val key = Key(sessionId, action)
+ suspend fun await(sessionId: String, operation: WearRecordingOperation): WearCommand.RecordingResult {
+ val key = Key(sessionId, operation)
results[key]?.let { return it }
return updates.first { result -> result.key() == key }
}
- private fun WearCommand.RecordingResult.key() = Key(sessionId, action)
+ private fun WearCommand.RecordingResult.key() = Key(sessionId, operation)
- private data class Key(val sessionId: String, val action: WearRecordingAction)
+ private data class Key(val sessionId: String, val operation: WearRecordingOperation)
private companion object {
const val BUFFER_SIZE = 16
diff --git a/wear/src/main/java/com/tomasrepcik/sensorbox/communication/WearCommandEnvironment.kt b/wear/src/main/java/com/tomasrepcik/sensorbox/communication/WearCommandEnvironment.kt
index 8fcd36f..3bcc0ac 100644
--- a/wear/src/main/java/com/tomasrepcik/sensorbox/communication/WearCommandEnvironment.kt
+++ b/wear/src/main/java/com/tomasrepcik/sensorbox/communication/WearCommandEnvironment.kt
@@ -5,9 +5,9 @@ import com.tomasrepcik.sensorbox.core.error.AppErrorCode
import com.tomasrepcik.sensorbox.core.error.AppResult
import com.tomasrepcik.sensorbox.core.preferences.AppPreferences
import com.tomasrepcik.sensorbox.core.preferences.AppPreferencesRepository
-import com.tomasrepcik.sensorbox.domain.measurement.WearMeasurementPermissionUseCase
-import com.tomasrepcik.sensorbox.domain.sensors.GetWearSensorsUseCase
-import com.tomasrepcik.sensorbox.domain.sensors.toWearSensorInfo
+import com.tomasrepcik.sensorbox.domain.recording.WatchRecordingPermissionUseCase
+import com.tomasrepcik.sensorbox.domain.sensors.GetWatchSensorsUseCase
+import com.tomasrepcik.sensorbox.domain.sensors.toWatchSensorInfo
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingRequest
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearSensorInfo
import kotlinx.coroutines.flow.first
@@ -20,22 +20,22 @@ interface WearRecordingRequirementsUseCase {
}
class WearRecordingEnvironment @Inject constructor(
- private val measurementPermissions: WearMeasurementPermissionUseCase,
+ private val recordingPermissions: WatchRecordingPermissionUseCase,
private val preferencesRepository: AppPreferencesRepository,
- private val getWearSensors: GetWearSensorsUseCase,
+ private val getWatchSensors: GetWatchSensorsUseCase,
) : WearRecordingRequirementsUseCase {
override suspend fun validate(request: WearRecordingRequest): AppResult {
- val availableSensorIds = getWearSensors().map { it.type }.toSet()
+ val availableSensorIds = getWatchSensors().map { it.type }.toSet()
if (!availableSensorIds.containsAll(request.sensorIds)) {
- return AppResult.failure(AppError(AppErrorCode.VALIDATION, "Validate Wear recording sensors"))
+ return AppResult.failure(AppError(AppErrorCode.VALIDATION, "Validate watch recording sensors"))
}
- val missingPermissions = measurementPermissions(request.includesGps)
+ val missingPermissions = recordingPermissions(request.includesGps)
if (missingPermissions.isNotEmpty()) {
return AppResult.failure(
AppError(
code = AppErrorCode.PERMISSION,
- operation = "Validate Wear recording permissions",
+ operation = "Validate watch recording permissions",
diagnosticMessage = "Wear OS is missing required recording permissions",
context = mapOf("missingPermissions" to missingPermissions.sorted().joinToString()),
),
@@ -45,5 +45,7 @@ class WearRecordingEnvironment @Inject constructor(
return preferencesRepository.preferences.first()
}
- override fun availableSensors(): List = getWearSensors().map { sensor -> sensor.toWearSensorInfo() }
+ override fun availableSensors(): List = getWatchSensors().map { sensor ->
+ sensor.toWatchSensorInfo()
+ }
}
diff --git a/wear/src/main/java/com/tomasrepcik/sensorbox/communication/WearCommandHandler.kt b/wear/src/main/java/com/tomasrepcik/sensorbox/communication/WearCommandHandler.kt
index ab1aef2..1b1bdb4 100644
--- a/wear/src/main/java/com/tomasrepcik/sensorbox/communication/WearCommandHandler.kt
+++ b/wear/src/main/java/com/tomasrepcik/sensorbox/communication/WearCommandHandler.kt
@@ -3,19 +3,19 @@ package com.tomasrepcik.sensorbox.communication
import com.tomasrepcik.sensorbox.core.error.AppError
import com.tomasrepcik.sensorbox.core.error.AppErrorCode
import com.tomasrepcik.sensorbox.core.error.AppResult
-import com.tomasrepcik.sensorbox.domain.measurement.WearRecordingControlUseCase
+import com.tomasrepcik.sensorbox.domain.recording.WatchRecordingControlUseCase
import com.tomasrepcik.sensorbox.wearoslib.WearOsConstants.PHONE_APP_CAPABILITY
import com.tomasrepcik.sensorbox.wearoslib.WearOsConstants.PHONE_MESSAGE_PATH
import com.tomasrepcik.sensorbox.wearoslib.protocol.SendWearCommandUseCase
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearCommand
-import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingAction
+import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingOperation
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingOutcome
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearStopReason
import kotlinx.coroutines.withTimeoutOrNull
import javax.inject.Inject
class WearCommandHandler @Inject constructor(
- private val recording: WearRecordingControlUseCase,
+ private val recording: WatchRecordingControlUseCase,
private val environment: WearRecordingRequirementsUseCase,
private val sendCommand: SendWearCommandUseCase,
private val phoneResults: PhoneRecordingResultInbox,
@@ -45,7 +45,7 @@ class WearCommandHandler @Inject constructor(
val sessionId = activeSessionId ?: return AppResult.success(Unit)
activeSessionId = null
val command = WearCommand.StopRecording(sessionId, reason)
- phoneResults.clear(sessionId, WearRecordingAction.STOP)
+ phoneResults.clear(sessionId, WearRecordingOperation.STOP)
var lastSendError: AppError? = null
repeat(ATTEMPT_COUNT) { retryCount ->
@@ -55,7 +55,7 @@ class WearCommandHandler @Inject constructor(
is AppResult.Success -> {
lastSendError = null
val result = withTimeoutOrNull(RESULT_TIMEOUT_MILLIS / ATTEMPT_COUNT) {
- phoneResults.await(sessionId, WearRecordingAction.STOP)
+ phoneResults.await(sessionId, WearRecordingOperation.STOP)
}
if (result != null) return result.toAppResult(retryCount)
}
@@ -77,7 +77,7 @@ class WearCommandHandler @Inject constructor(
}
private suspend fun start(command: WearCommand.StartRecording): WearCommand.RecordingResult {
- cached(command.sessionId, WearRecordingAction.START)?.let { return it }
+ cached(command.sessionId, WearRecordingOperation.START)?.let { return it }
val result = when (val active = activeSessionId) {
command.sessionId -> AppResult.success(Unit)
@@ -97,18 +97,18 @@ class WearCommandHandler @Inject constructor(
else -> AppResult.failure(
AppError(
code = AppErrorCode.CONFLICT,
- operation = "Start Wear recording",
- diagnosticMessage = "Another Wear recording is active",
+ operation = "Start watch recording",
+ diagnosticMessage = "Another watch recording is active",
context = mapOf("activeSessionId" to active),
),
)
}
- return remember(command.sessionId, WearRecordingAction.START, result)
+ return remember(command.sessionId, WearRecordingOperation.START, result)
}
private fun stop(command: WearCommand.StopRecording): WearCommand.RecordingResult {
- cached(command.sessionId, WearRecordingAction.STOP)?.let { return it }
+ cached(command.sessionId, WearRecordingOperation.STOP)?.let { return it }
val result = if (activeSessionId == command.sessionId) {
recording.stop(command.sessionId, command.reason).onSuccess {
@@ -118,7 +118,7 @@ class WearCommandHandler @Inject constructor(
AppResult.success(Unit)
}
- return remember(command.sessionId, WearRecordingAction.STOP, result)
+ return remember(command.sessionId, WearRecordingOperation.STOP, result)
}
private suspend fun sendAvailableSensors(): AppResult = sendCommand(
@@ -135,24 +135,24 @@ class WearCommandHandler @Inject constructor(
private fun remember(
sessionId: String,
- action: WearRecordingAction,
+ operation: WearRecordingOperation,
result: AppResult,
): WearCommand.RecordingResult {
val error = result.errorOrNull()
return WearCommand.RecordingResult(
sessionId = sessionId,
- action = action,
+ operation = operation,
outcome = if (result.isSuccess) WearRecordingOutcome.SUCCEEDED else WearRecordingOutcome.FAILED,
errorCode = error?.code,
errorOperation = error?.operation,
errorMessage = error?.diagnosticMessage,
errorContext = error?.context.orEmpty(),
failureCount = if (result.isFailure) 1 else 0,
- ).also { completedResults[ResultKey(sessionId, action)] = it }
+ ).also { completedResults[ResultKey(sessionId, operation)] = it }
}
- private fun cached(sessionId: String, action: WearRecordingAction): WearCommand.RecordingResult? =
- completedResults[ResultKey(sessionId, action)]
+ private fun cached(sessionId: String, operation: WearRecordingOperation): WearCommand.RecordingResult? =
+ completedResults[ResultKey(sessionId, operation)]
private fun WearCommand.RecordingResult.toAppResult(retryCount: Int): AppResult =
if (outcome == WearRecordingOutcome.SUCCEEDED) {
@@ -161,8 +161,8 @@ class WearCommandHandler @Inject constructor(
AppResult.failure(
AppError(
code = errorCode ?: AppErrorCode.UNKNOWN,
- operation = errorOperation ?: "Handle phone $action result",
- diagnosticMessage = errorMessage ?: "Phone $action failed",
+ operation = errorOperation ?: "Handle phone $operation result",
+ diagnosticMessage = errorMessage ?: "Phone $operation failed",
context = errorContext + mapOf(
"source" to "phone",
"sessionId" to sessionId,
@@ -173,7 +173,7 @@ class WearCommandHandler @Inject constructor(
)
}
- private data class ResultKey(val sessionId: String, val action: WearRecordingAction)
+ private data class ResultKey(val sessionId: String, val operation: WearRecordingOperation)
private companion object {
const val RESULT_TIMEOUT_MILLIS = 5_000L
diff --git a/wear/src/main/java/com/tomasrepcik/sensorbox/communication/WearRecordingSessionObserver.kt b/wear/src/main/java/com/tomasrepcik/sensorbox/communication/WearRecordingSessionObserver.kt
index 2a99b1b..16ace59 100644
--- a/wear/src/main/java/com/tomasrepcik/sensorbox/communication/WearRecordingSessionObserver.kt
+++ b/wear/src/main/java/com/tomasrepcik/sensorbox/communication/WearRecordingSessionObserver.kt
@@ -2,9 +2,9 @@ package com.tomasrepcik.sensorbox.communication
import com.tomasrepcik.sensorbox.core.error.DiagnosticLogger
import com.tomasrepcik.sensorbox.core.error.toDiagnosticEvent
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementSessionStore
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementStopReason
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementStopped
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionStopReason
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionStopped
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionStore
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearStopReason
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -17,7 +17,7 @@ import javax.inject.Singleton
@Singleton
class WearRecordingSessionObserver @Inject constructor(
- private val sessionStore: MeasurementSessionStore,
+ private val sessionStore: RecordingSessionStore,
private val commandHandler: WearCommandHandler,
private val diagnosticLogger: DiagnosticLogger,
) {
@@ -31,19 +31,19 @@ class WearRecordingSessionObserver @Inject constructor(
}
}
- private suspend fun onStopped(event: MeasurementStopped) {
+ private suspend fun onStopped(event: RecordingSessionStopped) {
event.result.errorOrNull()?.let { error -> diagnosticLogger.record(error.toDiagnosticEvent()) }
- val reason = event.reason.toAutomaticWearReason() ?: return
+ val reason = event.reason.toAutomaticWatchReason() ?: return
commandHandler.onAutomaticStop(reason).onFailure { error ->
diagnosticLogger.record(error.toDiagnosticEvent())
}
}
- private fun MeasurementStopReason.toAutomaticWearReason(): WearStopReason? = when (this) {
- MeasurementStopReason.USER_REQUEST -> null
- MeasurementStopReason.DURATION_EXPIRED -> WearStopReason.DURATION_EXPIRED
- MeasurementStopReason.LOW_BATTERY -> WearStopReason.LOW_BATTERY
- MeasurementStopReason.SOURCE_FAILURE -> WearStopReason.SOURCE_FAILURE
- MeasurementStopReason.SERVICE_DESTROYED -> WearStopReason.SERVICE_DESTROYED
+ private fun RecordingSessionStopReason.toAutomaticWatchReason(): WearStopReason? = when (this) {
+ RecordingSessionStopReason.USER_REQUEST -> null
+ RecordingSessionStopReason.DURATION_EXPIRED -> WearStopReason.DURATION_EXPIRED
+ RecordingSessionStopReason.LOW_BATTERY -> WearStopReason.LOW_BATTERY
+ RecordingSessionStopReason.SOURCE_FAILURE -> WearStopReason.SOURCE_FAILURE
+ RecordingSessionStopReason.SERVICE_DESTROYED -> WearStopReason.SERVICE_DESTROYED
}
}
diff --git a/wear/src/main/java/com/tomasrepcik/sensorbox/di/RecordingModule.kt b/wear/src/main/java/com/tomasrepcik/sensorbox/di/RecordingModule.kt
index 461469a..53cdc6b 100644
--- a/wear/src/main/java/com/tomasrepcik/sensorbox/di/RecordingModule.kt
+++ b/wear/src/main/java/com/tomasrepcik/sensorbox/di/RecordingModule.kt
@@ -2,8 +2,8 @@ package com.tomasrepcik.sensorbox.di
import com.tomasrepcik.sensorbox.communication.WearRecordingEnvironment
import com.tomasrepcik.sensorbox.communication.WearRecordingRequirementsUseCase
-import com.tomasrepcik.sensorbox.domain.measurement.WearMeasurementControlUseCase
-import com.tomasrepcik.sensorbox.domain.measurement.WearRecordingControlUseCase
+import com.tomasrepcik.sensorbox.domain.recording.DefaultWatchRecordingControlUseCase
+import com.tomasrepcik.sensorbox.domain.recording.WatchRecordingControlUseCase
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@@ -13,9 +13,9 @@ import dagger.hilt.components.SingletonComponent
@InstallIn(SingletonComponent::class)
abstract class RecordingModule {
@Binds
- abstract fun bindWearRecordingControlUseCase(
- implementation: WearMeasurementControlUseCase,
- ): WearRecordingControlUseCase
+ abstract fun bindWatchRecordingControlUseCase(
+ implementation: DefaultWatchRecordingControlUseCase,
+ ): WatchRecordingControlUseCase
@Binds
abstract fun bindWearRecordingRequirementsUseCase(
diff --git a/wear/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/WearMeasurementControlUseCase.kt b/wear/src/main/java/com/tomasrepcik/sensorbox/domain/recording/WatchRecordingControlUseCase.kt
similarity index 74%
rename from wear/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/WearMeasurementControlUseCase.kt
rename to wear/src/main/java/com/tomasrepcik/sensorbox/domain/recording/WatchRecordingControlUseCase.kt
index 3238f78..83ab4cf 100644
--- a/wear/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/WearMeasurementControlUseCase.kt
+++ b/wear/src/main/java/com/tomasrepcik/sensorbox/domain/recording/WatchRecordingControlUseCase.kt
@@ -1,4 +1,4 @@
-package com.tomasrepcik.sensorbox.domain.measurement
+package com.tomasrepcik.sensorbox.domain.recording
import android.content.Context
import android.content.Intent
@@ -8,24 +8,24 @@ import com.tomasrepcik.sensorbox.core.error.AppErrorCode
import com.tomasrepcik.sensorbox.core.error.AppResult
import com.tomasrepcik.sensorbox.core.error.appResult
import com.tomasrepcik.sensorbox.core.preferences.AppPreferences
-import com.tomasrepcik.sensorbox.sensorservices.intent.MeasurementIntentFactory
-import com.tomasrepcik.sensorbox.sensorservices.intent.MeasurementLaunchRequest
-import com.tomasrepcik.sensorbox.sensorservices.services.MeasurementService
+import com.tomasrepcik.sensorbox.sensorservices.intent.RecordingIntentFactory
+import com.tomasrepcik.sensorbox.sensorservices.intent.RecordingRequest
+import com.tomasrepcik.sensorbox.sensorservices.services.RecordingService
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearRecordingRequest
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearStopReason
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
-interface WearRecordingControlUseCase {
+interface WatchRecordingControlUseCase {
fun start(sessionId: String, request: WearRecordingRequest, preferences: AppPreferences): AppResult
fun stop(sessionId: String, reason: WearStopReason): AppResult
}
-class WearMeasurementControlUseCase @Inject constructor(
+class DefaultWatchRecordingControlUseCase @Inject constructor(
@ApplicationContext private val context: Context,
- private val intentFactory: MeasurementIntentFactory,
-) : WearRecordingControlUseCase {
+ private val intentFactory: RecordingIntentFactory,
+) : WatchRecordingControlUseCase {
override fun start(
sessionId: String,
request: WearRecordingRequest,
@@ -46,15 +46,15 @@ class WearMeasurementControlUseCase @Inject constructor(
sessionId: String = java.util.UUID.randomUUID().toString(),
folderName: String = intentFactory.newFolderName(),
durationMillis: Long = 0L,
- ): AppResult = appResult(AppErrorCode.MEASUREMENT, "Request Wear measurement start") {
- val request = MeasurementLaunchRequest(
+ ): AppResult = appResult(AppErrorCode.RECORDING, "Request watch recording start") {
+ val request = RecordingRequest(
sessionId = sessionId,
folderName = folderName,
useInternalStorage = true,
sensorIds = sensorIds,
sensorSamplingPeriod = samplingPeriod(preferences.recording.sensorSamplingPeriod),
includesGps = includesGps,
- stopOnLowBattery = preferences.recording.restrictMeasurementOnLowBattery,
+ stopOnLowBattery = preferences.recording.stopRecordingOnLowBattery,
useWakeLock = preferences.recording.useWakeLock,
gpsIntervalSeconds = preferences.recording.gpsIntervalSeconds,
gpsMinDistanceMeters = preferences.recording.gpsMinDistanceMeters,
@@ -63,9 +63,9 @@ class WearMeasurementControlUseCase @Inject constructor(
ContextCompat.startForegroundService(context, intentFactory.create(request))
}
- fun stop(): AppResult = appResult(AppErrorCode.MEASUREMENT, "Request Wear measurement stop") {
- val intent = Intent(context, MeasurementService::class.java)
- .setAction(MeasurementService.ACTION_STOP)
+ fun stop(): AppResult = appResult(AppErrorCode.RECORDING, "Request watch recording stop") {
+ val intent = Intent(context, RecordingService::class.java)
+ .setAction(RecordingService.ACTION_STOP_RECORDING)
context.startService(intent)
}
diff --git a/wear/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/WearMeasurementPermissionUseCase.kt b/wear/src/main/java/com/tomasrepcik/sensorbox/domain/recording/WatchRecordingPermissionUseCase.kt
similarity index 59%
rename from wear/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/WearMeasurementPermissionUseCase.kt
rename to wear/src/main/java/com/tomasrepcik/sensorbox/domain/recording/WatchRecordingPermissionUseCase.kt
index 286a2ab..fa12ec1 100644
--- a/wear/src/main/java/com/tomasrepcik/sensorbox/domain/measurement/WearMeasurementPermissionUseCase.kt
+++ b/wear/src/main/java/com/tomasrepcik/sensorbox/domain/recording/WatchRecordingPermissionUseCase.kt
@@ -1,4 +1,4 @@
-package com.tomasrepcik.sensorbox.domain.measurement
+package com.tomasrepcik.sensorbox.domain.recording
import android.Manifest
import android.content.Context
@@ -7,14 +7,14 @@ import androidx.core.content.ContextCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
-class WearMeasurementPermissionUseCase @Inject constructor(@ApplicationContext private val context: Context) {
+class WatchRecordingPermissionUseCase @Inject constructor(@ApplicationContext private val context: Context) {
operator fun invoke(includesGps: Boolean): Set =
- requiredWearMeasurementPermissions(includesGps).filterNot(::isGranted).toSet()
+ requiredWatchRecordingPermissions(includesGps).filterNot(::isGranted).toSet()
private fun isGranted(permission: String): Boolean =
ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
}
-internal fun requiredWearMeasurementPermissions(includesGps: Boolean): Set = buildSet {
+internal fun requiredWatchRecordingPermissions(includesGps: Boolean): Set = buildSet {
if (includesGps) add(Manifest.permission.ACCESS_FINE_LOCATION)
}
diff --git a/wear/src/main/java/com/tomasrepcik/sensorbox/domain/sensors/GetWearSensorsUseCase.kt b/wear/src/main/java/com/tomasrepcik/sensorbox/domain/sensors/GetWatchSensorsUseCase.kt
similarity index 83%
rename from wear/src/main/java/com/tomasrepcik/sensorbox/domain/sensors/GetWearSensorsUseCase.kt
rename to wear/src/main/java/com/tomasrepcik/sensorbox/domain/sensors/GetWatchSensorsUseCase.kt
index e31633a..fa0657b 100644
--- a/wear/src/main/java/com/tomasrepcik/sensorbox/domain/sensors/GetWearSensorsUseCase.kt
+++ b/wear/src/main/java/com/tomasrepcik/sensorbox/domain/sensors/GetWatchSensorsUseCase.kt
@@ -6,17 +6,17 @@ import android.hardware.SensorManager
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
-class GetWearSensorsUseCase @Inject constructor(@ApplicationContext context: Context) {
+class GetWatchSensorsUseCase @Inject constructor(@ApplicationContext context: Context) {
private val sensorManager = context.getSystemService(SensorManager::class.java)
- operator fun invoke(): List = sensorManager
+ operator fun invoke(): List = sensorManager
.getSensorList(Sensor.TYPE_ALL)
.filter { it.type in WEAR_SENSOR_TYPES }
.distinctBy(Sensor::getType)
.map { sensor -> sensor.toDescriptor() }
- .sortedBy(WearSensorDescriptor::name)
+ .sortedBy(WatchSensorDescriptor::name)
- private fun Sensor.toDescriptor() = WearSensorDescriptor(
+ private fun Sensor.toDescriptor() = WatchSensorDescriptor(
type = type,
name = name,
vendor = vendor,
diff --git a/wear/src/main/java/com/tomasrepcik/sensorbox/domain/sensors/WearSensorDescriptor.kt b/wear/src/main/java/com/tomasrepcik/sensorbox/domain/sensors/WatchSensorDescriptor.kt
similarity index 89%
rename from wear/src/main/java/com/tomasrepcik/sensorbox/domain/sensors/WearSensorDescriptor.kt
rename to wear/src/main/java/com/tomasrepcik/sensorbox/domain/sensors/WatchSensorDescriptor.kt
index 8122c25..1055a61 100644
--- a/wear/src/main/java/com/tomasrepcik/sensorbox/domain/sensors/WearSensorDescriptor.kt
+++ b/wear/src/main/java/com/tomasrepcik/sensorbox/domain/sensors/WatchSensorDescriptor.kt
@@ -2,7 +2,7 @@ package com.tomasrepcik.sensorbox.domain.sensors
import com.tomasrepcik.sensorbox.wearoslib.protocol.WearSensorInfo
-data class WearSensorDescriptor(
+data class WatchSensorDescriptor(
val type: Int,
val name: String,
val vendor: String,
@@ -17,7 +17,7 @@ data class WearSensorDescriptor(
val isWakeUpSensor: Boolean,
)
-fun WearSensorDescriptor.toWearSensorInfo() = WearSensorInfo(
+fun WatchSensorDescriptor.toWatchSensorInfo() = WearSensorInfo(
type = type,
name = name,
vendor = vendor,
diff --git a/wear/src/main/java/com/tomasrepcik/sensorbox/domain/sync/SyncWearMeasurementsUseCase.kt b/wear/src/main/java/com/tomasrepcik/sensorbox/domain/sync/SyncWatchMeasurementsUseCase.kt
similarity index 90%
rename from wear/src/main/java/com/tomasrepcik/sensorbox/domain/sync/SyncWearMeasurementsUseCase.kt
rename to wear/src/main/java/com/tomasrepcik/sensorbox/domain/sync/SyncWatchMeasurementsUseCase.kt
index bbc6068..7497642 100644
--- a/wear/src/main/java/com/tomasrepcik/sensorbox/domain/sync/SyncWearMeasurementsUseCase.kt
+++ b/wear/src/main/java/com/tomasrepcik/sensorbox/domain/sync/SyncWatchMeasurementsUseCase.kt
@@ -16,7 +16,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import java.io.File
import javax.inject.Inject
-class SyncWearMeasurementsUseCase @Inject constructor(
+class SyncWatchMeasurementsUseCase @Inject constructor(
@ApplicationContext private val context: Context,
private val connectionRepository: WearConnectionRepository,
private val transferClient: WearFileTransferClient,
@@ -27,7 +27,7 @@ class SyncWearMeasurementsUseCase @Inject constructor(
if (node == null) {
return@suspendFlatMap AppResult.failure(AppError(AppErrorCode.CONNECTIVITY, "Find connected phone"))
}
- appResult(AppErrorCode.STORAGE, "List Wear measurements", ::measurementFiles).suspendFlatMap { files ->
+ appResult(AppErrorCode.STORAGE, "List watch measurements", ::measurementFiles).suspendFlatMap { files ->
var transferResult: AppResult = AppResult.success(Unit)
for (file in files) {
if (transferResult.isFailure) break
@@ -35,11 +35,11 @@ class SyncWearMeasurementsUseCase @Inject constructor(
}
transferResult.map { files.size }
}
- }.withAppError(AppErrorCode.CONNECTIVITY, "Sync Wear measurements")
+ }.withAppError(AppErrorCode.CONNECTIVITY, "Sync watch measurements")
private suspend fun sendFile(nodeId: String, file: File): AppResult {
val measurementName = file.parentFile?.name
- ?: return AppResult.failure(AppError(AppErrorCode.STORAGE, "Read Wear measurement folder"))
+ ?: return AppResult.failure(AppError(AppErrorCode.STORAGE, "Read watch measurement folder"))
return transferClient.send(
nodeId = nodeId,
metadata = WearFileMetadata(measurementName, file.name),
diff --git a/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/WearComponents.kt b/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/WearComponents.kt
index dc96e23..ba1aad8 100644
--- a/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/WearComponents.kt
+++ b/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/WearComponents.kt
@@ -191,7 +191,7 @@ internal fun TransformingLazyColumnItemScope.WearSwitchRow(
}
@Composable
-internal fun TransformingLazyColumnItemScope.WearPrimaryAction(
+internal fun TransformingLazyColumnItemScope.WearPrimaryButton(
label: String,
transformation: TransformationSpec,
enabled: Boolean = true,
@@ -208,7 +208,7 @@ internal fun TransformingLazyColumnItemScope.WearPrimaryAction(
}
@Composable
-internal fun TransformingLazyColumnItemScope.WearSecondaryAction(
+internal fun TransformingLazyColumnItemScope.WearSecondaryButton(
label: String,
transformation: TransformationSpec,
onClick: () -> Unit,
@@ -230,7 +230,7 @@ internal fun TransformingLazyColumnItemScope.WearSecondaryAction(
}
@Composable
-internal fun WearPrimaryEdgeAction(label: String, enabled: Boolean = true, onClick: () -> Unit) {
+internal fun WearPrimaryEdgeButton(label: String, enabled: Boolean = true, onClick: () -> Unit) {
EdgeButton(
onClick = onClick,
enabled = enabled,
diff --git a/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/dashboard/WearDashboardContract.kt b/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/dashboard/WearDashboardContract.kt
index 156a93f..4d8cb4b 100644
--- a/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/dashboard/WearDashboardContract.kt
+++ b/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/dashboard/WearDashboardContract.kt
@@ -1,15 +1,15 @@
package com.tomasrepcik.sensorbox.presentation.dashboard
import com.tomasrepcik.sensorbox.core.preferences.AppPreferences
-import com.tomasrepcik.sensorbox.domain.sensors.WearSensorDescriptor
+import com.tomasrepcik.sensorbox.domain.sensors.WatchSensorDescriptor
import com.tomasrepcik.sensorbox.presentation.menu.WearMenuDestination
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementSessionState
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionState
enum class WearRoute { MENU, RECORD, LIVE, SETTINGS, ACTIVE }
data class WearDashboardState(
val route: WearRoute = WearRoute.MENU,
- val sensors: List = emptyList(),
+ val sensors: List = emptyList(),
val selectedSensorIds: Set = emptySet(),
val includesGps: Boolean = false,
val liveSensorType: Int? = null,
@@ -33,9 +33,9 @@ sealed interface WearDashboardIntent {
data object Back : WearDashboardIntent
data class ToggleSensor(val sensorType: Int) : WearDashboardIntent
data object ToggleGps : WearDashboardIntent
- data object StartMeasurement : WearDashboardIntent
+ data object StartRecording : WearDashboardIntent
data class PermissionsResolved(val granted: Boolean) : WearDashboardIntent
- data object StopMeasurement : WearDashboardIntent
+ data object StopRecording : WearDashboardIntent
data class ObserveSensor(val sensorType: Int) : WearDashboardIntent
data class SetSamplingPeriod(val index: Int) : WearDashboardIntent
data object ToggleBatteryRestriction : WearDashboardIntent
@@ -60,10 +60,10 @@ object WearDashboardReducer {
else -> state
}
- fun measurementSessionChanged(state: WearDashboardState, session: MeasurementSessionState) = when {
- session is MeasurementSessionState.Running -> state.copy(route = WearRoute.ACTIVE)
+ fun recordingSessionChanged(state: WearDashboardState, session: RecordingSessionState) = when {
+ session is RecordingSessionState.Running -> state.copy(route = WearRoute.ACTIVE)
- session is MeasurementSessionState.Idle && state.route == WearRoute.ACTIVE ->
+ session is RecordingSessionState.Idle && state.route == WearRoute.ACTIVE ->
state.copy(route = WearRoute.MENU)
else -> state
diff --git a/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/dashboard/WearDashboardScreen.kt b/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/dashboard/WearDashboardScreen.kt
index 568674a..66db328 100644
--- a/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/dashboard/WearDashboardScreen.kt
+++ b/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/dashboard/WearDashboardScreen.kt
@@ -31,15 +31,15 @@ import com.patrykandpatrick.vico.compose.cartesian.layer.rememberLineCartesianLa
import com.patrykandpatrick.vico.compose.cartesian.rememberCartesianChart
import com.tomasrepcik.sensorbox.R
import com.tomasrepcik.sensorbox.core.format.ValueFormats
-import com.tomasrepcik.sensorbox.domain.sensors.WearSensorDescriptor
+import com.tomasrepcik.sensorbox.domain.sensors.WatchSensorDescriptor
import com.tomasrepcik.sensorbox.presentation.WearCheckboxRow
import com.tomasrepcik.sensorbox.presentation.WearListScreen
import com.tomasrepcik.sensorbox.presentation.WearNavigationRow
import com.tomasrepcik.sensorbox.presentation.WearPageTitle
-import com.tomasrepcik.sensorbox.presentation.WearPrimaryAction
-import com.tomasrepcik.sensorbox.presentation.WearPrimaryEdgeAction
+import com.tomasrepcik.sensorbox.presentation.WearPrimaryButton
+import com.tomasrepcik.sensorbox.presentation.WearPrimaryEdgeButton
import com.tomasrepcik.sensorbox.presentation.WearRadioRow
-import com.tomasrepcik.sensorbox.presentation.WearSecondaryAction
+import com.tomasrepcik.sensorbox.presentation.WearSecondaryButton
import com.tomasrepcik.sensorbox.presentation.WearSectionTitle
import com.tomasrepcik.sensorbox.presentation.WearSwitchRow
import com.tomasrepcik.sensorbox.presentation.menu.WearMenuScreen
@@ -65,10 +65,10 @@ private fun WearRecordScreen(state: WearDashboardState, accept: (WearDashboardIn
val selectedCount = state.selectedSensorIds.size + if (state.includesGps) 1 else 0
WearListScreen(
edgeButton = {
- WearPrimaryEdgeAction(
+ WearPrimaryEdgeButton(
label = pluralStringResource(R.plurals.start_source_count, selectedCount, selectedCount),
enabled = selectedCount > 0,
- onClick = { accept(WearDashboardIntent.StartMeasurement) },
+ onClick = { accept(WearDashboardIntent.StartRecording) },
)
},
) { transformation ->
@@ -97,7 +97,7 @@ private fun WearRecordScreen(state: WearDashboardState, accept: (WearDashboardIn
state.message?.let { message ->
item { WearSectionTitle(wearMessageText(message), transformation) }
}
- item { WearBackAction(transformation, accept) }
+ item { WearBackButton(transformation, accept) }
}
}
@@ -150,7 +150,7 @@ private fun WearLiveScreen(
}
@Composable
-private fun WearSensorPicker(sensors: List, accept: (WearDashboardIntent) -> Unit) {
+private fun WearSensorPicker(sensors: List, accept: (WearDashboardIntent) -> Unit) {
WearListScreen { transformation ->
item { WearPageTitle(stringResource(R.string.live_values), transformation) }
sensors.forEach { sensor ->
@@ -163,7 +163,7 @@ private fun WearSensorPicker(sensors: List, accept: (WearD
) { accept(WearDashboardIntent.ObserveSensor(sensor.type)) }
}
}
- item { WearBackAction(transformation, accept) }
+ item { WearBackButton(transformation, accept) }
}
}
@@ -174,7 +174,7 @@ private fun WearSettingsScreen(state: WearDashboardState, accept: (WearDashboard
preferenceItems(state, transformation, accept)
item { WearSectionTitle(stringResource(R.string.transfer), transformation) }
item {
- WearPrimaryAction(
+ WearPrimaryButton(
label = stringResource(if (state.isSyncing) R.string.syncing else R.string.sync_recordings),
transformation = transformation,
enabled = !state.isSyncing,
@@ -190,7 +190,7 @@ private fun WearSettingsScreen(state: WearDashboardState, accept: (WearDashboard
state.message?.let { message ->
item { WearSectionTitle(wearMessageText(message), transformation) }
}
- item { WearBackAction(transformation, accept) }
+ item { WearBackButton(transformation, accept) }
}
}
@@ -218,7 +218,7 @@ private fun TransformingLazyColumnScope.preferenceItems(
item {
WearSwitchRow(
label = stringResource(R.string.stop_on_low_battery),
- checked = state.preferences.recording.restrictMeasurementOnLowBattery,
+ checked = state.preferences.recording.stopRecordingOnLowBattery,
transformation = transformation,
onCheckedChange = { accept(WearDashboardIntent.ToggleBatteryRestriction) },
)
@@ -274,7 +274,7 @@ private fun WearActiveScreen(state: WearDashboardState, accept: (WearDashboardIn
)
Spacer(Modifier.height(22.dp))
Button(
- onClick = { accept(WearDashboardIntent.StopMeasurement) },
+ onClick = { accept(WearDashboardIntent.StopRecording) },
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error,
@@ -289,11 +289,11 @@ private fun WearActiveScreen(state: WearDashboardState, accept: (WearDashboardIn
}
@Composable
-private fun TransformingLazyColumnItemScope.WearBackAction(
+private fun TransformingLazyColumnItemScope.WearBackButton(
transformation: TransformationSpec,
accept: (WearDashboardIntent) -> Unit,
) {
- WearSecondaryAction(stringResource(R.string.back), transformation) {
+ WearSecondaryButton(stringResource(R.string.back), transformation) {
accept(WearDashboardIntent.Back)
}
}
diff --git a/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/dashboard/WearDashboardViewModel.kt b/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/dashboard/WearDashboardViewModel.kt
index dc67329..1022a09 100644
--- a/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/dashboard/WearDashboardViewModel.kt
+++ b/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/dashboard/WearDashboardViewModel.kt
@@ -9,13 +9,13 @@ import com.tomasrepcik.sensorbox.core.error.AppErrorCode
import com.tomasrepcik.sensorbox.core.error.AppResult
import com.tomasrepcik.sensorbox.core.preferences.AppPreferencesIntent
import com.tomasrepcik.sensorbox.core.preferences.AppPreferencesRepository
-import com.tomasrepcik.sensorbox.domain.measurement.WearMeasurementControlUseCase
-import com.tomasrepcik.sensorbox.domain.measurement.WearMeasurementPermissionUseCase
-import com.tomasrepcik.sensorbox.domain.sensors.GetWearSensorsUseCase
+import com.tomasrepcik.sensorbox.domain.recording.DefaultWatchRecordingControlUseCase
+import com.tomasrepcik.sensorbox.domain.recording.WatchRecordingPermissionUseCase
+import com.tomasrepcik.sensorbox.domain.sensors.GetWatchSensorsUseCase
import com.tomasrepcik.sensorbox.domain.sensors.ObserveSensorValuesUseCase
-import com.tomasrepcik.sensorbox.domain.sync.SyncWearMeasurementsUseCase
+import com.tomasrepcik.sensorbox.domain.sync.SyncWatchMeasurementsUseCase
import com.tomasrepcik.sensorbox.presentation.menu.WearMenuDestination
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementSessionStore
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionStore
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
@@ -28,13 +28,13 @@ import javax.inject.Inject
@HiltViewModel
class WearDashboardViewModel @Inject constructor(
- getSensors: GetWearSensorsUseCase,
+ getSensors: GetWatchSensorsUseCase,
private val observeSensorValues: ObserveSensorValuesUseCase,
- private val permissionUseCase: WearMeasurementPermissionUseCase,
- private val measurementControl: WearMeasurementControlUseCase,
+ private val permissionUseCase: WatchRecordingPermissionUseCase,
+ private val recordingControl: DefaultWatchRecordingControlUseCase,
private val preferencesRepository: AppPreferencesRepository,
- private val sessionStore: MeasurementSessionStore,
- private val syncMeasurements: SyncWearMeasurementsUseCase,
+ private val sessionStore: RecordingSessionStore,
+ private val syncMeasurements: SyncWatchMeasurementsUseCase,
) : ViewModel() {
private val mutableState = MutableStateFlow(WearDashboardState(sensors = getSensors()))
private val mutableEffects = Channel(Channel.BUFFERED)
@@ -54,9 +54,9 @@ class WearDashboardViewModel @Inject constructor(
mutableState.value = WearDashboardReducer.reduce(mutableState.value, intent)
when (intent) {
is WearDashboardIntent.Open -> handleDestination(intent.destination)
- WearDashboardIntent.StartMeasurement -> requestMeasurementStart()
+ WearDashboardIntent.StartRecording -> requestRecordingStart()
is WearDashboardIntent.PermissionsResolved -> handlePermissionResult(intent.granted)
- WearDashboardIntent.StopMeasurement -> measurementControl.stop().showFailure()
+ WearDashboardIntent.StopRecording -> recordingControl.stop().showFailure()
is WearDashboardIntent.ObserveSensor -> observeLiveSensor(intent.sensorType)
is WearDashboardIntent.SetSamplingPeriod -> setSamplingPeriod(intent.index)
WearDashboardIntent.ToggleBatteryRestriction -> toggleBatteryRestriction()
@@ -80,14 +80,14 @@ class WearDashboardViewModel @Inject constructor(
mutableEffects.trySend(WearDashboardEffect.OpenUrl(destination))
}
- private fun requestMeasurementStart() {
+ private fun requestRecordingStart() {
val state = mutableState.value
if (state.selectedSensorIds.isEmpty() && !state.includesGps) {
mutableState.value = state.copy(message = WearDashboardMessage.PickSource)
return
}
val missing = permissionUseCase(state.includesGps)
- if (missing.isEmpty()) startMeasurement() else requestPermissions(missing)
+ if (missing.isEmpty()) startRecording() else requestPermissions(missing)
}
private fun requestPermissions(permissions: Set) {
@@ -96,14 +96,14 @@ class WearDashboardViewModel @Inject constructor(
}
private fun handlePermissionResult(granted: Boolean) {
- if (granted && pendingStart) startMeasurement()
+ if (granted && pendingStart) startRecording()
if (!granted) mutableState.value = mutableState.value.copy(message = WearDashboardMessage.PermissionRequired)
pendingStart = false
}
- private fun startMeasurement() {
+ private fun startRecording() {
val state = mutableState.value
- measurementControl.start(state.selectedSensorIds, state.includesGps, state.preferences).showFailure()
+ recordingControl.start(state.selectedSensorIds, state.includesGps, state.preferences).showFailure()
}
private fun observeLiveSensor(sensorType: Int) {
@@ -111,7 +111,7 @@ class WearDashboardViewModel @Inject constructor(
sensorJob = viewModelScope.launch {
observeSensorValues(sensorType)
.catch { error ->
- AppError.from(AppErrorCode.MEASUREMENT, "Observe live sensor", error)
+ AppError.from(AppErrorCode.RECORDING, "Observe live sensor", error)
showSensorError()
}
.collect(::publishSensorValue)
@@ -142,7 +142,7 @@ class WearDashboardViewModel @Inject constructor(
private fun observeSession() = viewModelScope.launch {
sessionStore.state.collect { session ->
- mutableState.value = WearDashboardReducer.measurementSessionChanged(mutableState.value, session)
+ mutableState.value = WearDashboardReducer.recordingSessionChanged(mutableState.value, session)
}
}
@@ -155,8 +155,8 @@ class WearDashboardViewModel @Inject constructor(
)
private fun toggleBatteryRestriction() = updatePreference(
- AppPreferencesIntent.SetLowBatteryRestriction(
- !mutableState.value.preferences.recording.restrictMeasurementOnLowBattery,
+ AppPreferencesIntent.SetStopOnLowBattery(
+ !mutableState.value.preferences.recording.stopRecordingOnLowBattery,
),
)
diff --git a/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/phonelaunch/PhoneLaunchScreen.kt b/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/phonelaunch/PhoneLaunchScreen.kt
index 6ee10d3..a6f44c2 100644
--- a/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/phonelaunch/PhoneLaunchScreen.kt
+++ b/wear/src/main/java/com/tomasrepcik/sensorbox/presentation/phonelaunch/PhoneLaunchScreen.kt
@@ -5,7 +5,7 @@ import androidx.compose.ui.res.stringResource
import com.tomasrepcik.sensorbox.R
import com.tomasrepcik.sensorbox.presentation.WearListScreen
import com.tomasrepcik.sensorbox.presentation.WearPageTitle
-import com.tomasrepcik.sensorbox.presentation.WearPrimaryAction
+import com.tomasrepcik.sensorbox.presentation.WearPrimaryButton
import com.tomasrepcik.sensorbox.presentation.WearSectionTitle
@Composable
@@ -14,7 +14,7 @@ fun PhoneLaunchScreen(state: PhoneLaunchState, onLaunchPhone: () -> Unit) {
item { WearPageTitle(stringResource(R.string.activity_info_phone), transformation) }
item { WearSectionTitle(stringResource(statusText(state.status)), transformation) }
item {
- WearPrimaryAction(
+ WearPrimaryButton(
label = stringResource(R.string.open_phone),
transformation = transformation,
onClick = onLaunchPhone,
diff --git a/wear/src/screenshotTest/kotlin/com/tomasrepcik/sensorbox/presentation/dashboard/ReadmeWearScreenshotPreviews.kt b/wear/src/screenshotTest/kotlin/com/tomasrepcik/sensorbox/presentation/dashboard/ReadmeWearScreenshotPreviews.kt
index a519c5b..8038dac 100644
--- a/wear/src/screenshotTest/kotlin/com/tomasrepcik/sensorbox/presentation/dashboard/ReadmeWearScreenshotPreviews.kt
+++ b/wear/src/screenshotTest/kotlin/com/tomasrepcik/sensorbox/presentation/dashboard/ReadmeWearScreenshotPreviews.kt
@@ -19,7 +19,7 @@ import com.patrykandpatrick.vico.compose.cartesian.data.CartesianChartModelProdu
import com.tomasrepcik.sensorbox.core.preferences.AppPreferences
import com.tomasrepcik.sensorbox.core.preferences.DisplayPreferences
import com.tomasrepcik.sensorbox.core.preferences.RecordingPreferences
-import com.tomasrepcik.sensorbox.domain.sensors.WearSensorDescriptor
+import com.tomasrepcik.sensorbox.domain.sensors.WatchSensorDescriptor
import com.tomasrepcik.sensorbox.ui.theme.WearSensorBoxTheme
@PreviewTest
@@ -99,7 +99,7 @@ private fun readmeWearSensors() = listOf(
readmeWearSensor(2, "Goldfish 3-axis Magnetic field sensor"),
)
-private fun readmeWearSensor(type: Int, name: String) = WearSensorDescriptor(
+private fun readmeWearSensor(type: Int, name: String) = WatchSensorDescriptor(
type = type,
name = name,
vendor = "Android",
diff --git a/wear/src/test/java/com/tomasrepcik/sensorbox/communication/WearCommandHandlerTest.kt b/wear/src/test/java/com/tomasrepcik/sensorbox/communication/WearCommandHandlerTest.kt
index 0524e03..2a8565c 100644
--- a/wear/src/test/java/com/tomasrepcik/sensorbox/communication/WearCommandHandlerTest.kt
+++ b/wear/src/test/java/com/tomasrepcik/sensorbox/communication/WearCommandHandlerTest.kt
@@ -5,7 +5,7 @@ import com.tomasrepcik.sensorbox.core.error.AppErrorCode
import com.tomasrepcik.sensorbox.core.error.AppResult
import com.tomasrepcik.sensorbox.core.error.DiagnosticLogger
import com.tomasrepcik.sensorbox.core.preferences.AppPreferences
-import com.tomasrepcik.sensorbox.domain.measurement.WearRecordingControlUseCase
+import com.tomasrepcik.sensorbox.domain.recording.WatchRecordingControlUseCase
import com.tomasrepcik.sensorbox.wearoslib.WearOsConstants.WEAR_MESSAGE_PATH
import com.tomasrepcik.sensorbox.wearoslib.connectivity.SendWearMessageUseCase
import com.tomasrepcik.sensorbox.wearoslib.connectivity.WearConnection
@@ -69,7 +69,7 @@ class WearCommandHandlerTest {
fixture.requirements.validationResult = AppResult.failure(
AppError(
code = AppErrorCode.PERMISSION,
- operation = "Validate Wear recording permissions",
+ operation = "Validate watch recording permissions",
diagnosticMessage = "Wear OS is missing required recording permissions",
context = mapOf("missingPermissions" to "android.permission.ACCESS_FINE_LOCATION"),
),
@@ -82,7 +82,7 @@ class WearCommandHandlerTest {
val result = fixture.repository.commands.single() as WearCommand.RecordingResult
assertEquals(WearRecordingOutcome.FAILED, result.outcome)
assertEquals(AppErrorCode.PERMISSION, result.errorCode)
- assertEquals("Validate Wear recording permissions", result.errorOperation)
+ assertEquals("Validate watch recording permissions", result.errorOperation)
assertEquals(
"android.permission.ACCESS_FINE_LOCATION",
result.errorContext["missingPermissions"],
@@ -139,7 +139,7 @@ class WearCommandHandlerTest {
}
}
-private class FakeWearRecordingControl : WearRecordingControlUseCase {
+private class FakeWearRecordingControl : WatchRecordingControlUseCase {
var startCalls = 0
var stopCalls = 0
diff --git a/wear/src/test/java/com/tomasrepcik/sensorbox/domain/measurement/WearMeasurementPermissionUseCaseTest.kt b/wear/src/test/java/com/tomasrepcik/sensorbox/domain/measurement/WearMeasurementPermissionUseCaseTest.kt
deleted file mode 100644
index da50589..0000000
--- a/wear/src/test/java/com/tomasrepcik/sensorbox/domain/measurement/WearMeasurementPermissionUseCaseTest.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.tomasrepcik.sensorbox.domain.measurement
-
-import android.Manifest
-import org.junit.Assert.assertEquals
-import org.junit.Test
-
-class WearMeasurementPermissionUseCaseTest {
- @Test
- fun `Given Wear GPS When permissions are derived Then only location is required`() {
- assertEquals(
- setOf(Manifest.permission.ACCESS_FINE_LOCATION),
- requiredWearMeasurementPermissions(includesGps = true),
- )
- }
-
- @Test
- fun `Given Wear sensors without GPS When permissions are derived Then none are required`() {
- assertEquals(
- emptySet(),
- requiredWearMeasurementPermissions(includesGps = false),
- )
- }
-}
diff --git a/wear/src/test/java/com/tomasrepcik/sensorbox/domain/recording/WatchRecordingPermissionUseCaseTest.kt b/wear/src/test/java/com/tomasrepcik/sensorbox/domain/recording/WatchRecordingPermissionUseCaseTest.kt
new file mode 100644
index 0000000..726b64a
--- /dev/null
+++ b/wear/src/test/java/com/tomasrepcik/sensorbox/domain/recording/WatchRecordingPermissionUseCaseTest.kt
@@ -0,0 +1,23 @@
+package com.tomasrepcik.sensorbox.domain.recording
+
+import android.Manifest
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class WatchRecordingPermissionUseCaseTest {
+ @Test
+ fun `Given watch GPS When permissions are derived Then only location is required`() {
+ assertEquals(
+ setOf(Manifest.permission.ACCESS_FINE_LOCATION),
+ requiredWatchRecordingPermissions(includesGps = true),
+ )
+ }
+
+ @Test
+ fun `Given watch sensors without GPS When permissions are derived Then none are required`() {
+ assertEquals(
+ emptySet(),
+ requiredWatchRecordingPermissions(includesGps = false),
+ )
+ }
+}
diff --git a/wear/src/test/java/com/tomasrepcik/sensorbox/domain/sensors/WearSensorTypesTest.kt b/wear/src/test/java/com/tomasrepcik/sensorbox/domain/sensors/WatchSensorTypesTest.kt
similarity index 62%
rename from wear/src/test/java/com/tomasrepcik/sensorbox/domain/sensors/WearSensorTypesTest.kt
rename to wear/src/test/java/com/tomasrepcik/sensorbox/domain/sensors/WatchSensorTypesTest.kt
index 98c23d1..5d77e36 100644
--- a/wear/src/test/java/com/tomasrepcik/sensorbox/domain/sensors/WearSensorTypesTest.kt
+++ b/wear/src/test/java/com/tomasrepcik/sensorbox/domain/sensors/WatchSensorTypesTest.kt
@@ -4,9 +4,9 @@ import android.hardware.Sensor
import org.junit.Assert.assertFalse
import org.junit.Test
-class WearSensorTypesTest {
+class WatchSensorTypesTest {
@Test
- fun `Given Wear sensor discovery When supported types are inspected Then heart rate is absent`() {
+ fun `Given watch sensor discovery When supported types are inspected Then heart rate is absent`() {
assertFalse(Sensor.TYPE_HEART_RATE in WEAR_SENSOR_TYPES)
}
}
diff --git a/wear/src/test/java/com/tomasrepcik/sensorbox/presentation/dashboard/WearDashboardReducerTest.kt b/wear/src/test/java/com/tomasrepcik/sensorbox/presentation/dashboard/WearDashboardReducerTest.kt
index c9ef21d..2fcd87e 100644
--- a/wear/src/test/java/com/tomasrepcik/sensorbox/presentation/dashboard/WearDashboardReducerTest.kt
+++ b/wear/src/test/java/com/tomasrepcik/sensorbox/presentation/dashboard/WearDashboardReducerTest.kt
@@ -2,7 +2,7 @@ package com.tomasrepcik.sensorbox.presentation.dashboard
import com.tomasrepcik.sensorbox.core.testing.AppPreferencesFixtures
import com.tomasrepcik.sensorbox.presentation.menu.WearMenuDestination
-import com.tomasrepcik.sensorbox.sensorservices.session.MeasurementSessionState
+import com.tomasrepcik.sensorbox.sensorservices.session.RecordingSessionState
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
@@ -54,12 +54,12 @@ class WearDashboardReducerTest {
@Test
fun `Given recording screen When recording finishes Then menu with sync is restored`() {
- val active = WearDashboardReducer.measurementSessionChanged(
+ val active = WearDashboardReducer.recordingSessionChanged(
WearDashboardState(route = WearRoute.RECORD),
- MeasurementSessionState.Running("session", "fixture", 1L, listOf(1), false),
+ RecordingSessionState.Running("session", "fixture", 1L, listOf(1), false),
)
- val finished = WearDashboardReducer.measurementSessionChanged(active, MeasurementSessionState.Idle)
+ val finished = WearDashboardReducer.recordingSessionChanged(active, RecordingSessionState.Idle)
assertEquals(WearRoute.ACTIVE, active.route)
assertEquals(WearRoute.MENU, finished.route)