diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index a4430c195..c0426ad56 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -7,8 +7,6 @@
-
-
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0ac40f10e..cb8dce872 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,9 @@
# Release Notes
-## Unreleased
+## 10.2.1
* Wrap Composables in a `Surface` for additional background color customization
+* Add metadata support
## 10.2.0
diff --git a/lib/VERSION b/lib/VERSION
index 8317185ad..30df293c8 100644
--- a/lib/VERSION
+++ b/lib/VERSION
@@ -1 +1 @@
-10.2.1-SNAPSHOT
\ No newline at end of file
+10.2.2-SNAPSHOT
diff --git a/lib/lib.gradle.kts b/lib/lib.gradle.kts
index 877e6785a..b7e7422bd 100644
--- a/lib/lib.gradle.kts
+++ b/lib/lib.gradle.kts
@@ -82,6 +82,11 @@ composeCompiler {
metricsDestination = layout.buildDirectory.dir("compose_compiler")
}
+moshi {
+ // Opt-in to enable moshi-sealed, disabled by default.
+ enableSealed.set(true)
+}
+
mavenPublishing {
publishToMavenCentral(automaticRelease = true)
signAllPublications()
diff --git a/lib/src/androidTest/java/com/smileidentity/compose/selfie/SelfieCaptureScreenTest.kt b/lib/src/androidTest/java/com/smileidentity/compose/selfie/SelfieCaptureScreenTest.kt
index 37a81002f..ec54f7ebc 100644
--- a/lib/src/androidTest/java/com/smileidentity/compose/selfie/SelfieCaptureScreenTest.kt
+++ b/lib/src/androidTest/java/com/smileidentity/compose/selfie/SelfieCaptureScreenTest.kt
@@ -134,7 +134,7 @@ class SelfieCaptureScreenTest {
// given
val takePictureTag = "takePictureButton"
val viewModel: SelfieViewModel = spyk()
- every { viewModel.analyzeImage(any()) } just Runs
+ every { viewModel.analyzeImage(any(), camSelector) } just Runs
// when
composeTestRule.apply {
@@ -143,6 +143,6 @@ class SelfieCaptureScreenTest {
}
// then
- verify(atLeast = 1, timeout = 1000) { viewModel.analyzeImage(any()) }
+ verify(atLeast = 1, timeout = 1000) { viewModel.analyzeImage(any(), camSelector) }
}
}
diff --git a/lib/src/main/java/com/smileidentity/SmileID.kt b/lib/src/main/java/com/smileidentity/SmileID.kt
index 5c4c7e2cd..97c4267c2 100644
--- a/lib/src/main/java/com/smileidentity/SmileID.kt
+++ b/lib/src/main/java/com/smileidentity/SmileID.kt
@@ -1,8 +1,10 @@
package com.smileidentity
+import android.annotation.SuppressLint
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE
+import android.provider.Settings.Secure
import com.google.android.gms.common.moduleinstall.ModuleInstall
import com.google.android.gms.common.moduleinstall.ModuleInstallRequest
import com.google.mlkit.common.sdkinternal.MlKitContext
@@ -85,6 +87,7 @@ object SmileID {
internal var apiKey: String? = null
internal lateinit var fileSavePath: String
+ internal var fingerprint = ""
/**
* Initialize the SDK. This must be called before any other SDK methods.
@@ -98,6 +101,8 @@ object SmileID {
* source docs for [SmileIDCrashReporting]
* @param okHttpClient An optional [OkHttpClient.Builder] to use for the network requests
*/
+ // "Using device identifiers is not recommended other than for high value fraud prevention"
+ @SuppressLint("HardwareIds")
@JvmStatic
@JvmOverloads
fun initialize(
@@ -137,6 +142,8 @@ object SmileID {
// Usually looks like: /data/user/0//app_SmileID
fileSavePath = context.getDir("SmileID", MODE_PRIVATE).absolutePath
+ // ANDROID_ID may be null. Since Android 8, each app has a different value
+ Secure.getString(context.contentResolver, Secure.ANDROID_ID)?.let { fingerprint = it }
}
/**
diff --git a/lib/src/main/java/com/smileidentity/compose/SmileIDExt.kt b/lib/src/main/java/com/smileidentity/compose/SmileIDExt.kt
index 23dbd2260..2a9e8d054 100644
--- a/lib/src/main/java/com/smileidentity/compose/SmileIDExt.kt
+++ b/lib/src/main/java/com/smileidentity/compose/SmileIDExt.kt
@@ -13,6 +13,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
import com.smileidentity.SmileID
import com.smileidentity.compose.biometric.OrchestratedBiometricKYCScreen
+import com.smileidentity.compose.components.LocalMetadata
import com.smileidentity.compose.components.SmileThemeSurface
import com.smileidentity.compose.consent.OrchestratedConsentScreen
import com.smileidentity.compose.consent.bvn.OrchestratedBvnConsentScreen
@@ -242,6 +243,7 @@ fun SmileID.DocumentVerification(
onResult: SmileIDCallback = {},
) {
SmileThemeSurface(colorScheme = colorScheme, typography = typography) {
+ val metadata = LocalMetadata.current
OrchestratedDocumentVerificationScreen(
modifier = modifier,
userId = userId,
@@ -264,6 +266,7 @@ fun SmileID.DocumentVerification(
captureBothSides = captureBothSides,
selfieFile = bypassSelfieCaptureWithFile,
extraPartnerParams = extraPartnerParams,
+ metadata = metadata,
)
},
),
@@ -327,6 +330,7 @@ fun SmileID.EnhancedDocumentVerificationScreen(
onResult: SmileIDCallback = {},
) {
SmileThemeSurface(colorScheme = colorScheme, typography = typography) {
+ val metadata = LocalMetadata.current
OrchestratedDocumentVerificationScreen(
modifier = modifier,
userId = userId,
@@ -349,6 +353,7 @@ fun SmileID.EnhancedDocumentVerificationScreen(
captureBothSides = captureBothSides,
selfieFile = bypassSelfieCaptureWithFile,
extraPartnerParams = extraPartnerParams,
+ metadata = metadata,
)
},
),
diff --git a/lib/src/main/java/com/smileidentity/compose/components/MetadataCompositionLocal.kt b/lib/src/main/java/com/smileidentity/compose/components/MetadataCompositionLocal.kt
new file mode 100644
index 000000000..afb259fb2
--- /dev/null
+++ b/lib/src/main/java/com/smileidentity/compose/components/MetadataCompositionLocal.kt
@@ -0,0 +1,9 @@
+package com.smileidentity.compose.components
+
+import android.annotation.SuppressLint
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.runtime.toMutableStateList
+import com.smileidentity.models.v2.Metadata
+
+@SuppressLint("ComposeCompositionLocalUsage")
+val LocalMetadata = staticCompositionLocalOf { Metadata.default().items.toMutableStateList() }
diff --git a/lib/src/main/java/com/smileidentity/compose/components/SmileThemeSurface.kt b/lib/src/main/java/com/smileidentity/compose/components/SmileThemeSurface.kt
index 05cdf17e9..c7c24153f 100644
--- a/lib/src/main/java/com/smileidentity/compose/components/SmileThemeSurface.kt
+++ b/lib/src/main/java/com/smileidentity/compose/components/SmileThemeSurface.kt
@@ -5,6 +5,10 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Typography
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.toMutableStateList
+import com.smileidentity.models.v2.Metadata
@Composable
internal fun SmileThemeSurface(
@@ -12,7 +16,11 @@ internal fun SmileThemeSurface(
typography: Typography,
content: @Composable () -> Unit,
) {
- MaterialTheme(colorScheme = colorScheme, typography = typography) {
- Surface(content = content)
+ CompositionLocalProvider(
+ LocalMetadata provides remember { Metadata.default().items.toMutableStateList() },
+ ) {
+ MaterialTheme(colorScheme = colorScheme, typography = typography) {
+ Surface(content = content)
+ }
}
}
diff --git a/lib/src/main/java/com/smileidentity/compose/consent/bvn/OrchestratedBvnConsentScreen.kt b/lib/src/main/java/com/smileidentity/compose/consent/bvn/OrchestratedBvnConsentScreen.kt
index 7a8cda6d4..cb8075113 100644
--- a/lib/src/main/java/com/smileidentity/compose/consent/bvn/OrchestratedBvnConsentScreen.kt
+++ b/lib/src/main/java/com/smileidentity/compose/consent/bvn/OrchestratedBvnConsentScreen.kt
@@ -1,11 +1,13 @@
package com.smileidentity.compose.consent.bvn
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
@@ -35,6 +37,7 @@ internal fun OrchestratedBvnConsentScreen(
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Box(
modifier = modifier
+ .background(color = MaterialTheme.colorScheme.background)
.windowInsetsPadding(WindowInsets.statusBars)
.consumeWindowInsets(WindowInsets.statusBars)
.fillMaxSize(),
diff --git a/lib/src/main/java/com/smileidentity/compose/document/DocumentCaptureScreen.kt b/lib/src/main/java/com/smileidentity/compose/document/DocumentCaptureScreen.kt
index 929242d83..e141c6231 100644
--- a/lib/src/main/java/com/smileidentity/compose/document/DocumentCaptureScreen.kt
+++ b/lib/src/main/java/com/smileidentity/compose/document/DocumentCaptureScreen.kt
@@ -28,6 +28,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
@@ -49,8 +50,10 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.smileidentity.R
import com.smileidentity.SmileIDCrashReporting
import com.smileidentity.compose.components.ImageCaptureConfirmationDialog
+import com.smileidentity.compose.components.LocalMetadata
import com.smileidentity.compose.preview.Preview
import com.smileidentity.compose.preview.SmilePreviews
+import com.smileidentity.models.v2.Metadatum
import com.smileidentity.util.createDocumentFile
import com.smileidentity.util.isValidDocumentImage
import com.smileidentity.util.toast
@@ -94,9 +97,17 @@ fun DocumentCaptureScreen(
onConfirm: (File) -> Unit,
onError: (Throwable) -> Unit,
modifier: Modifier = Modifier,
+ metadata: SnapshotStateList = LocalMetadata.current,
onSkip: () -> Unit = { },
viewModel: DocumentCaptureViewModel = viewModel(
- factory = viewModelFactory { DocumentCaptureViewModel(jobId, side, knownIdAspectRatio) },
+ factory = viewModelFactory {
+ DocumentCaptureViewModel(
+ jobId,
+ side,
+ knownIdAspectRatio,
+ metadata,
+ )
+ },
key = side.name,
),
) {
@@ -163,7 +174,7 @@ fun DocumentCaptureScreen(
confirmButtonText = stringResource(
id = R.string.si_doc_v_confirmation_dialog_confirm_button,
),
- onConfirm = { onConfirm(documentImageToConfirm) },
+ onConfirm = { viewModel.onConfirm(documentImageToConfirm, onConfirm) },
retakeButtonText = stringResource(
id = R.string.si_doc_v_confirmation_dialog_retake_button,
),
@@ -183,7 +194,7 @@ fun DocumentCaptureScreen(
areEdgesDetected = uiState.areEdgesDetected,
showCaptureInProgress = uiState.showCaptureInProgress,
showManualCaptureButton = uiState.showManualCaptureButton,
- onCaptureClicked = viewModel::captureDocument,
+ onCaptureClicked = viewModel::captureDocumentManually,
imageAnalyzer = viewModel::analyze,
onFocusEvent = viewModel::onFocusEvent,
modifier = modifier,
diff --git a/lib/src/main/java/com/smileidentity/compose/document/OrchestratedDocumentVerificationScreen.kt b/lib/src/main/java/com/smileidentity/compose/document/OrchestratedDocumentVerificationScreen.kt
index af3eb12e6..6dc222226 100644
--- a/lib/src/main/java/com/smileidentity/compose/document/OrchestratedDocumentVerificationScreen.kt
+++ b/lib/src/main/java/com/smileidentity/compose/document/OrchestratedDocumentVerificationScreen.kt
@@ -1,12 +1,14 @@
package com.smileidentity.compose.document
import android.os.Parcelable
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.saveable.rememberSaveable
@@ -44,6 +46,7 @@ internal fun OrchestratedDocumentVerificationScreen(
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Box(
modifier = modifier
+ .background(color = MaterialTheme.colorScheme.background)
.windowInsetsPadding(WindowInsets.statusBars)
.consumeWindowInsets(WindowInsets.statusBars)
.fillMaxSize(),
diff --git a/lib/src/main/java/com/smileidentity/compose/selfie/OrchestratedSelfieCaptureScreen.kt b/lib/src/main/java/com/smileidentity/compose/selfie/OrchestratedSelfieCaptureScreen.kt
index 4d90d342b..905d4c67b 100644
--- a/lib/src/main/java/com/smileidentity/compose/selfie/OrchestratedSelfieCaptureScreen.kt
+++ b/lib/src/main/java/com/smileidentity/compose/selfie/OrchestratedSelfieCaptureScreen.kt
@@ -1,17 +1,20 @@
package com.smileidentity.compose.selfie
import android.graphics.BitmapFactory
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
@@ -21,7 +24,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.smileidentity.R
import com.smileidentity.compose.components.ImageCaptureConfirmationDialog
+import com.smileidentity.compose.components.LocalMetadata
import com.smileidentity.compose.components.ProcessingScreen
+import com.smileidentity.models.v2.Metadatum
import com.smileidentity.results.SmartSelfieResult
import com.smileidentity.results.SmileIDCallback
import com.smileidentity.util.randomJobId
@@ -47,6 +52,7 @@ fun OrchestratedSelfieCaptureScreen(
showAttribution: Boolean = true,
showInstructions: Boolean = true,
extraPartnerParams: ImmutableMap = persistentMapOf(),
+ metadata: SnapshotStateList = LocalMetadata.current,
viewModel: SelfieViewModel = viewModel(
factory = viewModelFactory {
SelfieViewModel(
@@ -55,6 +61,7 @@ fun OrchestratedSelfieCaptureScreen(
jobId = jobId,
allowNewEnroll = allowNewEnroll,
skipApiSubmission = skipApiSubmission,
+ metadata = metadata,
extraPartnerParams = extraPartnerParams,
)
},
@@ -65,6 +72,7 @@ fun OrchestratedSelfieCaptureScreen(
var acknowledgedInstructions by rememberSaveable { mutableStateOf(false) }
Box(
modifier = modifier
+ .background(color = MaterialTheme.colorScheme.background)
.windowInsetsPadding(WindowInsets.statusBars)
.consumeWindowInsets(WindowInsets.statusBars)
.fillMaxSize(),
diff --git a/lib/src/main/java/com/smileidentity/compose/selfie/SelfieCaptureScreen.kt b/lib/src/main/java/com/smileidentity/compose/selfie/SelfieCaptureScreen.kt
index 1d64dfef4..d26f00fc5 100644
--- a/lib/src/main/java/com/smileidentity/compose/selfie/SelfieCaptureScreen.kt
+++ b/lib/src/main/java/com/smileidentity/compose/selfie/SelfieCaptureScreen.kt
@@ -22,6 +22,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -36,8 +37,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.smileidentity.R
import com.smileidentity.compose.components.ForceBrightness
+import com.smileidentity.compose.components.LocalMetadata
import com.smileidentity.compose.preview.Preview
import com.smileidentity.compose.preview.SmilePreviews
+import com.smileidentity.models.v2.Metadatum
import com.smileidentity.util.randomJobId
import com.smileidentity.util.randomUserId
import com.smileidentity.viewmodel.MAX_FACE_AREA_THRESHOLD
@@ -64,6 +67,7 @@ fun SelfieCaptureScreen(
isEnroll: Boolean = true,
allowAgentMode: Boolean = true,
skipApiSubmission: Boolean = false,
+ metadata: SnapshotStateList = LocalMetadata.current,
viewModel: SelfieViewModel = viewModel(
factory = viewModelFactory {
SelfieViewModel(
@@ -72,6 +76,7 @@ fun SelfieCaptureScreen(
jobId = jobId,
allowNewEnroll = allowNewEnroll,
skipApiSubmission = skipApiSubmission,
+ metadata = metadata,
)
},
),
@@ -89,7 +94,7 @@ fun SelfieCaptureScreen(
camSelector = camSelector,
implementationMode = ImplementationMode.Performance,
imageAnalyzer = cameraState.rememberImageAnalyzer(
- analyze = viewModel::analyzeImage,
+ analyze = { viewModel.analyzeImage(it, camSelector) },
// Guarantees only one image will be delivered for analysis at a time
imageAnalysisBackpressureStrategy = KeepOnlyLatest,
),
diff --git a/lib/src/main/java/com/smileidentity/compose/selfie/v2/SelfieCaptureScreenV2.kt b/lib/src/main/java/com/smileidentity/compose/selfie/v2/SelfieCaptureScreenV2.kt
index d1d19b728..a62174ba3 100644
--- a/lib/src/main/java/com/smileidentity/compose/selfie/v2/SelfieCaptureScreenV2.kt
+++ b/lib/src/main/java/com/smileidentity/compose/selfie/v2/SelfieCaptureScreenV2.kt
@@ -36,6 +36,7 @@ import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -65,6 +66,7 @@ import com.smileidentity.compose.components.Face
import com.smileidentity.compose.components.FaceMovingBack
import com.smileidentity.compose.components.FaceMovingCloser
import com.smileidentity.compose.components.ForceBrightness
+import com.smileidentity.compose.components.LocalMetadata
import com.smileidentity.compose.components.LottieFace
import com.smileidentity.compose.components.LottieFaceLookingLeft
import com.smileidentity.compose.components.LottieFaceLookingRight
@@ -76,6 +78,7 @@ import com.smileidentity.compose.preview.Preview
import com.smileidentity.compose.preview.SmilePreviews
import com.smileidentity.compose.selfie.AgentModeSwitch
import com.smileidentity.ml.SelfieQualityModel
+import com.smileidentity.models.v2.Metadatum
import com.smileidentity.results.SmartSelfieResult
import com.smileidentity.results.SmileIDCallback
import com.smileidentity.results.SmileIDResult
@@ -125,6 +128,7 @@ fun OrchestratedSelfieCaptureScreenV2(
allowAgentMode: Boolean = false,
allowNewEnroll: Boolean? = null,
extraPartnerParams: ImmutableMap = persistentMapOf(),
+ metadata: SnapshotStateList = LocalMetadata.current,
viewModel: SmartSelfieV2ViewModel = viewModel(
initializer = {
SmartSelfieV2ViewModel(
@@ -134,6 +138,7 @@ fun OrchestratedSelfieCaptureScreenV2(
useStrictMode = useStrictMode,
extraPartnerParams = extraPartnerParams,
selfieQualityModel = selfieQualityModel,
+ metadata = metadata,
onResult = onResult,
)
},
@@ -185,7 +190,7 @@ fun OrchestratedSelfieCaptureScreenV2(
implementationMode = ImplementationMode.Compatible,
scaleType = ScaleType.FillCenter,
imageAnalyzer = cameraState.rememberImageAnalyzer(
- analyze = viewModel::analyzeImage,
+ analyze = { viewModel.analyzeImage(it, camSelector) },
),
isImageAnalysisEnabled = true,
modifier = Modifier
diff --git a/lib/src/main/java/com/smileidentity/models/PrepUpload.kt b/lib/src/main/java/com/smileidentity/models/PrepUpload.kt
index 1e4189e13..69958c37a 100644
--- a/lib/src/main/java/com/smileidentity/models/PrepUpload.kt
+++ b/lib/src/main/java/com/smileidentity/models/PrepUpload.kt
@@ -4,6 +4,7 @@ package com.smileidentity.models
import com.smileidentity.BuildConfig
import com.smileidentity.SmileID
+import com.smileidentity.models.v2.Metadatum
import com.smileidentity.networking.calculateSignature
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@@ -16,6 +17,7 @@ data class PrepUploadRequest(
// TODO - Michael will change this to a boolean
@Json(name = "allow_new_enroll") val allowNewEnroll: String,
@Json(name = "smile_client_id") val partnerId: String = SmileID.config.partnerId,
+ @Json(name = "metadata") val metadata: List? = null,
@Json(name = "source_sdk") val sourceSdk: String = "android",
@Json(name = "source_sdk_version") val sourceSdkVersion: String = BuildConfig.VERSION_NAME,
@Json(name = "timestamp") val timestamp: String = System.currentTimeMillis().toString(),
diff --git a/lib/src/main/java/com/smileidentity/models/v2/Metadata.kt b/lib/src/main/java/com/smileidentity/models/v2/Metadata.kt
index b55b5c776..33a731b22 100644
--- a/lib/src/main/java/com/smileidentity/models/v2/Metadata.kt
+++ b/lib/src/main/java/com/smileidentity/models/v2/Metadata.kt
@@ -1,10 +1,15 @@
package com.smileidentity.models.v2
+import android.os.Build
import android.os.Parcelable
import com.smileidentity.BuildConfig
+import com.smileidentity.SmileID
+import com.smileidentity.SmileIDCrashReporting
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
+import kotlin.time.Duration
import kotlinx.parcelize.Parcelize
+import timber.log.Timber
/**
* Wrap Metadatum in a list. This allows for easy conversion with Moshi and the format the
@@ -15,19 +20,154 @@ data class Metadata(val items: List) : Parcelable {
companion object {
fun default(): Metadata = Metadata(
listOf(
- Metadatum("sdk", "android"),
- Metadatum("sdk_version", BuildConfig.VERSION_NAME),
+ Metadatum.Sdk,
+ Metadatum.SdkVersion,
+ Metadatum.Fingerprint,
+ Metadatum.DeviceModel,
+ Metadatum.DeviceOS,
),
)
}
}
+fun List.asNetworkRequest(): Metadata = Metadata(this)
+
/**
* key-value pair that can be used to store additional information about a job
*/
@Parcelize
@JsonClass(generateAdapter = true)
-data class Metadatum(
+open class Metadatum(
@Json(name = "name") val name: String,
@Json(name = "value") val value: String,
-) : Parcelable
+) : Parcelable {
+ @Parcelize
+ data object Sdk : Metadatum("sdk", "android")
+
+ @Parcelize
+ data object SdkVersion : Metadatum("sdk_version", BuildConfig.VERSION_NAME)
+
+ @Parcelize
+ data object DeviceModel : Metadatum("device_model", model)
+
+ @Parcelize
+ data object DeviceOS : Metadatum("device_os", os)
+
+ @Parcelize
+ data object Fingerprint : Metadatum("fingerprint", SmileID.fingerprint)
+
+ @Parcelize
+ data class SelfieImageOrigin(val origin: SelfieImageOriginValue) :
+ Metadatum("selfie_image_origin", origin.value)
+
+ /**
+ * This represents the time it took for the user to complete *their* portion of the task. It
+ * does *NOT* include network time.
+ */
+ @Parcelize
+ data class SelfieCaptureDuration(val duration: Duration) :
+ Metadatum("selfie_capture_duration_ms", duration.inWholeMilliseconds.toString())
+
+ @Parcelize
+ data class DocumentFrontImageOrigin(val origin: DocumentImageOriginValue) :
+ Metadatum("document_front_image_origin", origin.value)
+
+ @Parcelize
+ data class DocumentBackImageOrigin(val origin: DocumentImageOriginValue) :
+ Metadatum("document_back_image_origin", origin.value)
+
+ @Parcelize
+ data class DocumentFrontCaptureRetries(val retries: Int) :
+ Metadatum("document_front_capture_retries", retries.toString())
+
+ @Parcelize
+ data class DocumentBackCaptureRetries(val retries: Int) :
+ Metadatum("document_back_capture_retries", retries.toString())
+
+ /**
+ * This represents the time it took for the user to complete *their* portion of the task. It
+ * does *NOT* include network time.
+ */
+ @Parcelize
+ data class DocumentFrontCaptureDuration(val duration: Duration) :
+ Metadatum("document_front_capture_duration_ms", duration.inWholeMilliseconds.toString())
+
+ /**
+ * This represents the time it took for the user to complete *their* portion of the task. It
+ * does *NOT* include network time.
+ */
+ @Parcelize
+ data class DocumentBackCaptureDuration(val duration: Duration) :
+ Metadatum("document_back_capture_duration_ms", duration.inWholeMilliseconds.toString())
+}
+
+enum class DocumentImageOriginValue(val value: String) {
+ Gallery("gallery"),
+
+ CameraAutoCapture("camera_auto_capture"),
+
+ CameraManualCapture("camera_manual_capture"),
+}
+
+enum class SelfieImageOriginValue(val value: String) {
+ FrontCamera("front_camera"),
+
+ BackCamera("back_camera"),
+}
+
+private val isEmulator: Boolean
+ get() {
+ try {
+ return (
+ (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) ||
+ Build.FINGERPRINT.startsWith("generic") ||
+ Build.FINGERPRINT.startsWith("unknown") ||
+ Build.HARDWARE.contains("goldfish") ||
+ Build.HARDWARE.contains("ranchu") ||
+ Build.MODEL.contains("google_sdk") ||
+ Build.MODEL.contains("Emulator") ||
+ Build.MODEL.contains("Android SDK built for x86") ||
+ Build.MANUFACTURER.contains("Genymotion") ||
+ Build.PRODUCT.contains("sdk_google") ||
+ Build.PRODUCT.contains("google_sdk") ||
+ Build.PRODUCT.contains("sdk") ||
+ Build.PRODUCT.contains("sdk_x86") ||
+ Build.PRODUCT.contains("sdk_gphone64_arm64") ||
+ Build.PRODUCT.contains("vbox86p") ||
+ Build.PRODUCT.contains("emulator") ||
+ Build.PRODUCT.contains("simulator")
+ )
+ } catch (e: Exception) {
+ return false
+ }
+ }
+
+/**
+ * Returns the model of the device. If the device is an emulator, it returns "emulator". Any errors
+ * result in "unknown"
+ */
+private val model: String
+ get() {
+ try {
+ val manufacturer = Build.MANUFACTURER
+ val model = Build.MODEL
+ return if (isEmulator) {
+ "emulator"
+ } else if (model.contains(manufacturer, ignoreCase = true)) {
+ model
+ } else {
+ "$manufacturer $model"
+ }
+ } catch (e: Exception) {
+ Timber.w(e, "Error getting device model")
+ SmileIDCrashReporting.hub.addBreadcrumb("Error getting device model: $e")
+ return "unknown"
+ }
+ }
+
+/**
+ * On Android, we return the API level, as this provides much more signal than the consumer facing
+ * version number
+ */
+private val os: String
+ get() = "Android API ${Build.VERSION.SDK_INT}"
diff --git a/lib/src/main/java/com/smileidentity/networking/Retrofit.kt b/lib/src/main/java/com/smileidentity/networking/Retrofit.kt
index c74e4832f..8c54ba141 100644
--- a/lib/src/main/java/com/smileidentity/networking/Retrofit.kt
+++ b/lib/src/main/java/com/smileidentity/networking/Retrofit.kt
@@ -251,6 +251,11 @@ object StringifiedBooleanAdapter {
fun fromJson(value: String): Boolean = value.toBoolean()
}
+/**
+ * Mainly necessary so that requests that need multipart form data are formatted correctly, since
+ * directly including a List type changes how Retrofit handles the parameter. While this can be
+ * used for other JSON request bodies, it's not necessary and you can simply use `List`
+ */
@Suppress("unused")
object MetadataAdapter {
@ToJson
diff --git a/lib/src/main/java/com/smileidentity/viewmodel/ActiveLivenessTask.kt b/lib/src/main/java/com/smileidentity/viewmodel/ActiveLivenessTask.kt
index 99399db61..0007aada7 100644
--- a/lib/src/main/java/com/smileidentity/viewmodel/ActiveLivenessTask.kt
+++ b/lib/src/main/java/com/smileidentity/viewmodel/ActiveLivenessTask.kt
@@ -39,7 +39,6 @@ internal class ActiveLivenessTask(
private data object UpEnd : Up, Endpoint(UpMid)
private data object UpMid : Up, Midpoint
- // private val orderedFaceDirections = FaceDirection.entries.shuffled()
private val orderedFaceDirections = listOf(LeftEnd, RightEnd, UpEnd)
.shuffled()
.flatMap {
diff --git a/lib/src/main/java/com/smileidentity/viewmodel/SelfieViewModel.kt b/lib/src/main/java/com/smileidentity/viewmodel/SelfieViewModel.kt
index a007913bf..00cba00ee 100644
--- a/lib/src/main/java/com/smileidentity/viewmodel/SelfieViewModel.kt
+++ b/lib/src/main/java/com/smileidentity/viewmodel/SelfieViewModel.kt
@@ -21,6 +21,10 @@ import com.smileidentity.models.JobType.SmartSelfieEnrollment
import com.smileidentity.models.PartnerParams
import com.smileidentity.models.PrepUploadRequest
import com.smileidentity.models.SmileIDException
+import com.smileidentity.models.v2.Metadatum
+import com.smileidentity.models.v2.SelfieImageOriginValue.BackCamera
+import com.smileidentity.models.v2.SelfieImageOriginValue.FrontCamera
+import com.smileidentity.models.v2.asNetworkRequest
import com.smileidentity.networking.doSmartSelfieAuthentication
import com.smileidentity.networking.doSmartSelfieEnrollment
import com.smileidentity.results.SmartSelfieResult
@@ -41,11 +45,13 @@ import com.smileidentity.util.isNetworkFailure
import com.smileidentity.util.moveJobToSubmitted
import com.smileidentity.util.postProcessImageBitmap
import com.smileidentity.util.rotated
+import com.ujizin.camposer.state.CamSelector
import io.sentry.Breadcrumb
import io.sentry.SentryLevel
import java.io.File
import kotlin.math.absoluteValue
import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.TimeSource
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.coroutines.FlowPreview
@@ -94,6 +100,7 @@ class SelfieViewModel(
private val jobId: String,
private val allowNewEnroll: Boolean,
private val skipApiSubmission: Boolean,
+ private val metadata: MutableList,
private val extraPartnerParams: ImmutableMap = persistentMapOf(),
) : ViewModel() {
private val _uiState = MutableStateFlow(SelfieUiState())
@@ -124,8 +131,10 @@ class SelfieViewModel(
}.build()
private val faceDetector by lazy { FaceDetection.getClient(faceDetectorOptions) }
+ private val metadataTimerStart = TimeSource.Monotonic.markNow()
+
@OptIn(ExperimentalGetImage::class)
- internal fun analyzeImage(imageProxy: ImageProxy) {
+ internal fun analyzeImage(imageProxy: ImageProxy, camSelector: CamSelector) {
val image = imageProxy.image
val elapsedTimeMs = System.currentTimeMillis() - lastAutoCaptureTimeMs
if (!shouldAnalyzeImages || elapsedTimeMs < INTRA_IMAGE_MIN_DELAY_MS || image == null) {
@@ -228,6 +237,7 @@ class SelfieViewModel(
resizeLongerDimensionTo = SELFIE_IMAGE_SIZE,
)
shouldAnalyzeImages = false
+ setCameraFacingMetadata(camSelector)
_uiState.update {
it.copy(
progress = 1f,
@@ -253,6 +263,14 @@ class SelfieViewModel(
}
}
+ private fun setCameraFacingMetadata(camSelector: CamSelector) {
+ metadata.removeAll { it is Metadatum.SelfieImageOrigin }
+ when (camSelector) {
+ CamSelector.Front -> metadata.add(Metadatum.SelfieImageOrigin(FrontCamera))
+ CamSelector.Back -> metadata.add(Metadatum.SelfieImageOrigin(BackCamera))
+ }
+ }
+
private fun hasFaceRotatedEnough(face: Face): Boolean {
val rotationXDelta = (face.headEulerAngleX - previousHeadRotationX).absoluteValue
val rotationYDelta = (face.headEulerAngleY - previousHeadRotationY).absoluteValue
@@ -263,6 +281,7 @@ class SelfieViewModel(
}
private fun submitJob(selfieFile: File, livenessFiles: List) {
+ metadata.add(Metadatum.SelfieCaptureDuration(metadataTimerStart.elapsedNow()))
if (skipApiSubmission) {
result = SmileIDResult.Success(SmartSelfieResult(selfieFile, livenessFiles, null))
_uiState.update { it.copy(processingState = ProcessingState.Success) }
@@ -330,6 +349,7 @@ class SelfieViewModel(
extras = extraPartnerParams,
),
allowNewEnroll = allowNewEnroll.toString(),
+ metadata = metadata,
timestamp = "",
signature = "",
),
@@ -343,6 +363,7 @@ class SelfieViewModel(
userId = userId,
partnerParams = extraPartnerParams,
allowNewEnroll = allowNewEnroll,
+ metadata = metadata.asNetworkRequest(),
)
} else {
SmileID.api.doSmartSelfieAuthentication(
@@ -350,6 +371,7 @@ class SelfieViewModel(
livenessImages = livenessFiles,
userId = userId,
partnerParams = extraPartnerParams,
+ metadata = metadata.asNetworkRequest(),
)
}
// Move files from unsubmitted to submitted directories
@@ -412,6 +434,8 @@ class SelfieViewModel(
if (selfieFile != null && livenessFiles.size == NUM_LIVENESS_IMAGES) {
submitJob(selfieFile!!, livenessFiles)
} else {
+ metadata.removeAll { it is Metadatum.SelfieCaptureDuration }
+ metadata.removeAll { it is Metadatum.SelfieImageOrigin }
shouldAnalyzeImages = true
_uiState.update {
it.copy(processingState = null)
diff --git a/lib/src/main/java/com/smileidentity/viewmodel/SmartSelfieV2ViewModel.kt b/lib/src/main/java/com/smileidentity/viewmodel/SmartSelfieV2ViewModel.kt
index 73b5bb067..534930f27 100644
--- a/lib/src/main/java/com/smileidentity/viewmodel/SmartSelfieV2ViewModel.kt
+++ b/lib/src/main/java/com/smileidentity/viewmodel/SmartSelfieV2ViewModel.kt
@@ -20,7 +20,11 @@ import com.smileidentity.SmileID
import com.smileidentity.SmileIDCrashReporting
import com.smileidentity.ml.SelfieQualityModel
import com.smileidentity.models.v2.FailureReason
+import com.smileidentity.models.v2.Metadatum
+import com.smileidentity.models.v2.SelfieImageOriginValue.BackCamera
+import com.smileidentity.models.v2.SelfieImageOriginValue.FrontCamera
import com.smileidentity.models.v2.SmartSelfieResponse
+import com.smileidentity.models.v2.asNetworkRequest
import com.smileidentity.networking.doSmartSelfieAuthentication
import com.smileidentity.networking.doSmartSelfieEnrollment
import com.smileidentity.results.SmartSelfieResult
@@ -43,9 +47,11 @@ import com.smileidentity.viewmodel.SelfieHint.OnlyOneFace
import com.smileidentity.viewmodel.SelfieHint.PoorImageQuality
import com.smileidentity.viewmodel.SelfieHint.SearchingForFace
import com.smileidentity.viewmodel.SelfieHint.Smile
+import com.ujizin.camposer.state.CamSelector
import java.io.File
import java.io.IOException
import kotlin.math.absoluteValue
+import kotlin.time.TimeSource
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.coroutines.FlowPreview
@@ -129,6 +135,7 @@ class SmartSelfieV2ViewModel(
private val isEnroll: Boolean,
private val useStrictMode: Boolean,
private val selfieQualityModel: SelfieQualityModel,
+ private val metadata: MutableList,
private val allowNewEnroll: Boolean? = null,
private val extraPartnerParams: ImmutableMap = persistentMapOf(),
private val faceDetector: FaceDetector = FaceDetection.getClient(
@@ -159,6 +166,7 @@ class SmartSelfieV2ViewModel(
private val selfieQualityHistory = mutableListOf()
private var forcedFailureTimerExpired = false
private val shouldUseActiveLiveness: Boolean get() = useStrictMode && !forcedFailureTimerExpired
+ private val metadataTimerStart = TimeSource.Monotonic.markNow()
init {
startStrictModeTimerIfNecessary()
@@ -201,7 +209,7 @@ class SmartSelfieV2ViewModel(
* a. Face looking in the correct direction
*/
@OptIn(ExperimentalGetImage::class)
- fun analyzeImage(imageProxy: ImageProxy) {
+ fun analyzeImage(imageProxy: ImageProxy, camSelector: CamSelector) {
val elapsedTimeSinceCaptureMs = System.currentTimeMillis() - lastAutoCaptureTimeMs
val enoughTimeHasPassed = elapsedTimeSinceCaptureMs > INTRA_IMAGE_MIN_DELAY_MS
if (!enoughTimeHasPassed || !shouldAnalyzeImages) {
@@ -430,6 +438,7 @@ class SmartSelfieV2ViewModel(
}
shouldAnalyzeImages = false
+ setCameraFacingMetadata(camSelector)
val proxy = { e: Throwable ->
when {
e is IOException -> {
@@ -479,8 +488,8 @@ class SmartSelfieV2ViewModel(
}
}
- // activeLivenessDetails = ActiveLivenessDetails(orderedFaceDirections = activeLiveness.orderedFaceDirections, forceFailure = forcedFailureTimerExpired),
private suspend fun submitJob(selfieFile: File): SmartSelfieResponse {
+ metadata.add(Metadatum.SelfieCaptureDuration(metadataTimerStart.elapsedNow()))
return if (isEnroll) {
SmileID.api.doSmartSelfieEnrollment(
userId = userId,
@@ -489,7 +498,7 @@ class SmartSelfieV2ViewModel(
allowNewEnroll = allowNewEnroll,
partnerParams = extraPartnerParams,
failureReason = FailureReason(activeLivenessTimedOut = forcedFailureTimerExpired),
- metadata = null,
+ metadata = metadata.asNetworkRequest(),
)
} else {
SmileID.api.doSmartSelfieAuthentication(
@@ -497,7 +506,8 @@ class SmartSelfieV2ViewModel(
selfieImage = selfieFile,
livenessImages = livenessFiles,
partnerParams = extraPartnerParams,
- metadata = null,
+ failureReason = FailureReason(activeLivenessTimedOut = forcedFailureTimerExpired),
+ metadata = metadata.asNetworkRequest(),
)
}
}
@@ -508,12 +518,22 @@ class SmartSelfieV2ViewModel(
* file IO scenario.
*/
fun onRetry() {
+ metadata.removeAll { it is Metadatum.SelfieCaptureDuration }
+ metadata.removeAll { it is Metadatum.SelfieImageOrigin }
resetCaptureProgress(SearchingForFace)
forcedFailureTimerExpired = false
startStrictModeTimerIfNecessary()
shouldAnalyzeImages = true
}
+ private fun setCameraFacingMetadata(camSelector: CamSelector) {
+ metadata.removeAll { it is Metadatum.SelfieImageOrigin }
+ when (camSelector) {
+ CamSelector.Front -> metadata.add(Metadatum.SelfieImageOrigin(FrontCamera))
+ CamSelector.Back -> metadata.add(Metadatum.SelfieImageOrigin(BackCamera))
+ }
+ }
+
/**
* If the user hasn't satisfied capture conditions yet, then immediately update the UI with
* feedback. Otherwise, we will maintain the currently displayed directive *as long as just the
diff --git a/lib/src/main/java/com/smileidentity/viewmodel/document/DocumentCaptureViewModel.kt b/lib/src/main/java/com/smileidentity/viewmodel/document/DocumentCaptureViewModel.kt
index 906194c35..0c6c435dc 100644
--- a/lib/src/main/java/com/smileidentity/viewmodel/document/DocumentCaptureViewModel.kt
+++ b/lib/src/main/java/com/smileidentity/viewmodel/document/DocumentCaptureViewModel.kt
@@ -16,6 +16,8 @@ import com.google.mlkit.vision.objects.ObjectDetector
import com.google.mlkit.vision.objects.defaults.ObjectDetectorOptions
import com.smileidentity.R
import com.smileidentity.compose.document.DocumentCaptureSide
+import com.smileidentity.models.v2.DocumentImageOriginValue
+import com.smileidentity.models.v2.Metadatum
import com.smileidentity.util.calculateLuminance
import com.smileidentity.util.createDocumentFile
import com.smileidentity.util.postProcessImage
@@ -24,6 +26,7 @@ import com.ujizin.camposer.state.ImageCaptureResult
import java.io.File
import kotlin.math.abs
import kotlin.time.Duration.Companion.seconds
+import kotlin.time.TimeSource
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -59,12 +62,15 @@ class DocumentCaptureViewModel(
private val jobId: String,
private val side: DocumentCaptureSide,
private val knownAspectRatio: Float?,
+ private val metadata: MutableList,
private val objectDetector: ObjectDetector = ObjectDetection.getClient(
ObjectDetectorOptions.Builder()
.setDetectorMode(ObjectDetectorOptions.SINGLE_IMAGE_MODE)
.build(),
),
) : ViewModel() {
+ var documentImageOrigin: DocumentImageOriginValue? = null
+ private set
private val _uiState = MutableStateFlow(DocumentCaptureUiState())
val uiState = _uiState.asStateFlow()
private var lastAnalysisTimeMs = 0L
@@ -73,6 +79,8 @@ class DocumentCaptureViewModel(
private var documentFirstDetectedTimeMs: Long? = null
private var captureNextAnalysisFrame = false
private val defaultAspectRatio = knownAspectRatio ?: 1f
+ private var retryCount = 0
+ private val timerStart = TimeSource.Monotonic.markNow()
init {
_uiState.update { it.copy(idAspectRatio = defaultAspectRatio) }
@@ -111,12 +119,18 @@ class DocumentCaptureViewModel(
Timber.w(throwable)
_uiState.update { it.copy(captureError = throwable) }
} else {
+ documentImageOrigin = DocumentImageOriginValue.Gallery
_uiState.update {
it.copy(acknowledgedInstructions = true, documentImageToConfirm = selectedPhoto)
}
}
}
+ fun captureDocumentManually(cameraState: CameraState) {
+ documentImageOrigin = DocumentImageOriginValue.CameraManualCapture
+ captureDocument(cameraState)
+ }
+
/**
* To be called when auto capture determines the image quality is sufficient or when the user
* taps the manual capture button.
@@ -161,7 +175,22 @@ class DocumentCaptureViewModel(
// It is safe to delete the file here, even though it may have been selected from the
// gallery because we copied the URI contents to a new File first
uiState.value.documentImageToConfirm?.delete()
+ when (side) {
+ DocumentCaptureSide.Front -> {
+ metadata.removeAll { it is Metadatum.DocumentFrontCaptureRetries }
+ metadata.removeAll { it is Metadatum.DocumentFrontCaptureDuration }
+ metadata.removeAll { it is Metadatum.DocumentFrontImageOrigin }
+ }
+
+ DocumentCaptureSide.Back -> {
+ metadata.removeAll { it is Metadatum.DocumentBackCaptureRetries }
+ metadata.removeAll { it is Metadatum.DocumentBackCaptureDuration }
+ metadata.removeAll { it is Metadatum.DocumentBackImageOrigin }
+ }
+ }
isCapturing = false
+ documentImageOrigin = null
+ retryCount++
_uiState.update {
it.copy(
captureError = null,
@@ -173,6 +202,24 @@ class DocumentCaptureViewModel(
}
}
+ fun onConfirm(documentImageToConfirm: File, onConfirm: (File) -> Unit) {
+ val elapsed = timerStart.elapsedNow()
+ when (side) {
+ DocumentCaptureSide.Front -> {
+ metadata.add(Metadatum.DocumentFrontCaptureRetries(retryCount))
+ metadata.add(Metadatum.DocumentFrontCaptureDuration(elapsed))
+ documentImageOrigin?.let { metadata.add(Metadatum.DocumentFrontImageOrigin(it)) }
+ }
+
+ DocumentCaptureSide.Back -> {
+ metadata.add(Metadatum.DocumentBackCaptureRetries(retryCount))
+ metadata.add(Metadatum.DocumentBackCaptureDuration(elapsed))
+ documentImageOrigin?.let { metadata.add(Metadatum.DocumentBackImageOrigin(it)) }
+ }
+ }
+ onConfirm(documentImageToConfirm)
+ }
+
fun onFocusEvent(focusEvent: Int) {
isFocusing = focusEvent == TAP_TO_FOCUS_STARTED || focusEvent == TAP_TO_FOCUS_NOT_FOCUSED
if (isFocusing) {
@@ -248,6 +295,7 @@ class DocumentCaptureViewModel(
uiState.value.documentImageToConfirm == null
) {
captureNextAnalysisFrame = false
+ documentImageOrigin = DocumentImageOriginValue.CameraAutoCapture
captureDocument(cameraState)
}
}
diff --git a/lib/src/main/java/com/smileidentity/viewmodel/document/OrchestratedDocumentViewModel.kt b/lib/src/main/java/com/smileidentity/viewmodel/document/OrchestratedDocumentViewModel.kt
index f15f4b4ab..433ac41f2 100644
--- a/lib/src/main/java/com/smileidentity/viewmodel/document/OrchestratedDocumentViewModel.kt
+++ b/lib/src/main/java/com/smileidentity/viewmodel/document/OrchestratedDocumentViewModel.kt
@@ -15,6 +15,7 @@ import com.smileidentity.models.PartnerParams
import com.smileidentity.models.PrepUploadRequest
import com.smileidentity.models.SmileIDException
import com.smileidentity.models.UploadRequest
+import com.smileidentity.models.v2.Metadatum
import com.smileidentity.networking.asDocumentBackImage
import com.smileidentity.networking.asDocumentFrontImage
import com.smileidentity.networking.asLivenessImage
@@ -65,6 +66,7 @@ internal abstract class OrchestratedDocumentViewModel(
private val captureBothSides: Boolean,
protected var selfieFile: File? = null,
private var extraPartnerParams: ImmutableMap = persistentMapOf(),
+ private val metadata: MutableList,
) : ViewModel() {
private val _uiState = MutableStateFlow(OrchestratedDocumentUiState())
val uiState = _uiState.asStateFlow()
@@ -79,13 +81,9 @@ internal abstract class OrchestratedDocumentViewModel(
fun onDocumentFrontCaptureSuccess(documentImageFile: File) {
documentFrontFile = documentImageFile
if (captureBothSides) {
- _uiState.update {
- it.copy(currentStep = DocumentCaptureFlow.BackDocumentCapture)
- }
+ _uiState.update { it.copy(currentStep = DocumentCaptureFlow.BackDocumentCapture) }
} else if (selfieFile == null) {
- _uiState.update {
- it.copy(currentStep = DocumentCaptureFlow.SelfieCapture)
- }
+ _uiState.update { it.copy(currentStep = DocumentCaptureFlow.SelfieCapture) }
} else {
submitJob()
}
@@ -93,9 +91,7 @@ internal abstract class OrchestratedDocumentViewModel(
fun onDocumentBackSkip() {
if (selfieFile == null) {
- _uiState.update {
- it.copy(currentStep = DocumentCaptureFlow.SelfieCapture)
- }
+ _uiState.update { it.copy(currentStep = DocumentCaptureFlow.SelfieCapture) }
} else {
submitJob()
}
@@ -104,9 +100,7 @@ internal abstract class OrchestratedDocumentViewModel(
fun onDocumentBackCaptureSuccess(documentImageFile: File) {
documentBackFile = documentImageFile
if (selfieFile == null) {
- _uiState.update {
- it.copy(currentStep = DocumentCaptureFlow.SelfieCapture)
- }
+ _uiState.update { it.copy(currentStep = DocumentCaptureFlow.SelfieCapture) }
} else {
submitJob()
}
@@ -168,6 +162,7 @@ internal abstract class OrchestratedDocumentViewModel(
extras = extraPartnerParams,
),
allowNewEnroll = allowNewEnroll.toString(),
+ metadata = metadata,
timestamp = "",
signature = "",
),
@@ -184,6 +179,7 @@ internal abstract class OrchestratedDocumentViewModel(
partnerParams = authResponse.partnerParams.copy(extras = extraPartnerParams),
// TODO : Michael will change this to boolean
allowNewEnroll = allowNewEnroll.toString(),
+ metadata = metadata,
signature = authResponse.signature,
timestamp = authResponse.timestamp,
)
@@ -325,6 +321,7 @@ internal class DocumentVerificationViewModel(
captureBothSides: Boolean,
selfieFile: File? = null,
extraPartnerParams: ImmutableMap = persistentMapOf(),
+ metadata: MutableList,
) : OrchestratedDocumentViewModel(
jobType = jobType,
userId = userId,
@@ -335,6 +332,7 @@ internal class DocumentVerificationViewModel(
captureBothSides = captureBothSides,
selfieFile = selfieFile,
extraPartnerParams = extraPartnerParams,
+ metadata = metadata,
) {
override fun saveResult(
@@ -366,6 +364,7 @@ internal class EnhancedDocumentVerificationViewModel(
captureBothSides: Boolean,
selfieFile: File? = null,
extraPartnerParams: ImmutableMap = persistentMapOf(),
+ metadata: MutableList,
) : OrchestratedDocumentViewModel(
jobType = jobType,
userId = userId,
@@ -376,6 +375,7 @@ internal class EnhancedDocumentVerificationViewModel(
captureBothSides = captureBothSides,
selfieFile = selfieFile,
extraPartnerParams = extraPartnerParams,
+ metadata = metadata,
) {
override fun saveResult(
diff --git a/lib/src/test/java/com/smileidentity/SmileIDTest.kt b/lib/src/test/java/com/smileidentity/SmileIDTest.kt
index 924fc7e90..0f8477994 100644
--- a/lib/src/test/java/com/smileidentity/SmileIDTest.kt
+++ b/lib/src/test/java/com/smileidentity/SmileIDTest.kt
@@ -227,7 +227,7 @@ class SmileIDTest {
every { prepUploadRequestAdapter.fromJson(any()) } returns prepUploadRequest
every {
- prepUploadRequest.copy(any(), any(), any(), any(), any(), any(), any(), any())
+ prepUploadRequest.copy(any(), any(), any(), any(), any(), any(), any(), any(), any())
} returns prepUploadRequest
coEvery { api.authenticate(any()) } returns authResponse
diff --git a/lib/src/test/java/com/smileidentity/viewmodel/SelfieViewModelTest.kt b/lib/src/test/java/com/smileidentity/viewmodel/SelfieViewModelTest.kt
index b0264e531..bbd817c70 100644
--- a/lib/src/test/java/com/smileidentity/viewmodel/SelfieViewModelTest.kt
+++ b/lib/src/test/java/com/smileidentity/viewmodel/SelfieViewModelTest.kt
@@ -5,6 +5,7 @@ import com.smileidentity.R
import com.smileidentity.util.StringResource
import com.smileidentity.util.randomJobId
import com.smileidentity.util.randomUserId
+import com.ujizin.camposer.state.CamSelector
import io.mockk.confirmVerified
import io.mockk.every
import io.mockk.mockk
@@ -31,6 +32,7 @@ class SelfieViewModelTest {
jobId = randomJobId(),
allowNewEnroll = false,
skipApiSubmission = false,
+ metadata = mutableListOf(),
)
}
@@ -61,7 +63,7 @@ class SelfieViewModelTest {
subject.shouldAnalyzeImages = false
// when
- subject.analyzeImage(proxy)
+ subject.analyzeImage(proxy, CamSelector.Back)
// then
verify(exactly = 1) { proxy.close() }
diff --git a/lib/src/test/java/com/smileidentity/viewmodel/SmartSelfieV2ViewModelTest.kt b/lib/src/test/java/com/smileidentity/viewmodel/SmartSelfieV2ViewModelTest.kt
index b31a6d7fc..ad525d58a 100644
--- a/lib/src/test/java/com/smileidentity/viewmodel/SmartSelfieV2ViewModelTest.kt
+++ b/lib/src/test/java/com/smileidentity/viewmodel/SmartSelfieV2ViewModelTest.kt
@@ -23,6 +23,7 @@ class SmartSelfieV2ViewModelTest {
extraPartnerParams = persistentMapOf(),
selfieQualityModel = mockk(),
faceDetector = mockk(),
+ metadata = mutableListOf(),
onResult = {},
)
}
diff --git a/lib/src/test/java/com/smileidentity/viewmodel/document/DocumentCaptureViewModelTest.kt b/lib/src/test/java/com/smileidentity/viewmodel/document/DocumentCaptureViewModelTest.kt
index 81e847742..c4b0618e8 100644
--- a/lib/src/test/java/com/smileidentity/viewmodel/document/DocumentCaptureViewModelTest.kt
+++ b/lib/src/test/java/com/smileidentity/viewmodel/document/DocumentCaptureViewModelTest.kt
@@ -36,6 +36,7 @@ class DocumentCaptureViewModelTest {
side = DocumentCaptureSide.Front,
knownAspectRatio = null,
objectDetector = objectDetector,
+ metadata = mutableListOf(),
)
}
diff --git a/lib/src/test/java/com/smileidentity/viewmodel/document/DocumentViewModelTest.kt b/lib/src/test/java/com/smileidentity/viewmodel/document/DocumentViewModelTest.kt
index 72213cf27..4a7716c14 100644
--- a/lib/src/test/java/com/smileidentity/viewmodel/document/DocumentViewModelTest.kt
+++ b/lib/src/test/java/com/smileidentity/viewmodel/document/DocumentViewModelTest.kt
@@ -56,6 +56,7 @@ class DocumentViewModelTest {
documentType = "ID_CARD",
selfieFile = selfieFile,
captureBothSides = false,
+ metadata = mutableListOf(),
)
SmileID.config = Config(
partnerId = "partnerId",
diff --git a/lib/src/test/java/com/smileidentity/viewmodel/document/EnhancedDocumentVerificationViewModelTest.kt b/lib/src/test/java/com/smileidentity/viewmodel/document/EnhancedDocumentVerificationViewModelTest.kt
index aeea36e06..297c557f3 100644
--- a/lib/src/test/java/com/smileidentity/viewmodel/document/EnhancedDocumentVerificationViewModelTest.kt
+++ b/lib/src/test/java/com/smileidentity/viewmodel/document/EnhancedDocumentVerificationViewModelTest.kt
@@ -56,6 +56,7 @@ class EnhancedDocumentVerificationViewModelTest {
documentType = "ID_CARD",
selfieFile = selfieFile,
captureBothSides = false,
+ metadata = mutableListOf(),
)
SmileID.config = Config(
partnerId = "partnerId",
diff --git a/sample/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml
index 1181beeab..373ddc543 100644
--- a/sample/src/main/res/values/colors.xml
+++ b/sample/src/main/res/values/colors.xml
@@ -10,4 +10,5 @@
#FFDBDBC4
#FFE2DCD5
#FFF9F0E7
+ @color/si_color_background_dark