diff --git a/face/capture/src/main/java/com/simprints/face/capture/usecases/IsUsingAutoCaptureUseCase.kt b/face/capture/src/main/java/com/simprints/face/capture/usecases/IsUsingAutoCaptureUseCase.kt index 367a717ac0..1d8df3b4c9 100644 --- a/face/capture/src/main/java/com/simprints/face/capture/usecases/IsUsingAutoCaptureUseCase.kt +++ b/face/capture/src/main/java/com/simprints/face/capture/usecases/IsUsingAutoCaptureUseCase.kt @@ -3,19 +3,18 @@ package com.simprints.face.capture.usecases import android.content.Context import androidx.preference.PreferenceManager import com.simprints.infra.config.store.models.ProjectConfiguration -import com.simprints.infra.config.store.models.experimental import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import javax.inject.Singleton @Singleton class IsUsingAutoCaptureUseCase @Inject constructor( - @ApplicationContext private val context: Context, + @param:ApplicationContext private val context: Context, ) { private val preference = PreferenceManager.getDefaultSharedPreferences(context) operator fun invoke(projectConfiguration: ProjectConfiguration): Boolean { - val isFeatureEnabled = projectConfiguration.experimental().faceAutoCaptureEnabled + val isFeatureEnabled = projectConfiguration.face?.isAutoCapture ?: false return isFeatureEnabled && isOptionTurnedOnInSettings() } diff --git a/face/capture/src/test/java/com/simprints/face/capture/screens/livefeedback/LiveFeedbackAutoCaptureFragmentViewModelTest.kt b/face/capture/src/test/java/com/simprints/face/capture/screens/livefeedback/LiveFeedbackAutoCaptureFragmentViewModelTest.kt index 3e1cf9c9d8..0700ec2654 100644 --- a/face/capture/src/test/java/com/simprints/face/capture/screens/livefeedback/LiveFeedbackAutoCaptureFragmentViewModelTest.kt +++ b/face/capture/src/test/java/com/simprints/face/capture/screens/livefeedback/LiveFeedbackAutoCaptureFragmentViewModelTest.kt @@ -73,9 +73,7 @@ internal class LiveFeedbackAutoCaptureFragmentViewModelTest { ?.qualityThreshold } returns QUALITY_THRESHOLD every { isUsingAutoCapture.invoke(any()) } returns true - coEvery { - configRepository.getProjectConfiguration().custom - } returns mapOf("singleQualityFallbackRequired" to JsonPrimitive(false)) + every { timeHelper.now() } returnsMany (0..100L).map { Timestamp(it) } justRun { previewFrame.recycle() } val resolveFaceBioSdkUseCase = mockk { diff --git a/face/capture/src/test/java/com/simprints/face/capture/usecases/IsUsingAutoCaptureUseCaseTest.kt b/face/capture/src/test/java/com/simprints/face/capture/usecases/IsUsingAutoCaptureUseCaseTest.kt index 5b2338da16..a14f46b399 100644 --- a/face/capture/src/test/java/com/simprints/face/capture/usecases/IsUsingAutoCaptureUseCaseTest.kt +++ b/face/capture/src/test/java/com/simprints/face/capture/usecases/IsUsingAutoCaptureUseCaseTest.kt @@ -7,7 +7,6 @@ import com.simprints.infra.config.store.models.ProjectConfiguration import io.mockk.* import io.mockk.impl.annotations.MockK import kotlinx.coroutines.test.runTest -import kotlinx.serialization.json.JsonPrimitive import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before @@ -38,8 +37,7 @@ class IsUsingAutoCaptureUseCaseTest { featureEnabled: Boolean, preferenceEnabled: Boolean, ) { - coEvery { projectConfiguration.custom } returns - mapOf("faceAutoCaptureEnabled" to JsonPrimitive(featureEnabled)) + every { projectConfiguration.face?.isAutoCapture } returns featureEnabled every { sharedPreferences.getBoolean("preference_enable_face_auto_capture", true) } returns preferenceEnabled } diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/SettingsFragment.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/SettingsFragment.kt index b5ef297696..5a679ca423 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/SettingsFragment.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/SettingsFragment.kt @@ -62,8 +62,8 @@ internal class SettingsFragment : PreferenceFragmentCompat() { viewModel.generalConfiguration.observe(viewLifecycleOwner) { enableFingerprintSettings(it.modalities.contains(Modality.FINGERPRINT)) } - viewModel.experimentalConfiguration.observe(viewLifecycleOwner) { - showFaceAutoCaptureSetting(isVisible = it.faceAutoCaptureEnabled) + viewModel.faceConfiguration.observe(viewLifecycleOwner) { + showFaceAutoCaptureSetting(it?.isAutoCapture == true) } viewModel.languagePreference.observe(viewLifecycleOwner) { loadSelectedLanguage(it) diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/SettingsViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/SettingsViewModel.kt index b4f70568da..da75feb8c9 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/SettingsViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/SettingsViewModel.kt @@ -3,23 +3,20 @@ package com.simprints.feature.dashboard.settings import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.simprints.core.livedata.LiveDataEvent import com.simprints.core.livedata.LiveDataEventWithContent import com.simprints.core.livedata.send import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.ConfigSyncCache +import com.simprints.infra.config.store.models.FaceConfiguration import com.simprints.infra.config.store.models.GeneralConfiguration -import com.simprints.infra.config.store.models.ProjectConfiguration import com.simprints.infra.config.store.models.SettingsPasswordConfig -import com.simprints.infra.config.store.models.experimental import com.simprints.infra.events.device.DeviceEventTracker import com.simprints.infra.logging.LoggingConstants.CrashReportTag.SETTINGS import com.simprints.infra.logging.Simber import com.simprints.infra.sync.SyncOrchestrator import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject @@ -34,10 +31,9 @@ internal class SettingsViewModel @Inject constructor( get() = _generalConfiguration private val _generalConfiguration = MutableLiveData() - val experimentalConfiguration = configRepository - .observeProjectConfiguration() - .map(ProjectConfiguration::experimental) - .asLiveData(viewModelScope.coroutineContext) + val faceConfiguration: LiveData + get() = _faceConfiguration + private val _faceConfiguration = MutableLiveData() val languagePreference: LiveData get() = _languagePreference @@ -77,12 +73,13 @@ internal class SettingsViewModel @Inject constructor( } private fun load() = viewModelScope.launch { - val configuration = configRepository.getProjectConfiguration().general + val configuration = configRepository.getProjectConfiguration() _sinceConfigLastUpdated.send(configSyncCache.sinceLastUpdateTime()) _languagePreference.postValue(configRepository.getDeviceConfiguration().language) - _generalConfiguration.postValue(configuration) - _settingsLocked.postValue(configuration.settingsPassword) + _generalConfiguration.postValue(configuration.general) + _faceConfiguration.postValue(configuration.face) + _settingsLocked.postValue(configuration.general.settingsPassword) } fun unlockSettings() { diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/SettingsViewModelTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/SettingsViewModelTest.kt index 688b2868d1..da3bc3ca17 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/SettingsViewModelTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/SettingsViewModelTest.kt @@ -7,9 +7,8 @@ import com.simprints.core.domain.common.Modality import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.ConfigSyncCache import com.simprints.infra.config.store.models.DeviceConfiguration -import com.simprints.infra.config.store.models.ExperimentalProjectConfiguration +import com.simprints.infra.config.store.models.FaceConfiguration import com.simprints.infra.config.store.models.GeneralConfiguration -import com.simprints.infra.config.store.models.ProjectConfiguration import com.simprints.infra.config.store.models.SettingsPasswordConfig import com.simprints.infra.events.device.DeviceEventTracker import com.simprints.infra.sync.SyncOrchestrator @@ -18,7 +17,6 @@ import io.mockk.* import io.mockk.impl.annotations.MockK 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 @@ -40,6 +38,13 @@ class SettingsViewModelTest { settingsPassword = SettingsPasswordConfig.Locked("1234"), ) + private val faceConfiguration = FaceConfiguration( + allowedSDKs = listOf(), + isAutoCapture = true, + rankOne = null, + simFace = null, + ) + @MockK private lateinit var configRepository: ConfigRepository @@ -59,6 +64,7 @@ class SettingsViewModelTest { MockKAnnotations.init(this, relaxed = true) coEvery { configRepository.getProjectConfiguration().general } returns generalConfiguration + coEvery { configRepository.getProjectConfiguration().face } returns faceConfiguration coEvery { configRepository.getDeviceConfiguration().language } returns LANGUAGE coEvery { configSyncCache.sinceLastUpdateTime() } returnsMany listOf( @@ -71,36 +77,23 @@ class SettingsViewModelTest { viewModel = SettingsViewModel(configRepository, syncOrchestrator, configSyncCache, deviceEventTracker) } - @Test - fun `experimentalConfiguration live data should follow the project experimental configuration`() = runTest { - val experimentalConfig1 = mapOf("key1" to JsonPrimitive("value1")) - val experimentalConfig2 = mapOf("key2" to JsonPrimitive("value2")) - - coEvery { configRepository.observeProjectConfiguration() } returns flowOf( - mockk(relaxed = true) { - every { custom } returns experimentalConfig1 - }, - mockk(relaxed = true) { - every { custom } returns experimentalConfig2 - }, - ) - viewModel = SettingsViewModel(configRepository, syncOrchestrator, configSyncCache, deviceEventTracker) - - assertThat(viewModel.experimentalConfiguration.test().valueHistory()) - .isEqualTo( - listOf( - ExperimentalProjectConfiguration(experimentalConfig1), - ExperimentalProjectConfiguration(experimentalConfig2), - ), - ) - } - @Test fun `should initialize the live data correctly`() { assertThat(viewModel.generalConfiguration.value).isEqualTo(generalConfiguration) assertThat(viewModel.languagePreference.value).isEqualTo(LANGUAGE) assertThat(viewModel.settingsLocked.value).isEqualTo(SettingsPasswordConfig.Locked("1234")) assertThat(viewModel.sinceConfigLastUpdated.value?.peekContent()).isEqualTo(LAST_UPDATED) + assertThat(viewModel.faceConfiguration.value).isEqualTo(faceConfiguration) + } + + @Test + fun `should initialize the live data correctly when no face configured`() { + coEvery { configRepository.getProjectConfiguration().face } returns null + + // Recreating to set the new value + viewModel = SettingsViewModel(configRepository, syncOrchestrator, configSyncCache, deviceEventTracker) + + assertThat(viewModel.faceConfiguration.value).isNull() } @Test diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/ConfigStoreModule.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/ConfigStoreModule.kt index f250857c63..02911861b4 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/ConfigStoreModule.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/ConfigStoreModule.kt @@ -8,6 +8,7 @@ import com.simprints.infra.config.store.local.ConfigLocalDataSource import com.simprints.infra.config.store.local.ConfigLocalDataSourceImpl import com.simprints.infra.config.store.local.migrations.DeviceConfigSharedPrefsMigration import com.simprints.infra.config.store.local.migrations.ProjectConfigDevicesScopeBatchMigration +import com.simprints.infra.config.store.local.migrations.ProjectConfigFaceAutoCaptureConfigMigration import com.simprints.infra.config.store.local.migrations.ProjectConfigFaceBioSdkMigration import com.simprints.infra.config.store.local.migrations.ProjectConfigFaceEmptyVersionMigration import com.simprints.infra.config.store.local.migrations.ProjectConfigFaceSdkQualityThresholdMigration @@ -82,6 +83,7 @@ object DataStoreModule { projectConfigSampleUploadMigration: ProjectConfigSampleUploadMigration, projectConfigDownSyncConfigMigration: ProjectConfigSimprintsSyncConfigMigration, projectConfigDevicesScopeBatchMigration: ProjectConfigDevicesScopeBatchMigration, + projectConfigFaceAutoCaptureConfigMigration: ProjectConfigFaceAutoCaptureConfigMigration, ): DataStore = DataStoreFactory.create( serializer = ProjectConfigurationSerializer, produceFile = { appContext.dataStoreFile(PROJECT_CONFIG_DATA_STORE_FILE_NAME) }, @@ -97,6 +99,7 @@ object DataStoreModule { projectConfigSampleUploadMigration, projectConfigDownSyncConfigMigration, projectConfigDevicesScopeBatchMigration, + projectConfigFaceAutoCaptureConfigMigration, ), ) diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/migrations/ProjectConfigFaceAutoCaptureConfigMigration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/migrations/ProjectConfigFaceAutoCaptureConfigMigration.kt new file mode 100644 index 0000000000..acbdb8dffa --- /dev/null +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/migrations/ProjectConfigFaceAutoCaptureConfigMigration.kt @@ -0,0 +1,49 @@ +package com.simprints.infra.config.store.local.migrations + +import androidx.datastore.core.DataMigration +import com.simprints.infra.config.store.local.models.ProtoProjectConfiguration +import com.simprints.infra.logging.LoggingConstants.CrashReportTag.MIGRATION +import com.simprints.infra.logging.Simber +import com.simprints.infra.serialization.SimJson +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.booleanOrNull +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import javax.inject.Inject + +/** + * Can be removed once all the devices have been updated to 2026.3.0 + */ +class ProjectConfigFaceAutoCaptureConfigMigration @Inject constructor() : DataMigration { + override suspend fun cleanUp() { + Simber.i("Migration of face auto-capture flag is complete", tag = MIGRATION) + } + + override suspend fun shouldMigrate(currentData: ProtoProjectConfiguration) = with(currentData) { + hasFace() && !face.hasIsAutoCapture() && hasCustomJson() && customJson.contains("faceAutoCaptureEnabled") + } + + override suspend fun migrate(currentData: ProtoProjectConfiguration): ProtoProjectConfiguration { + Simber.i("Start migration of face auto-capture flag", tag = MIGRATION) + + val isAutoCapture = try { + SimJson + .parseToJsonElement(currentData.customJson) + .jsonObject["faceAutoCaptureEnabled"] + ?.jsonPrimitive + ?.booleanOrNull ?: false + } catch (e: SerializationException) { + Simber.e("Failed to parse custom config", e, tag = MIGRATION) + false + } + + return currentData + .toBuilder() + .setFace( + currentData.face + .toBuilder() + .setIsAutoCapture(isAutoCapture) + .build(), + ).build() + } +} diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/migrations/models/OldProjectConfig.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/migrations/models/OldProjectConfig.kt index ed001c01dd..f1e001a39e 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/migrations/models/OldProjectConfig.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/migrations/models/OldProjectConfig.kt @@ -106,6 +106,7 @@ internal data class OldProjectConfig( } else { FaceConfiguration( allowedSDKs = listOf(ModalitySdkType.RANK_ONE), + isAutoCapture = false, rankOne = FaceConfiguration.FaceSdkConfiguration( nbOfImagesToCapture = faceNbOfFramesCaptured?.toIntOrNull() ?: DEFAULT_FACE_FRAMES_TO_CAPTURE, qualityThreshold = faceQualityThreshold.toFloat(), diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/FaceConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/FaceConfiguration.kt index 8b935ccd82..0256be24a2 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/FaceConfiguration.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/FaceConfiguration.kt @@ -8,6 +8,7 @@ import com.simprints.infra.config.store.models.ModalitySdkType internal fun FaceConfiguration.toProto(): ProtoFaceConfiguration = ProtoFaceConfiguration .newBuilder() .addAllAllowedSdks(allowedSDKs.map { it.toProtoFaceConfiguration() }) + .setIsAutoCapture(isAutoCapture) .also { if (rankOne != null) it.rankOne = rankOne.toProto() } .also { if (simFace != null) it.simFace = simFace.toProto() } .build() @@ -32,6 +33,7 @@ internal fun FaceConfiguration.ImageSavingStrategy.toProto(): ProtoFaceConfigura internal fun ProtoFaceConfiguration.toDomain(): FaceConfiguration = FaceConfiguration( allowedSDKs = allowedSdksList.map { it.toDomain() }, + isAutoCapture = isAutoCapture, rankOne = if (hasRankOne()) rankOne.toDomain() else null, simFace = if (hasSimFace()) simFace.toDomain() else null, ) 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 3185844603..28147c0d8e 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 @@ -21,13 +21,6 @@ data class ExperimentalProjectConfiguration( ?.booleanOrNull .let { it == true } - val faceAutoCaptureEnabled: Boolean - get() = customConfig - ?.get(FACE_AUTO_CAPTURE_ENABLED) - ?.jsonPrimitive - ?.booleanOrNull - .let { it == true } - val faceAutoCaptureImagingDurationMillis: Long get() = customConfig ?.get(FACE_AUTO_CAPTURE_IMAGING_DURATION_MILLIS) @@ -182,7 +175,6 @@ data class ExperimentalProjectConfiguration( companion object { internal const val DISABLE_SUBJECT_POOL_VALIDATION = "disableSubjectPoolValidation" - internal const val FACE_AUTO_CAPTURE_ENABLED = "faceAutoCaptureEnabled" internal const val FACE_AUTO_CAPTURE_IMAGING_DURATION_MILLIS = "faceAutoCaptureImagingDurationMillis" internal const val RECORDS_DB_MIGRATION_FROM_REALM_TO_ROOM_ENABLED = "recordsDbMigrationFromRealmEnabled" diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/models/FaceConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/FaceConfiguration.kt index 26c811c2c6..ed447713c7 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/models/FaceConfiguration.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/FaceConfiguration.kt @@ -4,6 +4,7 @@ import com.simprints.core.domain.common.AgeGroup data class FaceConfiguration( val allowedSDKs: List, + val isAutoCapture: Boolean, val rankOne: FaceSdkConfiguration?, val simFace: FaceSdkConfiguration?, ) { diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiFaceConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiFaceConfiguration.kt index f6b2216ede..5aa931d023 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiFaceConfiguration.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiFaceConfiguration.kt @@ -10,11 +10,13 @@ import kotlinx.serialization.Serializable @Serializable internal data class ApiFaceConfiguration( val allowedSDKs: List, + val isAutoCapture: Boolean = false, val rankOne: ApiFaceSdkConfiguration? = null, val simFace: ApiFaceSdkConfiguration? = null, ) { fun toDomain(): FaceConfiguration = FaceConfiguration( allowedSDKs = allowedSDKs.map { it.toDomain() }, + isAutoCapture = isAutoCapture, rankOne = rankOne?.toDomain(), simFace = simFace?.toDomain(), ) diff --git a/infra/config-store/src/main/proto/project_config.proto b/infra/config-store/src/main/proto/project_config.proto index cf018c7198..d17ba87eeb 100644 --- a/infra/config-store/src/main/proto/project_config.proto +++ b/infra/config-store/src/main/proto/project_config.proto @@ -68,6 +68,8 @@ message ProtoFaceConfiguration { optional ProtoFaceSdkConfiguration rank_one = 6; optional ProtoFaceSdkConfiguration sim_face = 7; + optional bool is_auto_capture = 8; + enum ProtoBioSdk { RANK_ONE = 0; SIM_FACE = 1; diff --git a/infra/config-store/src/test/java/com/simprints/infra/config/store/local/migrations/ProjectConfigFaceAutoCaptureConfigMigrationTest.kt b/infra/config-store/src/test/java/com/simprints/infra/config/store/local/migrations/ProjectConfigFaceAutoCaptureConfigMigrationTest.kt new file mode 100644 index 0000000000..222a22fee6 --- /dev/null +++ b/infra/config-store/src/test/java/com/simprints/infra/config/store/local/migrations/ProjectConfigFaceAutoCaptureConfigMigrationTest.kt @@ -0,0 +1,116 @@ +package com.simprints.infra.config.store.local.migrations + +import com.google.common.truth.Truth.assertThat +import com.simprints.infra.config.store.local.models.ProtoFaceConfiguration +import com.simprints.infra.config.store.local.models.ProtoProjectConfiguration +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class ProjectConfigFaceAutoCaptureConfigMigrationTest { + private val migration = ProjectConfigFaceAutoCaptureConfigMigration() + + @Test + fun `should not migrate if face is missing`() = runTest { + val currentData = ProtoProjectConfiguration.newBuilder().build() + + assertThat(migration.shouldMigrate(currentData)).isFalse() + } + + @Test + fun `should not migrate if face already has auto capture`() = runTest { + val currentData = projectConfig( + customJson = """{"faceAutoCaptureEnabled":true}""", + hasAutoCapture = true, + isAutoCapture = true, + ) + + assertThat(migration.shouldMigrate(currentData)).isFalse() + } + + @Test + fun `should not migrate if face already has disabled auto capture`() = runTest { + val currentData = projectConfig( + customJson = """{"faceAutoCaptureEnabled":false}""", + hasAutoCapture = true, + isAutoCapture = false, + ) + + assertThat(migration.shouldMigrate(currentData)).isFalse() + } + + @Test + fun `should not migrate if custom json is missing`() = runTest { + val currentData = projectConfig() + + assertThat(migration.shouldMigrate(currentData)).isFalse() + } + + @Test + fun `should not migrate if custom json does not contain auto capture flag`() = runTest { + val currentData = projectConfig(customJson = """{"somethingElse":true}""") + + assertThat(migration.shouldMigrate(currentData)).isFalse() + } + + @Test + fun `should migrate if custom json contains auto capture flag`() = runTest { + val currentData = projectConfig(customJson = """{"faceAutoCaptureEnabled":true}""") + + assertThat(migration.shouldMigrate(currentData)).isTrue() + } + + @Test + fun `migrate should set auto capture to true when custom json flag is true`() = runTest { + val currentData = projectConfig(customJson = """{"faceAutoCaptureEnabled":true}""") + val migratedData = migration.migrate(currentData) + + assertThat(migratedData.face.isAutoCapture).isTrue() + } + + @Test + fun `migrate should set auto capture to false when custom json flag is false`() = runTest { + val currentData = projectConfig(customJson = """{"faceAutoCaptureEnabled":false}""") + + val migratedData = migration.migrate(currentData) + + assertThat(migratedData.face.isAutoCapture).isFalse() + } + + @Test + fun `migrate should default auto capture to false when custom json is malformed`() = runTest { + val currentData = projectConfig(customJson = """{"faceAutoCaptureEnabled":true""") + + val migratedData = migration.migrate(currentData) + + assertThat(migratedData.face.isAutoCapture).isFalse() + } + + private fun projectConfig( + customJson: String? = null, + hasAutoCapture: Boolean = false, + isAutoCapture: Boolean = false, + ): ProtoProjectConfiguration { + val faceBuilder = ProtoFaceConfiguration + .newBuilder() + .setRankOne( + ProtoFaceConfiguration.ProtoFaceSdkConfiguration + .newBuilder() + .setVersion("1.0") + .build(), + ) + + if (hasAutoCapture) { + faceBuilder.setIsAutoCapture(isAutoCapture) + } + + val projectBuilder = ProtoProjectConfiguration + .newBuilder() + .setFace(faceBuilder.build()) + + if (customJson != null) { + projectBuilder.setCustomJson(customJson) + } + + return projectBuilder.build() + } +} diff --git a/infra/config-store/src/test/java/com/simprints/infra/config/store/local/migrations/ProjectConfigSharedPrefsMigrationTest.kt b/infra/config-store/src/test/java/com/simprints/infra/config/store/local/migrations/ProjectConfigSharedPrefsMigrationTest.kt index 2aca1f32ec..8a0394b5c6 100644 --- a/infra/config-store/src/test/java/com/simprints/infra/config/store/local/migrations/ProjectConfigSharedPrefsMigrationTest.kt +++ b/infra/config-store/src/test/java/com/simprints/infra/config/store/local/migrations/ProjectConfigSharedPrefsMigrationTest.kt @@ -733,6 +733,7 @@ class ProjectConfigSharedPrefsMigrationTest { private val PROTO_FACE_CONFIGURATION = ProtoFaceConfiguration .newBuilder() + .setIsAutoCapture(false) .addAllowedSdks(ProtoFaceConfiguration.ProtoBioSdk.RANK_ONE) .setRankOne( ProtoFaceConfiguration.ProtoFaceSdkConfiguration @@ -754,6 +755,7 @@ class ProjectConfigSharedPrefsMigrationTest { private val PROTO_FACE_DEFAULT_CONFIGURATION = ProtoFaceConfiguration .newBuilder() + .setIsAutoCapture(false) .addAllowedSdks(ProtoFaceConfiguration.ProtoBioSdk.RANK_ONE) .setRankOne( ProtoFaceConfiguration.ProtoFaceSdkConfiguration 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 12038c119c..c10d2c46e5 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 @@ -3,7 +3,6 @@ package com.simprints.infra.config.store.models import com.google.common.truth.Truth.* import com.simprints.infra.config.store.models.ExperimentalProjectConfiguration.Companion.ALLOW_CONFIRMING_GUIDS_NOT_IN_CALLBACK import com.simprints.infra.config.store.models.ExperimentalProjectConfiguration.Companion.DISABLE_SUBJECT_POOL_VALIDATION -import com.simprints.infra.config.store.models.ExperimentalProjectConfiguration.Companion.FACE_AUTO_CAPTURE_ENABLED import com.simprints.infra.config.store.models.ExperimentalProjectConfiguration.Companion.FACE_AUTO_CAPTURE_IMAGING_DURATION_MILLIS import com.simprints.infra.config.store.models.ExperimentalProjectConfiguration.Companion.FACE_AUTO_CAPTURE_IMAGING_DURATION_MILLIS_DEFAULT import com.simprints.infra.config.store.models.ExperimentalProjectConfiguration.Companion.FACE_AUTO_CAPTURE_IMAGING_DURATION_MILLIS_MAX @@ -59,22 +58,6 @@ internal class ExperimentalProjectConfigurationTest { } } - @Test - fun `check face auto capture flag correctly`() { - mapOf( - // Value not present - emptyMap() to false, - // Value not boolean - mapOf(FACE_AUTO_CAPTURE_ENABLED to JsonPrimitive(1)) to false, - // Value present and FALSE - mapOf(FACE_AUTO_CAPTURE_ENABLED to JsonPrimitive(false)) to false, - // Value present and TRUE - mapOf(FACE_AUTO_CAPTURE_ENABLED to JsonPrimitive(true)) to true, - ).forEach { (config, result) -> - assertThat(ExperimentalProjectConfiguration(config).faceAutoCaptureEnabled).isEqualTo(result) - } - } - @Test fun `check face auto capture imaging duration flag correctly`() { mapOf( diff --git a/infra/config-store/src/test/java/com/simprints/infra/config/store/models/FaceConfigurationTest.kt b/infra/config-store/src/test/java/com/simprints/infra/config/store/models/FaceConfigurationTest.kt index b6e9f23bad..e21cd49f0d 100644 --- a/infra/config-store/src/test/java/com/simprints/infra/config/store/models/FaceConfigurationTest.kt +++ b/infra/config-store/src/test/java/com/simprints/infra/config/store/models/FaceConfigurationTest.kt @@ -22,6 +22,7 @@ class FaceConfigurationTest { private fun createConfiguration(): FaceConfiguration = FaceConfiguration( allowedSDKs = listOf(ModalitySdkType.RANK_ONE), + isAutoCapture = true, rankOne = FaceConfiguration.FaceSdkConfiguration( nbOfImagesToCapture = 2, qualityThreshold = 0.5f, diff --git a/infra/config-store/src/test/java/com/simprints/infra/config/store/testtools/Models.kt b/infra/config-store/src/test/java/com/simprints/infra/config/store/testtools/Models.kt index 8b3b3b9feb..0512c3634d 100644 --- a/infra/config-store/src/test/java/com/simprints/infra/config/store/testtools/Models.kt +++ b/infra/config-store/src/test/java/com/simprints/infra/config/store/testtools/Models.kt @@ -172,6 +172,7 @@ internal val faceSdkConfiguration = FaceSdkConfiguration( internal val apiFaceConfiguration = ApiFaceConfiguration( allowedSDKs = listOf(ApiFaceConfiguration.BioSdk.RANK_ONE), + isAutoCapture = true, rankOne = ApiFaceSdkConfiguration( nbOfImagesToCapture = 2, qualityThreshold = -1f, @@ -193,11 +194,13 @@ internal val apiFaceConfiguration = ApiFaceConfiguration( ) internal val faceConfiguration = FaceConfiguration( allowedSDKs = listOf(ModalitySdkType.RANK_ONE), + isAutoCapture = true, rankOne = faceSdkConfiguration, simFace = faceSdkConfiguration, ) internal val protoFaceConfiguration = ProtoFaceConfiguration .newBuilder() + .setIsAutoCapture(true) .addAllowedSdks(ProtoFaceConfiguration.ProtoBioSdk.RANK_ONE) .setRankOne( ProtoFaceConfiguration.ProtoFaceSdkConfiguration diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/config/testtools/Models.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/testtools/Models.kt index d517b76ce0..620a009391 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/config/testtools/Models.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/testtools/Models.kt @@ -55,6 +55,7 @@ internal val faceConfiguration = decisionPolicy = decisionPolicy, version = "1.0", ), + isAutoCapture = false, simFace = FaceConfiguration.FaceSdkConfiguration( nbOfImagesToCapture = 2, qualityThreshold = -1f,