Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.simprints.feature.selectagegroup.SelectSubjectAgeGroupContract
import com.simprints.feature.selectsubject.SelectSubjectContract
import com.simprints.feature.setup.SetupContract
import com.simprints.feature.validatepool.ValidateSubjectPoolContract
import com.simprints.feature.validatepool.ValidateSubjectPoolFragmentParams
import com.simprints.fingerprint.capture.FingerprintCaptureContract
import com.simprints.infra.config.store.models.ProjectConfiguration
import com.simprints.infra.config.store.models.allowedAgeRanges
Expand Down Expand Up @@ -65,6 +66,7 @@ internal class BuildStepsUseCase @Inject constructor(
biometricDataSource = action.biometricDataSource,
callerPackageName = action.actionIdentifier.callerPackageName,
projectConfiguration = projectConfiguration,
mode = ValidateSubjectPoolFragmentParams.ValidationMode.IDENTIFICATION,
),
buildAgeSelectionStepIfNeeded(action, projectConfiguration),
buildConsentStepIfNeeded(ConsentType.IDENTIFY, projectConfiguration),
Expand Down Expand Up @@ -191,16 +193,22 @@ internal class BuildStepsUseCase @Inject constructor(
flowType = enrolFlowType,
)
}

val matcherSteps = if (projectConfiguration.general.duplicateBiometricEnrolmentCheck) {
buildMatcherSteps(
projectConfiguration,
enrolFlowType,
resolvedAgeGroup,
buildMatcherSubjectQuery(projectConfiguration, action),
BiometricDataSource.fromString(
action.biometricDataSource,
action.actionIdentifier.callerPackageName,
),
val enrolmentRecordQuery = buildMatcherSubjectQuery(projectConfiguration, action)

buildValidateIdPoolStep(
enrolmentRecordQuery = enrolmentRecordQuery,
biometricDataSource = action.biometricDataSource,
callerPackageName = action.actionIdentifier.callerPackageName,
projectConfiguration = projectConfiguration,
mode = ValidateSubjectPoolFragmentParams.ValidationMode.ENROL_PLUS,
) + buildMatcherSteps(
projectConfiguration = projectConfiguration,
flowType = enrolFlowType,
ageGroup = resolvedAgeGroup,
enrolmentRecordQuery = enrolmentRecordQuery,
biometricDataSource = BiometricDataSource.fromString(action.biometricDataSource, action.actionIdentifier.callerPackageName),
)
} else {
emptyList()
Expand Down Expand Up @@ -347,7 +355,10 @@ internal class BuildStepsUseCase @Inject constructor(
biometricDataSource: String,
callerPackageName: String,
projectConfiguration: ProjectConfiguration,
) = if (projectConfiguration.experimental().idPoolValidationEnabled) {
mode: ValidateSubjectPoolFragmentParams.ValidationMode,
) = if (projectConfiguration.experimental().disableSubjectPoolValidation) {
emptyList()
} else {
when (
BiometricDataSource.fromString(
value = biometricDataSource,
Expand All @@ -359,14 +370,12 @@ internal class BuildStepsUseCase @Inject constructor(
id = StepId.VALIDATE_ID_POOL,
navigationActionId = R.id.action_orchestratorFragment_to_validateSubjectPool,
destinationId = ValidateSubjectPoolContract.DESTINATION,
params = ValidateSubjectPoolContract.getParams(enrolmentRecordQuery),
params = ValidateSubjectPoolContract.getParams(enrolmentRecordQuery, mode),
),
)

is BiometricDataSource.CommCare -> emptyList()
}
} else {
emptyList()
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import com.simprints.feature.selectsubject.SelectSubjectParams
import com.simprints.feature.selectsubject.SelectSubjectResult
import com.simprints.feature.setup.SetupResult
import com.simprints.feature.validatepool.ValidateSubjectPoolFragmentParams
import com.simprints.feature.validatepool.ValidateSubjectPoolFragmentParams.ValidationMode
import com.simprints.feature.validatepool.ValidateSubjectPoolResult
import com.simprints.fingerprint.capture.FingerprintCaptureParams
import com.simprints.infra.config.store.models.ModalitySdkType
Expand Down Expand Up @@ -320,7 +321,7 @@ class OrchestratorCacheIntegrationTest {
id = StepId.VALIDATE_ID_POOL,
navigationActionId = 5,
destinationId = 6,
params = ValidateSubjectPoolFragmentParams(EnrolmentRecordQuery()),
params = ValidateSubjectPoolFragmentParams(EnrolmentRecordQuery(), ValidationMode.IDENTIFICATION),
status = StepStatus.COMPLETED,
result = ValidateSubjectPoolResult(true),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,33 @@ class BuildStepsUseCaseTest {

val steps = useCase.build(action, projectConfiguration, enrolmentSubjectId, cachedScannedCredentialResult)

assertStepOrder(
steps,
StepId.SETUP,
StepId.CONSENT,
StepId.FINGERPRINT_CAPTURE,
StepId.FINGERPRINT_CAPTURE,
StepId.FACE_CAPTURE,
StepId.VALIDATE_ID_POOL,
StepId.FINGERPRINT_MATCHER,
StepId.FINGERPRINT_MATCHER,
StepId.FACE_MATCHER,
)
}

@Test
fun `build - enrol action - no age restriction - duplicate check - pool validation disabled - returns expected steps`() = runTest {
val projectConfiguration = mockCommonProjectConfiguration()
every { projectConfiguration.general.duplicateBiometricEnrolmentCheck } returns true

every { projectConfiguration.custom } returns
mapOf("disableSubjectPoolValidation" to JsonPrimitive(true))

val action = mockk<ActionRequest.EnrolActionRequest>(relaxed = true)
every { action.getSubjectAgeIfAvailable() } returns null

val steps = useCase.build(action, projectConfiguration, enrolmentSubjectId, cachedScannedCredentialResult)

assertStepOrder(
steps,
StepId.SETUP,
Expand Down Expand Up @@ -187,6 +214,7 @@ class BuildStepsUseCaseTest {
StepId.FINGERPRINT_CAPTURE,
StepId.FINGERPRINT_CAPTURE,
StepId.FACE_CAPTURE,
StepId.VALIDATE_ID_POOL,
StepId.FACE_MATCHER,
)
}
Expand All @@ -203,6 +231,7 @@ class BuildStepsUseCaseTest {
assertStepOrder(
steps,
StepId.SETUP,
StepId.VALIDATE_ID_POOL,
StepId.CONSENT,
StepId.FINGERPRINT_CAPTURE,
StepId.FINGERPRINT_CAPTURE,
Expand All @@ -226,27 +255,27 @@ class BuildStepsUseCaseTest {
assertStepOrder(
steps,
StepId.SETUP,
StepId.VALIDATE_ID_POOL,
StepId.CONSENT,
StepId.FACE_CAPTURE,
StepId.FACE_MATCHER,
)
}

@Test
fun `build - identify action - no age restriction - id pool validation - returns expected steps`() = runTest {
fun `build - identify action - no age restriction - pool validation disabled - returns expected steps`() = runTest {
val projectConfiguration = mockCommonProjectConfiguration()

val action = mockk<ActionRequest.IdentifyActionRequest>(relaxed = true)
every { action.getSubjectAgeIfAvailable() } returns null
every { projectConfiguration.custom } returns
mapOf("validateIdentificationPool" to JsonPrimitive(true))
mapOf("disableSubjectPoolValidation" to JsonPrimitive(true))

val steps = useCase.build(action, projectConfiguration, enrolmentSubjectId, cachedScannedCredentialResult)

assertStepOrder(
steps,
StepId.SETUP,
StepId.VALIDATE_ID_POOL,
StepId.CONSENT,
StepId.FINGERPRINT_CAPTURE,
StepId.FINGERPRINT_CAPTURE,
Expand Down Expand Up @@ -392,6 +421,7 @@ class BuildStepsUseCaseTest {
assertStepOrder(
steps,
StepId.SETUP,
StepId.VALIDATE_ID_POOL,
StepId.SELECT_SUBJECT_AGE,
StepId.CONSENT,
)
Expand Down Expand Up @@ -463,6 +493,7 @@ class BuildStepsUseCaseTest {
StepId.FINGERPRINT_CAPTURE,
StepId.FINGERPRINT_CAPTURE,
StepId.FACE_CAPTURE,
StepId.VALIDATE_ID_POOL,
StepId.FINGERPRINT_MATCHER,
StepId.FINGERPRINT_MATCHER,
StepId.FACE_MATCHER,
Expand All @@ -485,6 +516,7 @@ class BuildStepsUseCaseTest {
assertStepOrder(
steps,
StepId.SETUP,
StepId.VALIDATE_ID_POOL,
StepId.CONSENT,
StepId.FINGERPRINT_CAPTURE,
StepId.FINGERPRINT_CAPTURE,
Expand Down Expand Up @@ -662,6 +694,7 @@ class BuildStepsUseCaseTest {
StepId.FINGERPRINT_CAPTURE,
StepId.FINGERPRINT_CAPTURE,
StepId.FACE_CAPTURE,
StepId.VALIDATE_ID_POOL,
StepId.FINGERPRINT_MATCHER,
StepId.FINGERPRINT_MATCHER,
StepId.FACE_MATCHER,
Expand Down Expand Up @@ -817,6 +850,7 @@ class BuildStepsUseCaseTest {
assertStepOrder(
steps,
StepId.SETUP,
StepId.VALIDATE_ID_POOL,
StepId.CONSENT,
StepId.FINGERPRINT_CAPTURE,
StepId.FINGERPRINT_CAPTURE,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.simprints.feature.validatepool

import com.simprints.feature.validatepool.ValidateSubjectPoolFragmentParams.ValidationMode
import com.simprints.infra.enrolment.records.repository.domain.models.EnrolmentRecordQuery

object ValidateSubjectPoolContract {
val DESTINATION = R.id.validateSubjectPoolFragment

fun getParams(enrolmentRecordQuery: EnrolmentRecordQuery) = ValidateSubjectPoolFragmentParams(enrolmentRecordQuery)
fun getParams(
enrolmentRecordQuery: EnrolmentRecordQuery,
mode: ValidationMode = ValidationMode.IDENTIFICATION,
) = ValidateSubjectPoolFragmentParams(enrolmentRecordQuery, mode)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,10 @@ import kotlinx.serialization.Serializable
@SerialName("ValidateSubjectPoolFragmentParams")
data class ValidateSubjectPoolFragmentParams(
val enrolmentRecordQuery: EnrolmentRecordQuery,
) : StepParams
val mode: ValidationMode,
) : StepParams {
Comment thread
luhmirin-s marked this conversation as resolved.
enum class ValidationMode {
IDENTIFICATION,
ENROL_PLUS,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ internal class ValidateSubjectPoolFragment : Fragment(R.layout.fragment_validate

binding.validationActionsClose.setOnClickListener { finishWithResult(false) }
binding.validationActionsContinue.setOnClickListener { finishWithResult(true) }
binding.validationActionsSync.setOnClickListener { viewModel.startSync(params.enrolmentRecordQuery) }
binding.validationActionsSync.setOnClickListener { viewModel.startSync(params.enrolmentRecordQuery, params.mode) }

viewModel.checkIdentificationPool(params.enrolmentRecordQuery)
viewModel.checkIdentificationPool(params.enrolmentRecordQuery, params.mode)
}

private fun renderState(state: ValidateSubjectPoolState) = when (state) {
Expand All @@ -58,7 +58,7 @@ internal class ValidateSubjectPoolFragment : Fragment(R.layout.fragment_validate
showValidating = true,
)

ValidateSubjectPoolState.UserMismatch -> setViews(
ValidateSubjectPoolState.AttendantMismatch -> setViews(
descriptionRes = IDR.string.id_pool_validation_user_mismatch_message,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ internal sealed class ValidateSubjectPoolState {

data object Success : ValidateSubjectPoolState()

data object UserMismatch : ValidateSubjectPoolState()
data object AttendantMismatch : ValidateSubjectPoolState()

data object ModuleMismatch : ValidateSubjectPoolState()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import androidx.lifecycle.viewModelScope
import com.simprints.core.livedata.LiveDataEventWithContent
import com.simprints.core.livedata.send
import com.simprints.core.tools.time.TimeHelper
import com.simprints.feature.validatepool.ValidateSubjectPoolFragmentParams.ValidationMode
import com.simprints.feature.validatepool.usecase.HasRecordsUseCase
import com.simprints.feature.validatepool.usecase.IsModuleIdNotSyncedUseCase
import com.simprints.feature.validatepool.usecase.ShouldSuggestSyncUseCase
Expand All @@ -29,7 +30,7 @@ internal class ValidateSubjectPoolViewModel @Inject constructor(
private val timeHelper: TimeHelper,
) : ViewModel() {
private lateinit var cachedQuery: EnrolmentRecordQuery

private lateinit var cachedMode: ValidationMode
val state: LiveData<LiveDataEventWithContent<ValidateSubjectPoolState>>
get() = _state
private var _state = MutableLiveData<LiveDataEventWithContent<ValidateSubjectPoolState>>()
Expand All @@ -49,7 +50,7 @@ internal class ValidateSubjectPoolViewModel @Inject constructor(
syncStatusFlow.collect { syncState ->
if (syncState.isSyncReporterCompleted()) {
isSyncing = false
checkIdentificationPool(cachedQuery)
checkIdentificationPool(cachedQuery, cachedMode)
}
}
}
Expand All @@ -62,7 +63,10 @@ internal class ValidateSubjectPoolViewModel @Inject constructor(
}
}

fun checkIdentificationPool(enrolmentRecordQuery: EnrolmentRecordQuery) = viewModelScope.launch {
fun checkIdentificationPool(
enrolmentRecordQuery: EnrolmentRecordQuery,
mode: ValidationMode,
) = viewModelScope.launch {
if (isSyncing) {
// In case of configuration change while syncing, we want to show the sync in progress state instead of default state
_state.send(ValidateSubjectPoolState.SyncInProgress)
Expand All @@ -71,18 +75,35 @@ internal class ValidateSubjectPoolViewModel @Inject constructor(
_state.send(ValidateSubjectPoolState.Validating)

val validationState = when {
// Check that any record are available as a shortcut
hasRecords(enrolmentRecordQuery) -> ValidateSubjectPoolState.Success
Comment thread
luhmirin-s marked this conversation as resolved.
enrolmentRecordQuery.attendantId != null && hasRecords(EnrolmentRecordQuery()) -> ValidateSubjectPoolState.UserMismatch
enrolmentRecordQuery.moduleId?.let { isModuleIdNotSynced(it) } == true -> ValidateSubjectPoolState.ModuleMismatch
// Check attendant ID or module ID based on the ID configuration in the project
hasInvalidAttendantId(enrolmentRecordQuery) -> ValidateSubjectPoolState.AttendantMismatch
hasInvalidModuleId(enrolmentRecordQuery) -> ValidateSubjectPoolState.ModuleMismatch
// Check for stale sync for a better result
shouldSuggestSync() -> ValidateSubjectPoolState.RequiresSync
else -> ValidateSubjectPoolState.PoolEmpty
// For Identification requests, no-records is a fail state
mode == ValidationMode.IDENTIFICATION -> ValidateSubjectPoolState.PoolEmpty
// For Enrol+, no-records is a success state
else -> ValidateSubjectPoolState.Success
}

_state.send(validationState)
}

fun startSync(enrolmentRecordQuery: EnrolmentRecordQuery) = viewModelScope.launch {
private suspend fun hasInvalidModuleId(enrolmentRecordQuery: EnrolmentRecordQuery): Boolean =
enrolmentRecordQuery.moduleId?.let { isModuleIdNotSynced(it) } == true

private suspend fun hasInvalidAttendantId(enrolmentRecordQuery: EnrolmentRecordQuery): Boolean =
enrolmentRecordQuery.attendantId != null && hasRecords(EnrolmentRecordQuery())

fun startSync(
enrolmentRecordQuery: EnrolmentRecordQuery,
mode: ValidationMode,
) = viewModelScope.launch {
cachedQuery = enrolmentRecordQuery
cachedMode = mode

_state.send(ValidateSubjectPoolState.SyncInProgress)
isSyncing = true
syncOrchestrator.execute(OneTime.Events.start())
Expand Down
Loading
Loading