Skip to content
Merged
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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'
```
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ repositories {
mavenCentral()
}

project.version = "2025.2.2"
project.version = "2026.1.0"
ext {
VERSION_CODE = 202502002
VERSION_CODE = 202601000
}

android {
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/simprints/libsimprints/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ 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

/**
* 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
Expand All @@ -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")
Expand Down Expand Up @@ -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) },
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Identification> {
override fun compareTo(other: Identification): Int = when {
confidence == other.confidence -> 0
Expand All @@ -24,20 +28,33 @@ 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<Identification>.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()

fun fromJson(jsonString: String): List<Identification>? = fromJsonArrayString(jsonString) { json ->
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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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())

Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading