From 464b17783b9d4fe32840c4f4b1bdb392f36d3ca5 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 27 Apr 2026 14:23:58 +0300 Subject: [PATCH 1/3] MS-1203 Add MFID related flags to identification model --- .../libsimprints/contracts/VersionsList.kt | 5 ++++ .../contracts/data/Identification.kt | 25 ++++++++++++++++--- .../contracts/data/IdentificationTest.kt | 2 ++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/simprints/libsimprints/contracts/VersionsList.kt b/src/main/java/com/simprints/libsimprints/contracts/VersionsList.kt index b9731a7..97b6327 100644 --- a/src/main/java/com/simprints/libsimprints/contracts/VersionsList.kt +++ b/src/main/java/com/simprints/libsimprints/contracts/VersionsList.kt @@ -18,4 +18,9 @@ object VersionsList { * In this version deviceId and app version name fields were added to the response data */ const val ADDED_DEVICE_FIELDS = 20250200 + + /** + * In this version optional external credentials field was added to identification response. + */ + const val ADDED_EXTERNAL_CREDENTIALS = 20260200 } diff --git a/src/main/java/com/simprints/libsimprints/contracts/data/Identification.kt b/src/main/java/com/simprints/libsimprints/contracts/data/Identification.kt index b826f06..67578f3 100644 --- a/src/main/java/com/simprints/libsimprints/contracts/data/Identification.kt +++ b/src/main/java/com/simprints/libsimprints/contracts/data/Identification.kt @@ -5,14 +5,18 @@ import org.json.JSONArray /** * This constructor creates a new identification result * - * @param guid Global unique id of the verified person - * @param confidence An int containing the (matching) confidence - * @param confidenceBand Confidence qualifier based on the project's configuration + * @param guid Global unique id of the verified person + * @param confidence An int containing the (matching) confidence + * @param confidenceBand Confidence qualifier based on the project's configuration + * @param isLinkedToCredential GUID is linked to a credential in the system, null if external credential collection is not enabled + * @param isCredentialVerified Linked credential has been successfully verified, null if external credential collection is not enabled */ data class Identification( val guid: String, val confidence: Float, val confidenceBand: ConfidenceBand, + val isLinkedToCredential: Boolean? = null, + val isCredentialVerified: Boolean? = null, ) : Comparable { override fun compareTo(other: Identification): Int = when { confidence == other.confidence -> 0 @@ -24,12 +28,16 @@ data class Identification( private const val KEY_GUID = "guid" private const val KEY_CONFIDENCE = "confidence" private const val KEY_CONFIDENCE_BAND = "confidenceBand" + private const val KEY_IS_LINKED_TO_CREDENTIAL = "isLinkedToCredential" + private const val KEY_IS_CREDENTIAL_VERIFIED = "isVerified" fun List.toJson(): String = map { id -> id.asJsonObject { json -> json.put(KEY_GUID, id.guid) json.put(KEY_CONFIDENCE_BAND, id.confidenceBand.name) json.put(KEY_CONFIDENCE, id.confidence) + json.put(KEY_IS_LINKED_TO_CREDENTIAL, id.isLinkedToCredential) + json.put(KEY_IS_CREDENTIAL_VERIFIED, id.isCredentialVerified) } }.let { JSONArray(it) }.toString() @@ -37,7 +45,16 @@ data class Identification( val guid = json.getString(KEY_GUID) val confidence = json.getDouble(KEY_CONFIDENCE).toFloat() val band = json.getString(KEY_CONFIDENCE_BAND).let { ConfidenceBand.valueOf(it) } - Identification(guid, confidence, band) + + val isLinkedToCredential = json + .takeUnless { it.isNull(KEY_IS_LINKED_TO_CREDENTIAL) } + ?.getBoolean(KEY_IS_LINKED_TO_CREDENTIAL) + + val isCredentialVerified = json + .takeUnless { it.isNull(KEY_IS_CREDENTIAL_VERIFIED) } + ?.getBoolean(KEY_IS_CREDENTIAL_VERIFIED) + + Identification(guid, confidence, band, isLinkedToCredential, isCredentialVerified) } } } diff --git a/src/test/java/com/simprints/libsimprints/contracts/data/IdentificationTest.kt b/src/test/java/com/simprints/libsimprints/contracts/data/IdentificationTest.kt index b69dcd6..396efb4 100644 --- a/src/test/java/com/simprints/libsimprints/contracts/data/IdentificationTest.kt +++ b/src/test/java/com/simprints/libsimprints/contracts/data/IdentificationTest.kt @@ -15,6 +15,8 @@ class IdentificationTest { Identification("case2", 70f, ConfidenceBand.MEDIUM), Identification("case3", 50f, ConfidenceBand.LOW), Identification("case4", 30f, ConfidenceBand.NONE), + Identification("case5", 30f, ConfidenceBand.HIGH, isLinkedToCredential = true), + Identification("case6", 30f, ConfidenceBand.LOW, isLinkedToCredential = false, isCredentialVerified = true), ) val actual = Identification.fromJson(expected.toJson()) From c0021cdace44843146923e4572a913d33becea1b Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 27 Apr 2026 14:25:49 +0300 Subject: [PATCH 2/3] MS-1203 Update library version --- README.md | 4 ++-- build.gradle | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 81baac6..e00d049 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ **Installation** ``` -implementation 'com.simprints:libsimprints:2025.2.2' +implementation 'com.simprints:libsimprints:2026.1.0' ``` [Documentation](https://simprints.gitbook.io/docs/development/simprints-for-developers/integrating-with-simprints) @@ -20,5 +20,5 @@ implementation 'com.simprints:libsimprints:2025.2.2' maven("https://oss.sonatype.org/content/repositories/snapshots") // In app level gradle file -implementation 'com.simprints:libsimprints:2025.2.2-SNAPSHOT' +implementation 'com.simprints:libsimprints:2026.1.0-SNAPSHOT' ``` diff --git a/build.gradle b/build.gradle index 3f72609..281a773 100644 --- a/build.gradle +++ b/build.gradle @@ -20,9 +20,9 @@ repositories { mavenCentral() } -project.version = "2025.2.2" +project.version = "2026.1.0" ext { - VERSION_CODE = 202502002 + VERSION_CODE = 202601000 } android { From 4b6fcab6a216a0680ecd5306ba1d12c7c2417d31 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 27 Apr 2026 15:21:01 +0300 Subject: [PATCH 3/3] Added scanned credential field --- .../com/simprints/libsimprints/Constants.kt | 3 ++ .../contracts/SimprintsResponse.kt | 10 ++++++- .../contracts/data/ScannedCredential.kt | 29 +++++++++++++++++++ .../contracts/SimprintsResponseTest.kt | 16 +++++++++- .../contracts/data/ScannedCredentialTest.kt | 17 +++++++++++ 5 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/simprints/libsimprints/contracts/data/ScannedCredential.kt create mode 100644 src/test/java/com/simprints/libsimprints/contracts/data/ScannedCredentialTest.kt diff --git a/src/main/java/com/simprints/libsimprints/Constants.kt b/src/main/java/com/simprints/libsimprints/Constants.kt index c14b147..1d92f68 100644 --- a/src/main/java/com/simprints/libsimprints/Constants.kt +++ b/src/main/java/com/simprints/libsimprints/Constants.kt @@ -93,6 +93,9 @@ object Constants { // When SIMPRINTS_BIOMETRICS_COMPLETE_CHECK is true, the user has completed the Simprints flow const val SIMPRINTS_BIOMETRICS_COMPLETE_CHECK = "biometricsComplete" + const val SIMPRINTS_HAS_CREDENTIAL = "hasCredential" + const val SIMPRINTS_SCANNED_CREDENTIAL = "scannedCredential" + // These two values represent data that could be null. They only apply to projects using cosync const val SIMPRINTS_COSYNC_EVENT = "events" const val SIMPRINTS_COSYNC_SUBJECT_ACTIONS = "subjectActions" diff --git a/src/main/java/com/simprints/libsimprints/contracts/SimprintsResponse.kt b/src/main/java/com/simprints/libsimprints/contracts/SimprintsResponse.kt index 5df35db..5563ce3 100644 --- a/src/main/java/com/simprints/libsimprints/contracts/SimprintsResponse.kt +++ b/src/main/java/com/simprints/libsimprints/contracts/SimprintsResponse.kt @@ -5,6 +5,7 @@ import com.simprints.libsimprints.Constants import com.simprints.libsimprints.contracts.data.Enrolment import com.simprints.libsimprints.contracts.data.Identification import com.simprints.libsimprints.contracts.data.RefusalForm +import com.simprints.libsimprints.contracts.data.ScannedCredential import com.simprints.libsimprints.contracts.data.Verification import kotlin.let @@ -12,7 +13,7 @@ import kotlin.let * Container class for all possible response data that Simprints ID can return. * * In all cases at most one of - * * registration: [Registration] - in case of enrolment + * * enrolment: [Enrolment] - in case of enrolment * * verification: [Verification] - in case of verification * * identifications: List<[Identification]> - in case of identification * * refusal: [RefusalForm] - in case of patient refusal @@ -33,6 +34,9 @@ data class SimprintsResponse( val refusal: RefusalForm? = null, // Co-sync data val subjectActions: String? = null, + // MFID data + val hasCredential: Boolean? = null, + val scannedCredential: ScannedCredential? = null, ) { companion object { @Suppress("UNCHECKED_CAST") @@ -70,6 +74,10 @@ data class SimprintsResponse( .getStringExtra(Constants.SIMPRINTS_VERIFICATION) ?.let { Verification.fromJson(it) }, subjectActions = intent.getStringExtra(Constants.SIMPRINTS_COSYNC_SUBJECT_ACTIONS), + hasCredential = intent.getBooleanExtra(Constants.SIMPRINTS_HAS_CREDENTIAL, false), + scannedCredential = intent + .getStringExtra(Constants.SIMPRINTS_SCANNED_CREDENTIAL) + ?.let { ScannedCredential.fromJson(it) }, ) } } diff --git a/src/main/java/com/simprints/libsimprints/contracts/data/ScannedCredential.kt b/src/main/java/com/simprints/libsimprints/contracts/data/ScannedCredential.kt new file mode 100644 index 0000000..77836e8 --- /dev/null +++ b/src/main/java/com/simprints/libsimprints/contracts/data/ScannedCredential.kt @@ -0,0 +1,29 @@ +package com.simprints.libsimprints.contracts.data + +/** + * This constructor creates a new scanned credential response. + * + * @param type The type of the credential (QR, NHIS Card, Ghana ID Card, etc). + * @param value The value of the credential. + */ +data class ScannedCredential( + val type: String, + val value: String, +) { + fun toJson(): String = asJsonObject { + it.put(KEY_TYPE, type) + it.put(KEY_VALUE, value) + }.toString() + + companion object { + private const val KEY_TYPE = "type" + private const val KEY_VALUE = "value" + + fun fromJson(jsonString: String): ScannedCredential? = fromJsonString(jsonString) { json -> + val type = json.getString(KEY_TYPE) + val value = json.getString(KEY_VALUE) + + ScannedCredential(type, value) + } + } +} diff --git a/src/test/java/com/simprints/libsimprints/contracts/SimprintsResponseTest.kt b/src/test/java/com/simprints/libsimprints/contracts/SimprintsResponseTest.kt index 98990f5..7519e5d 100644 --- a/src/test/java/com/simprints/libsimprints/contracts/SimprintsResponseTest.kt +++ b/src/test/java/com/simprints/libsimprints/contracts/SimprintsResponseTest.kt @@ -1,17 +1,19 @@ package com.simprints.libsimprints.contracts import android.content.Intent -import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.ext.junit.runners.* import com.simprints.libsimprints.Constants import com.simprints.libsimprints.contracts.data.ConfidenceBand import com.simprints.libsimprints.contracts.data.Enrolment import com.simprints.libsimprints.contracts.data.Identification import com.simprints.libsimprints.contracts.data.Identification.Companion.toJson import com.simprints.libsimprints.contracts.data.RefusalForm +import com.simprints.libsimprints.contracts.data.ScannedCredential import com.simprints.libsimprints.contracts.data.Verification import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue import org.junit.Test import org.junit.runner.RunWith @@ -122,4 +124,16 @@ class SimprintsResponseTest { assertNull(result.identifications) assertNotNull(result.verification) } + + @Test + fun `correctly parses MFID fields intent`() { + val intent = Intent() + .putExtra(Constants.SIMPRINTS_HAS_CREDENTIAL, true) + .putExtra(Constants.SIMPRINTS_SCANNED_CREDENTIAL, ScannedCredential("type", "value").toJson()) + val result = SimprintsResponse.fromIntent(intent, Constants.SIMPRINTS_OK) + + assertEquals(Constants.SIMPRINTS_OK, result.resultCode) + assertTrue(result.hasCredential == true) + assertNotNull(result.scannedCredential) + } } diff --git a/src/test/java/com/simprints/libsimprints/contracts/data/ScannedCredentialTest.kt b/src/test/java/com/simprints/libsimprints/contracts/data/ScannedCredentialTest.kt new file mode 100644 index 0000000..1bb9725 --- /dev/null +++ b/src/test/java/com/simprints/libsimprints/contracts/data/ScannedCredentialTest.kt @@ -0,0 +1,17 @@ +package com.simprints.libsimprints.contracts.data + +import androidx.test.ext.junit.runners.* +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ScannedCredentialTest { + @Test + fun `test scanned credential parcelling`() { + val expected = ScannedCredential("QRCode", "123456") + val actual = ScannedCredential.fromJson(expected.toJson()) + + Assert.assertEquals(expected, actual) + } +}