diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/models/ExperimentalProjectConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/ExperimentalProjectConfiguration.kt index 1f13c61f67..97de79f867 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/models/ExperimentalProjectConfiguration.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/ExperimentalProjectConfiguration.kt @@ -283,6 +283,15 @@ data class ExperimentalProjectConfiguration( ?.booleanOrNull .let { it == true } + /** How often the device data state is reported to the backend, `0` disables the reporting. */ + val deviceStatusRepeatIntervalHours: Long + get() = customConfig + ?.get(DEVICE_STATUS_REPEAT_INTERVAL_HOURS) + ?.jsonPrimitive + ?.longOrNull + ?.coerceAtLeast(0) + ?: DEVICE_STATUS_REPEAT_INTERVAL_HOURS_DEFAULT + companion object { internal const val DISABLE_SUBJECT_POOL_VALIDATION = "disableSubjectPoolValidation" internal const val FACE_AUTO_CAPTURE_IMAGING_DURATION_MILLIS = "faceAutoCaptureImagingDurationMillis" @@ -373,5 +382,8 @@ data class ExperimentalProjectConfiguration( const val SPOOF_CHECK_MIN_VALIDATION_ERROR_UI_DURATION_MS_DEFAULT = 2000 const val USE_PARENTAL_CONSENT_AS_DEFAULT = "useParentalConsentAsDefault" + + const val DEVICE_STATUS_REPEAT_INTERVAL_HOURS = "deviceStatusRepeatIntervalHours" + const val DEVICE_STATUS_REPEAT_INTERVAL_HOURS_DEFAULT = 8L } } diff --git a/infra/config-store/src/test/java/com/simprints/infra/config/store/models/ExperimentalProjectConfigurationTest.kt b/infra/config-store/src/test/java/com/simprints/infra/config/store/models/ExperimentalProjectConfigurationTest.kt index bd2759cfb6..0c8548b3b0 100644 --- a/infra/config-store/src/test/java/com/simprints/infra/config/store/models/ExperimentalProjectConfigurationTest.kt +++ b/infra/config-store/src/test/java/com/simprints/infra/config/store/models/ExperimentalProjectConfigurationTest.kt @@ -503,4 +503,18 @@ internal class ExperimentalProjectConfigurationTest { assertThat(ExperimentalProjectConfiguration(config).faceCaptureAcceptedAreaRange).isEqualTo(result) } } + + @Test + fun `check device status repeat interval parsed correctly`() { + mapOf( + emptyMap() to ExperimentalProjectConfiguration.DEVICE_STATUS_REPEAT_INTERVAL_HOURS_DEFAULT, + mapOf(ExperimentalProjectConfiguration.DEVICE_STATUS_REPEAT_INTERVAL_HOURS to JsonPrimitive(true)) + to ExperimentalProjectConfiguration.DEVICE_STATUS_REPEAT_INTERVAL_HOURS_DEFAULT, + mapOf(ExperimentalProjectConfiguration.DEVICE_STATUS_REPEAT_INTERVAL_HOURS to JsonPrimitive(24)) to 24L, + mapOf(ExperimentalProjectConfiguration.DEVICE_STATUS_REPEAT_INTERVAL_HOURS to JsonPrimitive(0)) to 0L, + mapOf(ExperimentalProjectConfiguration.DEVICE_STATUS_REPEAT_INTERVAL_HOURS to JsonPrimitive(-5)) to 0L, + ).forEach { (config, result) -> + assertThat(ExperimentalProjectConfiguration(config).deviceStatusRepeatIntervalHours).isEqualTo(result) + } + } } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiTimestamp.kt b/infra/core/src/main/java/com/simprints/core/api/ApiTimestamp.kt similarity index 58% rename from infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiTimestamp.kt rename to infra/core/src/main/java/com/simprints/core/api/ApiTimestamp.kt index 2b4aa3129a..32a4ccc0b5 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiTimestamp.kt +++ b/infra/core/src/main/java/com/simprints/core/api/ApiTimestamp.kt @@ -1,4 +1,4 @@ -package com.simprints.infra.eventsync.event.remote.models +package com.simprints.core.api import androidx.annotation.Keep import com.simprints.core.tools.time.Timestamp @@ -6,10 +6,10 @@ import kotlinx.serialization.Serializable @Keep @Serializable -internal data class ApiTimestamp( +data class ApiTimestamp( val unixMs: Long, val isUnixMsTrustworthy: Boolean = false, val elapsedSinceBoot: Long? = null, ) -internal fun Timestamp.fromDomainToApi() = ApiTimestamp(ms, isTrustworthy, msSinceBoot) +fun Timestamp.fromDomainToApi() = ApiTimestamp(ms, isTrustworthy, msSinceBoot) diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiAgeGroupSelectionPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiAgeGroupSelectionPayload.kt index dd06bac0a1..1ca7c0c7b2 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiAgeGroupSelectionPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiAgeGroupSelectionPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.AgeGroupSelectionEvent import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiAlertScreenPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiAlertScreenPayload.kt index 8904ea8465..eccc7f6c49 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiAlertScreenPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiAlertScreenPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.AlertScreenEvent.AlertScreenPayload import com.simprints.infra.events.event.domain.models.AlertScreenEvent.AlertScreenPayload.AlertScreenEventType diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiAuthenticationPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiAuthenticationPayload.kt index 6dabc50b3a..492fac8dea 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiAuthenticationPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiAuthenticationPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.AuthenticationEvent.AuthenticationPayload import com.simprints.infra.events.event.domain.models.AuthenticationEvent.AuthenticationPayload.Result.AUTHENTICATED diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiAuthorizationPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiAuthorizationPayload.kt index b00b53f1e9..ac9889cda2 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiAuthorizationPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiAuthorizationPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.AuthorizationEvent.AuthorizationPayload import com.simprints.infra.events.event.domain.models.AuthorizationEvent.AuthorizationPayload.AuthorizationResult.AUTHORIZED diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiBiometricReferenceCreationPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiBiometricReferenceCreationPayload.kt index 37b8bbabba..74197411c4 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiBiometricReferenceCreationPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiBiometricReferenceCreationPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.BiometricReferenceCreationEvent.BiometricReferenceCreationPayload import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCallbackPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCallbackPayload.kt index 0ab8bd33e7..9e56cf26fb 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCallbackPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCallbackPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.ConfirmationCallbackEvent import com.simprints.infra.events.event.domain.models.EnrolmentCallbackEvent diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCalloutPayloadV2.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCalloutPayloadV2.kt index c491f5e896..d20b5185a2 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCalloutPayloadV2.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCalloutPayloadV2.kt @@ -2,6 +2,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep import com.simprints.core.ExcludedFromGeneratedTestCoverageReports +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.ConfirmationCalloutEventV2 import com.simprints.infra.events.event.domain.models.EnrolmentCalloutEventV2 diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCalloutPayloadV3.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCalloutPayloadV3.kt index 383fb020cc..6e5e1294bd 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCalloutPayloadV3.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCalloutPayloadV3.kt @@ -2,6 +2,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep import com.simprints.core.ExcludedFromGeneratedTestCoverageReports +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.ConfirmationCalloutEventV3 import com.simprints.infra.events.event.domain.models.EnrolmentCalloutEventV3 diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCandidateReadPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCandidateReadPayload.kt index 8b69966a1f..633a641afc 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCandidateReadPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCandidateReadPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.CandidateReadEvent.CandidateReadPayload import com.simprints.infra.events.event.domain.models.CandidateReadEvent.CandidateReadPayload.LocalResult diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCompletionCheckPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCompletionCheckPayload.kt index fc9548f527..1dff569a60 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCompletionCheckPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiCompletionCheckPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.CompletionCheckEvent.CompletionCheckPayload import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiConnectivitySnapshotPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiConnectivitySnapshotPayload.kt index c6e6603bdd..9f771f5dfa 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiConnectivitySnapshotPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiConnectivitySnapshotPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.core.tools.utils.SimNetworkUtils import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.ConnectivitySnapshotEvent.ConnectivitySnapshotPayload diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiConsentPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiConsentPayload.kt index 1962e7e8ff..b60fd5c94b 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiConsentPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiConsentPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.ConsentEvent.ConsentPayload import com.simprints.infra.events.event.domain.models.ConsentEvent.ConsentPayload.Result.ACCEPTED diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiDeviceConfigurationUpdatedPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiDeviceConfigurationUpdatedPayload.kt index 3f830198bc..07f64d13b7 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiDeviceConfigurationUpdatedPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiDeviceConfigurationUpdatedPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.DeviceConfigurationUpdatedEvent import com.simprints.infra.events.event.domain.models.DeviceConfigurationUpdatedEvent.DeviceConfigurationUpdateSource diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV2.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV2.kt index c3ce62aab1..fb182ae268 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV2.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV2.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.EnrolmentEventV2 import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV4.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV4.kt index c0dad2f2f8..97a5dce1a4 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV4.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV4.kt @@ -2,6 +2,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep import com.simprints.core.ExcludedFromGeneratedTestCoverageReports +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.EnrolmentEventV4 import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentUpdatePayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentUpdatePayload.kt index f93ff33513..9c74b2e389 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentUpdatePayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentUpdatePayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.EnrolmentUpdateEvent.EnrolmentUpdatePayload import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventDownSyncRequestPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventDownSyncRequestPayload.kt index 85950b6a83..8aee7823be 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventDownSyncRequestPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventDownSyncRequestPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.EventDownSyncRequestEvent import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayload.kt index c41fc24452..9f1ce1c339 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayload.kt @@ -1,6 +1,7 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.AgeGroupSelectionEvent import com.simprints.infra.events.event.domain.models.AlertScreenEvent.AlertScreenPayload diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventSampleUpSyncRequestPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventSampleUpSyncRequestPayload.kt index 0dadffbc20..9bd1c5278f 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventSampleUpSyncRequestPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventSampleUpSyncRequestPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.SampleUpSyncRequestEvent import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventUpSyncRequestPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventUpSyncRequestPayload.kt index dca0f6a2f1..4727b9b108 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventUpSyncRequestPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventUpSyncRequestPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.EventUpSyncRequestEvent import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialCapturePayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialCapturePayload.kt index 39eaf51c7a..b9b975685e 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialCapturePayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialCapturePayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.ExternalCredentialCaptureEvent.ExternalCredentialCapturePayload import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialCaptureValuePayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialCaptureValuePayload.kt index 186255fbad..0663e57008 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialCaptureValuePayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialCaptureValuePayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.ExternalCredentialCaptureValueEvent.ExternalCredentialCaptureValuePayload import com.simprints.infra.eventsync.event.remote.ApiExternalCredential diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialConfirmationPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialConfirmationPayload.kt index 972ecea9e4..086994dcea 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialConfirmationPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialConfirmationPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.ExternalCredentialConfirmationEvent import com.simprints.infra.events.event.domain.models.ExternalCredentialConfirmationEvent.ExternalCredentialConfirmationResult diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialSearchPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialSearchPayload.kt index b30c3cab42..d547bf3445 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialSearchPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialSearchPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.ExternalCredentialSearchEvent import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialSelectionPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialSelectionPayload.kt index 865047e955..2530424acb 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialSelectionPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiExternalCredentialSelectionPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.ExternalCredentialSelectionEvent import com.simprints.infra.events.event.domain.models.ExternalCredentialSelectionEvent.SkipReason diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceCaptureAttemptPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceCaptureAttemptPayload.kt index 17d067aa9c..63afc550c3 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceCaptureAttemptPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceCaptureAttemptPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.FaceCaptureAttemptEvent.FaceCaptureAttemptPayload import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceCaptureBiometricsPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceCaptureBiometricsPayload.kt index bd8deb4901..aca1e3a711 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceCaptureBiometricsPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceCaptureBiometricsPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.FaceCaptureBiometricsEvent import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceCaptureConfirmationPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceCaptureConfirmationPayload.kt index 168288ccab..26830fb80e 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceCaptureConfirmationPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceCaptureConfirmationPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.FaceCaptureConfirmationEvent.FaceCaptureConfirmationPayload import com.simprints.infra.events.event.domain.models.FaceCaptureConfirmationEvent.FaceCaptureConfirmationPayload.Result.CONTINUE diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceCapturePayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceCapturePayload.kt index a6bb29ec47..69a87d51de 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceCapturePayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceCapturePayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.FaceCaptureEvent.FaceCapturePayload import com.simprints.infra.events.event.domain.models.FaceCaptureEvent.FaceCapturePayload.SpoofSkipReason diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceFallbackCapturePayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceFallbackCapturePayload.kt index 7adbe2fe41..d5d937689c 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceFallbackCapturePayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceFallbackCapturePayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.FaceFallbackCaptureEvent.FaceFallbackCapturePayload import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceOnboardingCompletePayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceOnboardingCompletePayload.kt index fe2db6de5f..c1da3262a8 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceOnboardingCompletePayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFaceOnboardingCompletePayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.FaceOnboardingCompleteEvent.FaceOnboardingCompletePayload import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFingerprintCaptureBiometricsPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFingerprintCaptureBiometricsPayload.kt index 106315a503..f65aa55460 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFingerprintCaptureBiometricsPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFingerprintCaptureBiometricsPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.core.domain.common.TemplateIdentifier import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.FingerprintCaptureBiometricsEvent diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFingerprintCapturePayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFingerprintCapturePayload.kt index 19e1165d71..0e53f8b9c5 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFingerprintCapturePayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiFingerprintCapturePayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.core.domain.common.TemplateIdentifier import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.FingerprintCaptureEvent.FingerprintCapturePayload diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiGuidSelectionPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiGuidSelectionPayload.kt index 8854347f7b..43562f0817 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiGuidSelectionPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiGuidSelectionPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.GuidSelectionEvent.GuidSelectionPayload import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiIntentParsingPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiIntentParsingPayload.kt index 2b8bc135e0..376f95a3af 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiIntentParsingPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiIntentParsingPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.IntentParsingEvent.IntentParsingPayload import com.simprints.infra.events.event.domain.models.IntentParsingEvent.IntentParsingPayload.IntegrationInfo diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiInvalidIntentPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiInvalidIntentPayload.kt index ec11ad08d3..6dc4b00752 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiInvalidIntentPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiInvalidIntentPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.core.tools.extensions.toStringMap import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.InvalidIntentEvent.InvalidIntentPayload diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiLicenseCheckEventPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiLicenseCheckEventPayload.kt index 873957465e..20948ef6f5 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiLicenseCheckEventPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiLicenseCheckEventPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.LicenseCheckEvent import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToManyMatchPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToManyMatchPayload.kt index ac6d38f6d5..51358f1b0c 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToManyMatchPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToManyMatchPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.EventType import com.simprints.infra.events.event.domain.models.OneToManyMatchEvent.OneToManyMatchPayload diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToOneMatchPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToOneMatchPayload.kt index 9e58bdfc18..cfd4b45d78 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToOneMatchPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToOneMatchPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.OneToOneMatchEvent.OneToOneMatchPayload import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiPersonCreationPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiPersonCreationPayload.kt index 3b6718a03e..efa3512382 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiPersonCreationPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiPersonCreationPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.PersonCreationEvent.PersonCreationPayload import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiRefusalPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiRefusalPayload.kt index ed332fd6e7..1b65d2bbec 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiRefusalPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiRefusalPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.RefusalEvent.RefusalPayload import com.simprints.infra.events.event.domain.models.RefusalEvent.RefusalPayload.Answer.APP_NOT_WORKING diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiScannerConnectionPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiScannerConnectionPayload.kt index 3f951e6d50..ad3653e91e 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiScannerConnectionPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiScannerConnectionPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.ScannerConnectionEvent.ScannerConnectionPayload import com.simprints.infra.events.event.domain.models.ScannerConnectionEvent.ScannerConnectionPayload.ScannerGeneration diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiScannerFirmwareUpdatePayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiScannerFirmwareUpdatePayload.kt index 73b08b4b01..c4af512b27 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiScannerFirmwareUpdatePayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiScannerFirmwareUpdatePayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.ScannerFirmwareUpdateEvent.ScannerFirmwareUpdatePayload import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiSuspiciousIntentPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiSuspiciousIntentPayload.kt index c06cbee87d..21baced2a0 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiSuspiciousIntentPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiSuspiciousIntentPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.core.tools.extensions.toStringMap import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.SuspiciousIntentEvent.SuspiciousIntentPayload diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiVero2InfoSnapshotPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiVero2InfoSnapshotPayload.kt index a6dceb7a36..68396b45f4 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiVero2InfoSnapshotPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiVero2InfoSnapshotPayload.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.Vero2InfoSnapshotEvent import com.simprints.infra.events.event.domain.models.Vero2InfoSnapshotEvent.BatteryInfo diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/session/ApiEventScope.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/session/ApiEventScope.kt index 951d52b606..e8ff88232d 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/session/ApiEventScope.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/session/ApiEventScope.kt @@ -1,9 +1,9 @@ package com.simprints.infra.eventsync.event.remote.models.session import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp import com.simprints.infra.eventsync.event.remote.models.ApiEvent import com.simprints.infra.eventsync.event.remote.models.ApiModality -import com.simprints.infra.eventsync.event.remote.models.ApiTimestamp import kotlinx.serialization.EncodeDefault import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/session/ApiLocation.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/session/ApiLocation.kt index e39a0a11f9..5acba775a9 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/session/ApiLocation.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/session/ApiLocation.kt @@ -1,9 +1,9 @@ package com.simprints.infra.eventsync.event.remote.models.session import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.events.event.domain.models.scope.Location -import com.simprints.infra.eventsync.event.remote.models.ApiTimestamp -import com.simprints.infra.eventsync.event.remote.models.fromDomainToApi import kotlinx.serialization.Serializable @Keep diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/usecases/MapDomainEventScopeToApiUseCase.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/usecases/MapDomainEventScopeToApiUseCase.kt index c096732ede..b3de789fde 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/usecases/MapDomainEventScopeToApiUseCase.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/usecases/MapDomainEventScopeToApiUseCase.kt @@ -1,5 +1,6 @@ package com.simprints.infra.eventsync.event.usecases +import com.simprints.core.api.fromDomainToApi import com.simprints.infra.config.store.models.Project import com.simprints.infra.events.event.domain.models.Event import com.simprints.infra.events.event.domain.models.scope.EventScope diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiFingerprintCapturePayloadTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiFingerprintCapturePayloadTest.kt index b7c389eb42..b0fddc28a6 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiFingerprintCapturePayloadTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiFingerprintCapturePayloadTest.kt @@ -1,6 +1,7 @@ package com.simprints.infra.eventsync.event.remote.models import com.google.common.truth.Truth.assertThat +import com.simprints.core.api.ApiTimestamp import com.simprints.core.domain.common.TemplateIdentifier import com.simprints.core.tools.utils.randomUUID import com.simprints.infra.config.store.models.TokenKeyType diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToManyMatchPayloadTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToManyMatchPayloadTest.kt index 5d18c9f0f6..96fd4e70fe 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToManyMatchPayloadTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToManyMatchPayloadTest.kt @@ -1,6 +1,7 @@ package com.simprints.infra.eventsync.event.remote.models import com.google.common.truth.Truth.assertThat +import com.simprints.core.api.ApiTimestamp import com.simprints.infra.config.store.models.TokenKeyType import io.mockk.mockk import org.junit.Test diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/face/ApiFaceCapturePayloadTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/face/ApiFaceCapturePayloadTest.kt index 39ee1bcb54..08d402b65d 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/face/ApiFaceCapturePayloadTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/face/ApiFaceCapturePayloadTest.kt @@ -1,11 +1,11 @@ package com.simprints.infra.eventsync.event.remote.models.face import com.google.common.truth.Truth.* +import com.simprints.core.api.ApiTimestamp import com.simprints.core.tools.utils.randomUUID import com.simprints.infra.events.event.domain.models.FaceCaptureEvent import com.simprints.infra.events.sampledata.FACE_TEMPLATE_FORMAT import com.simprints.infra.eventsync.event.remote.models.ApiFaceCapturePayload -import com.simprints.infra.eventsync.event.remote.models.ApiTimestamp import com.simprints.infra.eventsync.event.remote.models.fromDomainToApi import org.junit.Test diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/session/ApiEventScopeTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/session/ApiEventScopeTest.kt index 3ea0b9e6ae..93ecf12cfd 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/session/ApiEventScopeTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/session/ApiEventScopeTest.kt @@ -1,7 +1,7 @@ package com.simprints.infra.eventsync.event.remote.models.session import com.google.common.truth.Truth -import com.simprints.infra.eventsync.event.remote.models.ApiTimestamp +import com.simprints.core.api.ApiTimestamp import com.simprints.infra.serialization.SimJson import org.junit.Test diff --git a/infra/sync/build.gradle.kts b/infra/sync/build.gradle.kts index 57a5a7b17c..f2d8ebe90b 100644 --- a/infra/sync/build.gradle.kts +++ b/infra/sync/build.gradle.kts @@ -1,5 +1,7 @@ plugins { id("simprints.infra") + id("simprints.library.kotlinSerialization") + id("simprints.library.backendApi") } android { @@ -42,5 +44,4 @@ dependencies { implementation(project(":fingerprint:infra:scanner")) implementation(libs.workManager.work) - implementation(libs.kotlin.serialization) } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/ScheduleSyncCommand.kt b/infra/sync/src/main/java/com/simprints/infra/sync/ScheduleSyncCommand.kt index 83aa579534..e6a4adf936 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/ScheduleSyncCommand.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/ScheduleSyncCommand.kt @@ -25,6 +25,10 @@ sealed class ScheduleCommand { val action: Action, ) : ScheduleCommand() + internal data class DeviceStatusCommand( + val action: Action, + ) : ScheduleCommand() + object Everything { fun reschedule(withDelay: Boolean = false): ScheduleCommand = EverythingCommand(action = Action.RESCHEDULE, withDelay = withDelay) @@ -42,4 +46,10 @@ sealed class ScheduleCommand { fun unschedule(): ScheduleCommand = ImagesCommand(action = Action.UNSCHEDULE) } + + object DeviceStatus { + fun reschedule(): ScheduleCommand = DeviceStatusCommand(action = Action.RESCHEDULE) + + fun unschedule(): ScheduleCommand = DeviceStatusCommand(action = Action.UNSCHEDULE) + } } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt index c7f931cb35..5ade366f17 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt @@ -28,6 +28,11 @@ internal object SyncConstants { const val EVENT_SYNC_WORK_NAME_ONE_TIME = "event-sync-work-one-time" const val EVENT_SYNC_WORKER_INTERVAL = BuildConfig.EVENT_SYNC_WORKER_INTERVAL_MINUTES + const val DEVICE_STATUS_REPORT_WORK_NAME = "device-status-report-work" + + // Offset from the other workers that are scheduled at the same time to avoid running in parallel + const val DEVICE_STATUS_REPORT_INITIAL_DELAY_MINUTES = 20L + const val PROGRESS_CURRENT = "progress_current" const val PROGRESS_MAX = "progress_max" diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt index 364550762a..7a90b45938 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt @@ -12,6 +12,7 @@ import com.simprints.core.AppScope import com.simprints.core.DispatcherIO import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.ConfigRepository +import com.simprints.infra.config.store.models.experimental import com.simprints.infra.config.store.models.imagesUploadRequiresUnmeteredConnection import com.simprints.infra.config.store.models.isCommCareEventDownSyncAllowed import com.simprints.infra.eventsync.DeleteSyncInfoUseCase @@ -21,6 +22,7 @@ import com.simprints.infra.eventsync.sync.EventSyncStateProcessor import com.simprints.infra.eventsync.sync.master.EventSyncMasterWorker import com.simprints.infra.sync.config.worker.DeviceConfigDownSyncWorker import com.simprints.infra.sync.config.worker.ProjectConfigDownSyncWorker +import com.simprints.infra.sync.devicestate.worker.DeviceStatusReportWorker import com.simprints.infra.sync.enrolments.EnrolmentRecordWorker import com.simprints.infra.sync.extensions.anyRunning import com.simprints.infra.sync.extensions.cancelWorkers @@ -45,6 +47,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton @@ -148,6 +151,12 @@ internal class SyncOrchestratorImpl @Inject constructor( unschedule = ::stopImageSync, reschedule = { rescheduleImageUpSync() }, ) + + is ScheduleCommand.DeviceStatusCommand -> executeSchedulingAction( + action = command.action, + unschedule = ::cancelDeviceStatusReport, + reschedule = { rescheduleDeviceStatusReport() }, + ) } override fun startConfigSync() { @@ -255,6 +264,7 @@ internal class SyncOrchestratorImpl @Inject constructor( constraints = getImageUploadConstraints(), ) rescheduleEventSync(withDelay = withDelay) + rescheduleDeviceStatusReport() if (shouldScheduleFirmwareUpdate()) { workManager.schedulePeriodicWorker( SyncConstants.FIRMWARE_UPDATE_WORK_NAME, @@ -273,6 +283,7 @@ internal class SyncOrchestratorImpl @Inject constructor( SyncConstants.FILE_UP_SYNC_WORK_NAME, SyncConstants.EVENT_SYNC_WORK_NAME, SyncConstants.FIRMWARE_UPDATE_WORK_NAME, + SyncConstants.DEVICE_STATUS_REPORT_WORK_NAME, ) stopEventSync() } @@ -330,6 +341,30 @@ internal class SyncOrchestratorImpl @Inject constructor( ) } + /** + * The interval comes from project configuration, so it is re-applied in place (UPDATE policy) + * rather than re-enqueued, to keep the existing schedule when nothing has changed. + */ + private suspend fun rescheduleDeviceStatusReport() { + val intervalHours = configRepository + .getProjectConfiguration() + .experimental() + .deviceStatusRepeatIntervalHours + if (intervalHours <= 0) { + cancelDeviceStatusReport() + return + } + workManager.schedulePeriodicWorker( + workName = SyncConstants.DEVICE_STATUS_REPORT_WORK_NAME, + repeatInterval = TimeUnit.HOURS.toMinutes(intervalHours), + initialDelay = SyncConstants.DEVICE_STATUS_REPORT_INITIAL_DELAY_MINUTES, + ) + } + + private fun cancelDeviceStatusReport() { + workManager.cancelWorkers(SyncConstants.DEVICE_STATUS_REPORT_WORK_NAME) + } + private suspend fun getImageUploadConstraints(): Constraints { val networkType = configRepository .getProjectConfiguration() diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCase.kt b/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCase.kt index 5f275eb8a2..b97d2808a7 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCase.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCase.kt @@ -1,6 +1,7 @@ package com.simprints.infra.sync.config.usecase import com.simprints.infra.config.store.models.ProjectConfiguration +import com.simprints.infra.config.store.models.experimental import com.simprints.infra.config.store.models.imagesUploadRequiresUnmeteredConnection import com.simprints.infra.sync.ScheduleCommand import com.simprints.infra.sync.SyncOrchestrator @@ -17,10 +18,18 @@ internal class RescheduleWorkersIfConfigChangedUseCase @Inject constructor( if (shouldRescheduleImageUpload(oldConfig, newConfig)) { syncOrchestrator.execute(ScheduleCommand.Images.reschedule()).await() } + if (shouldRescheduleDeviceStatusReport(oldConfig, newConfig)) { + syncOrchestrator.execute(ScheduleCommand.DeviceStatus.reschedule()).await() + } } private fun shouldRescheduleImageUpload( oldConfig: ProjectConfiguration, newConfig: ProjectConfiguration, ) = oldConfig.imagesUploadRequiresUnmeteredConnection() != newConfig.imagesUploadRequiresUnmeteredConnection() + + private fun shouldRescheduleDeviceStatusReport( + oldConfig: ProjectConfiguration, + newConfig: ProjectConfiguration, + ) = oldConfig.experimental().deviceStatusRepeatIntervalHours != newConfig.experimental().deviceStatusRepeatIntervalHours } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/devicestate/ReportDeviceDataStateUseCase.kt b/infra/sync/src/main/java/com/simprints/infra/sync/devicestate/ReportDeviceDataStateUseCase.kt new file mode 100644 index 0000000000..7f593ec278 --- /dev/null +++ b/infra/sync/src/main/java/com/simprints/infra/sync/devicestate/ReportDeviceDataStateUseCase.kt @@ -0,0 +1,28 @@ +package com.simprints.infra.sync.devicestate + +import com.simprints.core.DeviceID +import com.simprints.infra.backendapi.BackendApiClient +import com.simprints.infra.sync.devicestate.remote.DeviceStatusRemoteInterface +import com.simprints.infra.sync.devicestate.remote.toApi +import javax.inject.Inject + +internal class ReportDeviceDataStateUseCase @Inject constructor( + private val deviceStateDataTracker: DeviceStateDataTracker, + private val backendApiClient: BackendApiClient, + @param:DeviceID private val deviceId: String, +) { + /** + * @return `false` if there is no signed-in project to report for, `true` once the report is accepted. + * @throws Throwable if the backend call fails. + */ + suspend operator fun invoke(): Boolean { + val state = deviceStateDataTracker.getCurrentDeviceDataState() + val projectId = state.projectId ?: return false + + backendApiClient + .executeCall(DeviceStatusRemoteInterface::class) { + it.postDeviceStatus(projectId, deviceId, state.toApi()) + }.getOrThrow() + return true + } +} diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/devicestate/remote/ApiDeviceDataState.kt b/infra/sync/src/main/java/com/simprints/infra/sync/devicestate/remote/ApiDeviceDataState.kt new file mode 100644 index 0000000000..fa5d5be0e8 --- /dev/null +++ b/infra/sync/src/main/java/com/simprints/infra/sync/devicestate/remote/ApiDeviceDataState.kt @@ -0,0 +1,51 @@ +package com.simprints.infra.sync.devicestate.remote + +import androidx.annotation.Keep +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.api.fromDomainToApi +import com.simprints.infra.events.event.domain.models.scope.EventScopeType +import com.simprints.infra.sync.devicestate.DeviceDataState +import kotlinx.serialization.Serializable + +/** + * `null` fields are omitted from the request: a source that could not be read must never be reported as `0`. + */ +@Keep +@Serializable +internal data class ApiDeviceDataState( + val scopesToUpload: ApiScopesToUpload?, + val enrolmentsToUpload: Int?, + val samplesToUpload: Int?, + val lastEventSyncAt: ApiTimestamp?, + val lastEventSyncFailureReason: String?, + val lastSampleSyncAt: ApiTimestamp?, + val lastSampleSyncFailureReason: String?, +) + +@Keep +@Serializable +internal data class ApiScopesToUpload( + val sessionCount: Int, + val eventUpSyncCount: Int, + val eventDownSyncCount: Int, + val sampleUpSyncCount: Int, + val deviceCount: Int, +) + +internal fun DeviceDataState.toApi() = ApiDeviceDataState( + scopesToUpload = pendingScopes?.toApi(), + enrolmentsToUpload = pendingEnrolments, + samplesToUpload = pendingSamples, + lastEventSyncAt = lastEventSyncAt?.fromDomainToApi(), + lastEventSyncFailureReason = lastEventSyncFailure?.name, + lastSampleSyncAt = lastSampleSyncAt?.fromDomainToApi(), + lastSampleSyncFailureReason = lastSampleSyncFailure?.name, +) + +private fun Map.toApi(): ApiScopesToUpload = ApiScopesToUpload( + sessionCount = this[EventScopeType.SESSION] ?: 0, + eventUpSyncCount = this[EventScopeType.UP_SYNC] ?: 0, + eventDownSyncCount = this[EventScopeType.DOWN_SYNC] ?: 0, + sampleUpSyncCount = this[EventScopeType.SAMPLE_UP_SYNC] ?: 0, + deviceCount = this[EventScopeType.DEVICE] ?: 0, +) diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/devicestate/remote/DeviceStatusRemoteInterface.kt b/infra/sync/src/main/java/com/simprints/infra/sync/devicestate/remote/DeviceStatusRemoteInterface.kt new file mode 100644 index 0000000000..7643a10a7f --- /dev/null +++ b/infra/sync/src/main/java/com/simprints/infra/sync/devicestate/remote/DeviceStatusRemoteInterface.kt @@ -0,0 +1,17 @@ +package com.simprints.infra.sync.devicestate.remote + +import com.simprints.infra.network.SimRemoteInterface +import okhttp3.ResponseBody +import retrofit2.Response +import retrofit2.http.Body +import retrofit2.http.POST +import retrofit2.http.Path + +internal interface DeviceStatusRemoteInterface : SimRemoteInterface { + @POST("projects/{projectId}/devices/{deviceId}/status") + suspend fun postDeviceStatus( + @Path("projectId") projectId: String, + @Path("deviceId") deviceId: String, + @Body deviceStatus: ApiDeviceDataState, + ): Response +} diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/devicestate/worker/DeviceStatusReportWorker.kt b/infra/sync/src/main/java/com/simprints/infra/sync/devicestate/worker/DeviceStatusReportWorker.kt new file mode 100644 index 0000000000..48e19a1cca --- /dev/null +++ b/infra/sync/src/main/java/com/simprints/infra/sync/devicestate/worker/DeviceStatusReportWorker.kt @@ -0,0 +1,40 @@ +package com.simprints.infra.sync.devicestate.worker + +import android.content.Context +import androidx.hilt.work.HiltWorker +import androidx.work.WorkerParameters +import com.simprints.core.DispatcherBG +import com.simprints.core.workers.SimCoroutineWorker +import com.simprints.infra.sync.devicestate.ReportDeviceDataStateUseCase +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext + +/** + * Periodically reports the device data state to the backend, independently of any other sync. + * + * Failures are not retried: the report is a snapshot, so the next scheduled run sends a fresher one. + */ +@HiltWorker +internal class DeviceStatusReportWorker @AssistedInject constructor( + @Assisted context: Context, + @Assisted params: WorkerParameters, + private val reportDeviceDataState: ReportDeviceDataStateUseCase, + @param:DispatcherBG private val dispatcher: CoroutineDispatcher, +) : SimCoroutineWorker(context, params) { + override val tag: String = "DeviceStatusReport" + + override suspend fun doWork(): Result = withContext(dispatcher) { + crashlyticsLog("Started") + try { + if (reportDeviceDataState()) { + success() + } else { + success(message = "No signed-in project") + } + } catch (t: Throwable) { + fail(t) + } + } +} diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/ScheduleSyncCommandTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/ScheduleSyncCommandTest.kt index 6c32ef6e31..ef13aa8e29 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/ScheduleSyncCommandTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/ScheduleSyncCommandTest.kt @@ -36,4 +36,13 @@ class ScheduleSyncCommandTest { assertThat(ScheduleCommand.Images.unschedule()) .isEqualTo(ScheduleCommand.ImagesCommand(action = ScheduleCommand.Action.UNSCHEDULE)) } + + @Test + fun `device status commands build expected command`() { + assertThat(ScheduleCommand.DeviceStatus.reschedule()) + .isEqualTo(ScheduleCommand.DeviceStatusCommand(action = ScheduleCommand.Action.RESCHEDULE)) + + assertThat(ScheduleCommand.DeviceStatus.unschedule()) + .isEqualTo(ScheduleCommand.DeviceStatusCommand(action = ScheduleCommand.Action.UNSCHEDULE)) + } } diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorCommandExecutionTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorCommandExecutionTest.kt index f641d82be7..150ff28a73 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorCommandExecutionTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorCommandExecutionTest.kt @@ -8,10 +8,12 @@ import androidx.work.WorkManager import com.google.common.truth.Truth.assertThat import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.ConfigRepository +import com.simprints.infra.config.store.models.ExperimentalProjectConfiguration.Companion.DEVICE_STATUS_REPEAT_INTERVAL_HOURS import com.simprints.infra.eventsync.DeleteSyncInfoUseCase import com.simprints.infra.eventsync.EventSyncWorkerTagRepository import com.simprints.infra.eventsync.sync.EventSyncStateProcessor import com.simprints.infra.eventsync.sync.master.EventSyncMasterWorker +import com.simprints.infra.sync.SyncConstants.DEVICE_STATUS_REPORT_WORK_NAME import com.simprints.infra.sync.SyncConstants.DEVICE_SYNC_WORK_NAME import com.simprints.infra.sync.SyncConstants.EVENT_SYNC_WORK_NAME import com.simprints.infra.sync.SyncConstants.EVENT_SYNC_WORK_NAME_ONE_TIME @@ -31,10 +33,12 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.JsonPrimitive import org.junit.Before import org.junit.Rule import org.junit.Test import java.util.UUID +import java.util.concurrent.TimeUnit class SyncOrchestratorCommandExecutionTest { @get:Rule @@ -76,6 +80,7 @@ class SyncOrchestratorCommandExecutionTest { fun setup() { MockKAnnotations.init(this, relaxed = true) every { workManager.getWorkInfosFlow(any()) } returns flowOf(emptyList()) + coEvery { configRepository.getProjectConfiguration().custom } returns null orchestrator = createOrchestrator() } @@ -102,6 +107,7 @@ class SyncOrchestratorCommandExecutionTest { workManager.enqueueUniquePeriodicWork(FILE_UP_SYNC_WORK_NAME, any(), any()) workManager.enqueueUniquePeriodicWork(EVENT_SYNC_WORK_NAME, any(), any()) workManager.enqueueUniquePeriodicWork(FIRMWARE_UPDATE_WORK_NAME, any(), any()) + workManager.enqueueUniquePeriodicWork(DEVICE_STATUS_REPORT_WORK_NAME, any(), any()) } } @@ -169,10 +175,65 @@ class SyncOrchestratorCommandExecutionTest { workManager.cancelUniqueWork(EVENT_SYNC_WORK_NAME) workManager.cancelUniqueWork(EVENT_SYNC_WORK_NAME_ONE_TIME) workManager.cancelUniqueWork(FIRMWARE_UPDATE_WORK_NAME) + workManager.cancelUniqueWork(DEVICE_STATUS_REPORT_WORK_NAME) workManager.cancelAllWorkByTag("syncWorkers") } } + @Test + fun `schedules device status report with default interval and offset`() = runTest { + orchestrator.execute(ScheduleCommand.DeviceStatus.reschedule()).join() + + verify { + workManager.enqueueUniquePeriodicWork( + DEVICE_STATUS_REPORT_WORK_NAME, + ExistingPeriodicWorkPolicy.UPDATE, + match { + it.workSpec.intervalDuration == TimeUnit.HOURS.toMillis(8) && + it.workSpec.initialDelay == TimeUnit.MINUTES.toMillis(20) && + it.workSpec.constraints.requiredNetworkType == NetworkType.CONNECTED + }, + ) + } + } + + @Test + fun `schedules device status report with configured interval`() = runTest { + coEvery { configRepository.getProjectConfiguration().custom } returns mapOf( + DEVICE_STATUS_REPEAT_INTERVAL_HOURS to JsonPrimitive(24), + ) + + orchestrator.execute(ScheduleCommand.DeviceStatus.reschedule()).join() + + verify { + workManager.enqueueUniquePeriodicWork( + DEVICE_STATUS_REPORT_WORK_NAME, + any(), + match { it.workSpec.intervalDuration == TimeUnit.HOURS.toMillis(24) }, + ) + } + } + + @Test + fun `cancels device status report when configured interval is zero`() = runTest { + coEvery { configRepository.getProjectConfiguration().custom } returns mapOf( + DEVICE_STATUS_REPEAT_INTERVAL_HOURS to JsonPrimitive(0), + ) + + orchestrator.execute(ScheduleCommand.DeviceStatus.reschedule()).join() + + verify(exactly = 0) { workManager.enqueueUniquePeriodicWork(DEVICE_STATUS_REPORT_WORK_NAME, any(), any()) } + verify { workManager.cancelUniqueWork(DEVICE_STATUS_REPORT_WORK_NAME) } + } + + @Test + fun `unschedule device status report cancels the worker`() = runTest { + val job = orchestrator.execute(ScheduleCommand.DeviceStatus.unschedule()) + + assertThat(job.isCompleted).isTrue() + verify { workManager.cancelUniqueWork(DEVICE_STATUS_REPORT_WORK_NAME) } + } + @Test fun `reschedules event sync worker with correct tags`() = runTest { every { eventSyncWorkerTagRepository.getPeriodicWorkTags() } returns listOf("tag1", "tag2") diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCaseTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCaseTest.kt index b2f33da44e..7474768435 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCaseTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCaseTest.kt @@ -1,6 +1,7 @@ package com.simprints.infra.sync.config.usecase import com.google.common.truth.Truth.assertThat +import com.simprints.infra.config.store.models.ExperimentalProjectConfiguration.Companion.DEVICE_STATUS_REPEAT_INTERVAL_HOURS import com.simprints.infra.sync.ScheduleCommand import com.simprints.infra.sync.SyncOrchestrator import com.simprints.infra.sync.config.testtools.projectConfiguration @@ -15,6 +16,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.JsonPrimitive import org.junit.Before import org.junit.Test @@ -97,4 +99,35 @@ class RescheduleWorkersIfConfigChangedUseCaseTest { verify { syncOrchestrator.execute(ScheduleCommand.Images.reschedule()) } assertThat(useCaseJob.isCompleted).isTrue() } + + @Test + fun `should not reschedule device status report when interval does not change`() = runTest { + useCase( + projectConfiguration.copy(custom = null), + projectConfiguration.copy(custom = mapOf(DEVICE_STATUS_REPEAT_INTERVAL_HOURS to JsonPrimitive(8))), + ) + + verify(exactly = 0) { syncOrchestrator.execute(ScheduleCommand.DeviceStatus.reschedule()) } + } + + @Test + fun `should reschedule device status report when interval changes`() = runTest { + useCase( + projectConfiguration.copy(custom = null), + projectConfiguration.copy(custom = mapOf(DEVICE_STATUS_REPEAT_INTERVAL_HOURS to JsonPrimitive(24))), + ) + + verify(exactly = 1) { syncOrchestrator.execute(ScheduleCommand.DeviceStatus.reschedule()) } + verify(exactly = 0) { syncOrchestrator.execute(ScheduleCommand.Images.reschedule()) } + } + + @Test + fun `should reschedule device status report when reporting is disabled`() = runTest { + useCase( + projectConfiguration.copy(custom = mapOf(DEVICE_STATUS_REPEAT_INTERVAL_HOURS to JsonPrimitive(8))), + projectConfiguration.copy(custom = mapOf(DEVICE_STATUS_REPEAT_INTERVAL_HOURS to JsonPrimitive(0))), + ) + + verify(exactly = 1) { syncOrchestrator.execute(ScheduleCommand.DeviceStatus.reschedule()) } + } } diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/devicestate/ReportDeviceDataStateUseCaseTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/devicestate/ReportDeviceDataStateUseCaseTest.kt new file mode 100644 index 0000000000..c9f898603f --- /dev/null +++ b/infra/sync/src/test/java/com/simprints/infra/sync/devicestate/ReportDeviceDataStateUseCaseTest.kt @@ -0,0 +1,90 @@ +package com.simprints.infra.sync.devicestate + +import com.google.common.truth.Truth.* +import com.simprints.infra.backendapi.ApiResult +import com.simprints.infra.backendapi.BackendApiClient +import com.simprints.infra.sync.devicestate.remote.DeviceStatusRemoteInterface +import com.simprints.infra.sync.devicestate.remote.toApi +import io.mockk.* +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.test.runTest +import okhttp3.ResponseBody +import org.junit.Before +import org.junit.Test +import retrofit2.Response + +class ReportDeviceDataStateUseCaseTest { + @MockK + private lateinit var tracker: DeviceStateDataTracker + + @MockK + private lateinit var backendApiClient: BackendApiClient + + @MockK + private lateinit var remoteInterface: DeviceStatusRemoteInterface + + private lateinit var useCase: ReportDeviceDataStateUseCase + + @Before + fun setUp() { + MockKAnnotations.init(this, relaxed = true) + coEvery { backendApiClient.executeCall(any(), any()) } coAnswers { + try { + ApiResult.Success(secondArg Any>()(remoteInterface)) + } catch (e: Exception) { + ApiResult.Failure(e) + } + } + coEvery { remoteInterface.postDeviceStatus(any(), any(), any()) } returns Response.success(null as ResponseBody?) + + useCase = ReportDeviceDataStateUseCase(tracker, backendApiClient, DEVICE_ID) + } + + @Test + fun `posts a fresh snapshot for the signed-in project`() = runTest { + val state = state(projectId = PROJECT_ID) + coEvery { tracker.getCurrentDeviceDataState() } returns state + + val reported = useCase() + + assertThat(reported).isTrue() + coVerify { remoteInterface.postDeviceStatus(PROJECT_ID, DEVICE_ID, state.toApi()) } + } + + @Test + fun `skips reporting when no project is signed in`() = runTest { + coEvery { tracker.getCurrentDeviceDataState() } returns state(projectId = null) + + val reported = useCase() + + assertThat(reported).isFalse() + coVerify(exactly = 0) { backendApiClient.executeCall(any(), any()) } + } + + @Test(expected = IllegalStateException::class) + fun `propagates backend exceptions`() = runTest { + coEvery { tracker.getCurrentDeviceDataState() } returns state(projectId = PROJECT_ID) + val exception = IllegalStateException("backend down") + coEvery { remoteInterface.postDeviceStatus(any(), any(), any()) } throws exception + + useCase() + } + + private fun state(projectId: String?) = DeviceDataState( + projectId = projectId, + recordCount = 1, + pendingScopes = emptyMap(), + pendingEvents = 2, + pendingEnrolments = 3, + pendingSamples = 4, + lastEventSyncAt = null, + lastEventSyncFailure = null, + lastSampleSyncAt = null, + lastSampleSyncFailure = null, + ) + + companion object { + private const val PROJECT_ID = "projectId" + private const val DEVICE_ID = "deviceId" + } +} diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/devicestate/remote/ApiDeviceDataStateTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/devicestate/remote/ApiDeviceDataStateTest.kt new file mode 100644 index 0000000000..c2786e0e5b --- /dev/null +++ b/infra/sync/src/test/java/com/simprints/infra/sync/devicestate/remote/ApiDeviceDataStateTest.kt @@ -0,0 +1,83 @@ +package com.simprints.infra.sync.devicestate.remote + +import com.google.common.truth.Truth.* +import com.simprints.core.api.ApiTimestamp +import com.simprints.core.domain.sync.SyncFailureReason +import com.simprints.core.tools.time.Timestamp +import com.simprints.infra.events.event.domain.models.scope.EventScopeType +import com.simprints.infra.serialization.SimJson +import com.simprints.infra.sync.devicestate.DeviceDataState +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonObject +import org.junit.Test + +class ApiDeviceDataStateTest { + @Test + fun `maps every field of a fully readable state`() { + val api = fullState().toApi() + + assertThat(api).isEqualTo( + ApiDeviceDataState( + scopesToUpload = ApiScopesToUpload( + sessionCount = 1, + eventUpSyncCount = 2, + eventDownSyncCount = 3, + sampleUpSyncCount = 4, + deviceCount = 5, + ), + enrolmentsToUpload = 6, + samplesToUpload = 7, + lastEventSyncAt = ApiTimestamp(unixMs = 100L, isUnixMsTrustworthy = true, elapsedSinceBoot = 10L), + lastEventSyncFailureReason = "BACKEND_MAINTENANCE", + lastSampleSyncAt = ApiTimestamp(unixMs = 200L, isUnixMsTrustworthy = false, elapsedSinceBoot = null), + lastSampleSyncFailureReason = "UNKNOWN", + ), + ) + } + + @Test + fun `missing scope types are reported as zero`() { + val scopes = fullState().copy(pendingScopes = emptyMap()).toApi().scopesToUpload + + assertThat(scopes).isEqualTo(ApiScopesToUpload(0, 0, 0, 0, 0)) + } + + @Test + fun `unreadable sources are omitted from the request rather than sent as zero`() { + val state = DeviceDataState( + projectId = null, + recordCount = null, + pendingScopes = null, + pendingEvents = null, + pendingEnrolments = null, + pendingSamples = null, + lastEventSyncAt = null, + lastEventSyncFailure = null, + lastSampleSyncAt = null, + lastSampleSyncFailure = null, + ) + + val json = SimJson.encodeToJsonElement(ApiDeviceDataState.serializer(), state.toApi()).jsonObject + + assertThat(json).isEqualTo(JsonObject(emptyMap())) + } + + private fun fullState() = DeviceDataState( + projectId = "projectId", + recordCount = 99, + pendingScopes = mapOf( + EventScopeType.SESSION to 1, + EventScopeType.UP_SYNC to 2, + EventScopeType.DOWN_SYNC to 3, + EventScopeType.SAMPLE_UP_SYNC to 4, + EventScopeType.DEVICE to 5, + ), + pendingEvents = 50, + pendingEnrolments = 6, + pendingSamples = 7, + lastEventSyncAt = Timestamp(ms = 100L, isTrustworthy = true, msSinceBoot = 10L), + lastEventSyncFailure = SyncFailureReason.BACKEND_MAINTENANCE, + lastSampleSyncAt = Timestamp(ms = 200L), + lastSampleSyncFailure = SyncFailureReason.UNKNOWN, + ) +} diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/devicestate/worker/DeviceStatusReportWorkerTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/devicestate/worker/DeviceStatusReportWorkerTest.kt new file mode 100644 index 0000000000..0b19e26618 --- /dev/null +++ b/infra/sync/src/test/java/com/simprints/infra/sync/devicestate/worker/DeviceStatusReportWorkerTest.kt @@ -0,0 +1,56 @@ +package com.simprints.infra.sync.devicestate.worker + +import androidx.work.ListenableWorker +import com.google.common.truth.Truth.assertThat +import com.simprints.infra.sync.devicestate.ReportDeviceDataStateUseCase +import com.simprints.testtools.common.coroutines.TestCoroutineRule +import io.mockk.MockKAnnotations +import io.mockk.coEvery +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class DeviceStatusReportWorkerTest { + @get:Rule + val testCoroutineRule = TestCoroutineRule() + + @MockK + private lateinit var reportDeviceDataState: ReportDeviceDataStateUseCase + + private lateinit var worker: DeviceStatusReportWorker + + @Before + fun setUp() { + MockKAnnotations.init(this, relaxed = true) + worker = DeviceStatusReportWorker( + context = mockk(relaxed = true), + params = mockk(relaxed = true), + reportDeviceDataState = reportDeviceDataState, + dispatcher = testCoroutineRule.testCoroutineDispatcher, + ) + } + + @Test + fun `succeeds when the state is reported`() = runTest { + coEvery { reportDeviceDataState() } returns true + + assertThat(worker.doWork()).isEqualTo(ListenableWorker.Result.success()) + } + + @Test + fun `succeeds when there is no signed-in project to report for`() = runTest { + coEvery { reportDeviceDataState() } returns false + + assertThat(worker.doWork()).isEqualTo(ListenableWorker.Result.success()) + } + + @Test + fun `fails without retry when reporting throws`() = runTest { + coEvery { reportDeviceDataState() } throws IllegalStateException("backend down") + + assertThat(worker.doWork()).isEqualTo(ListenableWorker.Result.failure()) + } +}