diff --git a/README.md b/README.md
index 2bf59fb..ea619e1 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-# Acuant Android SDK v11.5.4
-**September 2022**
+# Acuant Android SDK v11.6.0
+**February 2023**
See [https://github.com/Acuant/AndroidSDKV11/releases](https://github.com/Acuant/AndroidSDKV11/releases) for release notes.
@@ -22,7 +22,7 @@ This document provides detailed information about the Acuant Android SDK. The Ac
## Updates
-**v11.5.0:** Please review [Migration Details](docs/MigrationDetails.md) for migration details (last updated for v11.5.0).
+**v11.6.0:** Please review [Migration Details](docs/MigrationDetails.md) for migration details (last updated for v11.6.0).
----------
@@ -116,15 +116,15 @@ The SDK includes the following modules:
- Add the following dependencies
- implementation 'com.acuant:acuantcommon:11.5.4'
- implementation 'com.acuant:acuantcamera:11.5.4'
- implementation 'com.acuant:acuantimagepreparation:11.5.4'
- implementation 'com.acuant:acuantdocumentprocessing:11.5.4'
- implementation 'com.acuant:acuantechipreader:11.5.4'
- implementation 'com.acuant:acuantipliveness:11.5.4'
- implementation 'com.acuant:acuantfacematch:11.5.4'
- implementation 'com.acuant:acuantfacecapture:11.5.4'
- implementation 'com.acuant:acuantpassiveliveness:11.5.4'
+ implementation 'com.acuant:acuantcommon:11.6.0'
+ implementation 'com.acuant:acuantcamera:11.6.0'
+ implementation 'com.acuant:acuantimagepreparation:11.6.0'
+ implementation 'com.acuant:acuantdocumentprocessing:11.6.0'
+ implementation 'com.acuant:acuantechipreader:11.6.0'
+ implementation 'com.acuant:acuantipliveness:11.6.0'
+ implementation 'com.acuant:acuantfacematch:11.6.0'
+ implementation 'com.acuant:acuantfacecapture:11.6.0'
+ implementation 'com.acuant:acuantpassiveliveness:11.6.0'
1. Create an .xml file with the following tags. (If you plan to use bearer tokens to initialize, include only the endpoints.):
@@ -191,7 +191,7 @@ Before you use the SDK, you must initialize it, either by using the credentials
AcuantInitializer.initializeWithToken("PATH/TO/CONFIG/FILENAME.XML",
token,
context,
- listOf(ImageProcessorInitializer(), EchipInitializer(), MrzCameraInitializer()),
+ listOf(MrzCameraInitializer()),
listener)
} catch(e: AcuantException) {
Log.e("Acuant Error", e.toString())
@@ -202,7 +202,7 @@ Before you use the SDK, you must initialize it, either by using the credentials
try {
AcuantInitializer.initialize("PATH/TO/CONFIG/FILENAME.XML",
context,
- listOf(ImageProcessorInitializer(), EchipInitializer(), MrzCameraInitializer()),
+ listOf(MrzCameraInitializer()),
listener)
} catch(e: AcuantException) {
Log.e("Acuant Error", e.toString())
@@ -211,19 +211,22 @@ Before you use the SDK, you must initialize it, either by using the credentials
* Using credentials hardcoded in the code (not recommended):
try {
- Credential.init("xxxxxxx",
- "xxxxxxxx",
- "xxxxxxxxxx",
- "https://frm.acuant.net",
- "https://services.assureid.net",
- "https://medicscan.acuant.net",
- "https://us.passlive.acuant.net",
- "https://acas.acuant.net",
- "https://ozone.acuant.net")
+ Credential.init(
+ username: String,
+ password: String,
+ subscription: String?,
+ acasEndpoint: String,
+ assureIdEndpoint: String? = null,
+ frmEndpoint: String? = null,
+ passiveLivenessEndpoint: String? = null,
+ ipLivenessEndpoint: String? = null,
+ ozoneEndpoint: String? = null,
+ healthInsuranceEndpoint: String? = null
+ )
AcuantInitializer.initialize(null,
context,
- listOf(ImageProcessorInitializer(), EchipInitializer(), MrzCameraInitializer()),
+ listOf(MrzCameraInitializer()),
listener)
} catch(e: AcuantException) {
Log.e("Acuant Error", e.toString())
@@ -273,7 +276,7 @@ The SDK can be initialized by providing only a username and a password. However,
if (result.resultCode == RESULT_OK) {
val data: Intent? = result.data
- val url = data?.getStringExtra(ACUANT_EXTRA_IMAGE_URL)
+ val bytes = AcuantCameraActivity.getLatestCapturedBytes(clearBytesAfterRead = true)
val barcodeString = data?.getStringExtra(ACUANT_EXTRA_PDF417_BARCODE)
//...
} else if (result.resultCode == RESULT_CANCELED) {
@@ -284,7 +287,9 @@ The SDK can be initialized by providing only a username and a password. However,
if (error is AcuantError) {
//...
}
- }
+ }
+
+1. (Optional) Add localized strings in app's string resources as indicated [here](#language-localization)
### Capturing a document barcode ###
@@ -321,6 +326,7 @@ The SDK can be initialized by providing only a username and a password. However,
//...
}
}
+1. (Optional) Add localized strings in app's string resources as indicated [here](#language-localization)
### Capturing MRZ data in a passport document ###
@@ -334,7 +340,7 @@ The SDK can be initialized by providing only a username and a password. However,
Capturing the MRZ data using AcuantCamera is similar to document capture.
- 1. Start camera activity:
+ 1. Start camera activity:
val cameraIntent = Intent(
this@MainActivity,
@@ -350,8 +356,7 @@ The SDK can be initialized by providing only a username and a password. However,
//start activity for result
- 1. Get activity result:
-
+ 1. Get activity result:
if (result.resultCode == RESULT_OK) {
val data: Intent? = result.data
@@ -365,8 +370,10 @@ The SDK can be initialized by providing only a username and a password. However,
if (error is AcuantError) {
//...
}
- }
-
+ }
+
+ 1. (Optional) Add localized strings in app's string resources as indicated [here](#language-localization)
+
----------
## AcuantImagePreparation ##
@@ -387,7 +394,7 @@ This section describes how to use **AcuantImagePreparation**.
passing in the cropping data:
- class CroppingData(imageUrlString: String)
+ class CroppingData(imageBytes: ByteArray)
and a callback listener:
@@ -397,9 +404,9 @@ This section describes how to use **AcuantImagePreparation**.
* **Important note:** Most listeners/callbacks in the SDK are extended off of AcuantListener, which contains the onError function shown below.
- interface AcuantListener {
- fun onError(error: AcuantError)
- }
+ interface AcuantListener {
+ fun onError(error: AcuantError)
+ }
The **AcuantImage** can be used to verify the crop, sharpness, and glare of the image, and then upload the document in the next step (see [AcuantDocumentProcessing](#acuantdocumentprocessing)).
@@ -435,7 +442,7 @@ After you capture a document image and completed crop, it can be processed using
class IdInstanceOptions (
val authenticationSensitivity: AuthenticationSensitivity,
- val tamperSensitivity: TamperSensitivity,
+ val tamperSensitivity: AuthenticationSensitivity,
val countryCode: String?
)
@@ -533,7 +540,7 @@ For most workflows, the steps resemble the following, with reuploads on error or
}
})
-3. Get the facial capture result (call after onSuccess in IPLivenessListener):
+1. Get the facial capture result (call after onSuccess in IPLivenessListener):
AcuantIPLiveness.getFacialLiveness(
token,
@@ -549,7 +556,9 @@ For most workflows, the steps resemble the following, with reuploads on error or
}
)
--------------------------------------
+1. (Optional) Add localized strings in app's string resources as indicated [here](#language-localization)
+
+----------
## AcuantFaceCapture ##
@@ -581,11 +590,13 @@ This module is used to automate capturing an image of a face appropriate for use
//error...
}
+3. (Optional) Add localized strings in app's string resources as indicated [here](#language-localization)
+
**Note:** HGLiveness/Blink Test Liveness can be accessed by modifying the options as follows:
FaceCaptureOptions(cameraMode = CameraMode.HgLiveness)
--------------------------------------
+----------
## AcuantPassiveLiveness ##
@@ -602,11 +613,17 @@ This module is used to determine liveness from a single selfie image.
}
class PassiveLivenessResult {
- var livenessAssessment: LivenessAssessment? = null
- var transactionId: String? = null
- var score = 0
- var errorDesc: String? = null
- var errorCode: PassiveLivenessErrorCode? = null
+ var livenessResult: LivenessResult?
+ var transactionId: String?
+ var errorDesc: String?
+ var unparsedErrorCode: String?
+ val errorCode: PassiveLivenessErrorCode?
+ }
+
+ class LivenessResult {
+ var unparsedLivenessAssessment: String?
+ val livenessAssessment: LivenessAssessment?
+ var score: Int
}
enum class LivenessAssessment {
@@ -695,6 +712,105 @@ Must include EchipInitializer() in initialization (See **Initializing the SDK**)
-------------------------------------
+## Language localization
+
+In order to display texts in the corresponding language you need to add the following strings to your app's strings resources:
+
+#### AcuantCamera
+
+ Info
+ This sample needs camera permission.
+ This device doesn\'t support Camera2 API.
+
+ ALIGN
+ ALIGN AND TAP
+ MOVE CLOSER
+ TOO CLOSE
+ NOT IN FRAME
+ HOLD STEADY
+ CAPTURING
+
+ Reading MRZ
+ Align
+ Move Closer
+ Try Repositioning
+ Read Successful
+
+ Capturing...
+ Capture Barcode
+
+#### AcuantFaceCapture
+
+ Align face to start capture
+ Too close! Move away
+ Move closer
+ Face has angle. Do not tilt
+ Move in frame
+ Hold steady
+ Please blink
+ Capturing…
+
+ - Capturing\n%d…
+ - Capturing\n%d…
+
+
+#### AcuantIPLiveness
+
+ en
+ Put your face in the oval
+ Fill the oval with your face
+ Put your face in the frame
+ Connecting…
+ Tap the screen to begin
+ Move closer
+ Go somewhere shadier
+ Streaming…
+ Streaming, network is slow…
+ Scanning…
+ Identifying face…
+ Confirming identity…
+ Assessing genuine presence…
+ Assessing liveness…
+ Loading…
+ Creating identity…
+ Finding face…
+ Authenticate
+ Enrol
+ %1$s to %2$s
+ Too close
+ Ambiguous outcome
+ Please do not move while iProoving
+ Ambient light too strong or screen brightness too low
+ Strong light source detected behind you
+ Your environment appears too dark
+ Too much light detected on your face
+ Please do not talk while iProoving
+ Your session has expired
+ %1$s as %2$s to %3$s
+ Avoid tilting your head
+ Avoid tilting your head
+ Turn slightly to your right
+ Turn slightly to your left
+ Hold the device at eye level
+ Hold the device at eye level
+ Scan completed
+ Get ready…
+ Loading…
+ Network error
+ Device is not supported
+ Camera error
+ Camera permission denied
+ Please allow camera access for this app in Android Settings
+ Server error
+ Application is in multi-window mode
+ Face detector error
+ An existing capture is already in progress
+ Before calling IProov.launch(), you should register a listener with IProov.registerListener()
+ Invalid iProov options
+ Unexpected error
+
+----------
+
### Error codes ###
object ErrorCodes {
@@ -896,13 +1012,39 @@ Must include EchipInitializer() in initialization (See **Initializing the SDK**)
## Frequently Asked Questions ##
+#### Why is the SDK so large ####
+
+The SDK is large because there are several ml-kit models bundled into it. This bundling has pros and cons. Bundling models into the SDK enables it to work in areas and on devices that do not have access to Google Play services. If the size of the SDK is an issue, you can reduce the size by downloading the models from Google Play services the first time the application is launched. The download can occur in the background. To enable the download, use the open versions of the face capture and camera modules. Within the Gradles of those models, remove the following lines:
+
+ implementation 'com.google.mlkit:face-detection:XXX'
+ implementation 'com.google.mlkit:barcode-scanning:XXX'
+
+and replace them with the following respectively (where XXX is the current version number on the previous lines):
+
+ implementation 'com.google.android.gms:play-services-mlkit-face-detection:XXX'
+ implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:XXX'
+
+Then, add the following to your manifest:
+
+
+ ...
+
+
+
+**Important:** If the models can’t be accessed, the SDK behaves unexpectedly. Use this size-saving procedure only if you are sure that all of your clients will use phones that have access to Google Play from regions that don’t block it.
+
+To read more about this and the ml-kit, see https://developers.google.com/ml-kit/guides.
+
+
#### How do I obfuscate my Android application? ####
Acuant does not provide obfuscation tools. See the Android developer documentation about obfuscation at: [https://developer.android.com/studio/build/shrink-code](https://developer.android.com/studio/build/shrink-code). Then open proguard-rules.pro for your project and set the obfuscation rules.
-------------------------------------
-**Copyright 2022 Acuant Inc. All rights reserved.**
+**Copyright 2023 Acuant Inc. All rights reserved.**
This document contains proprietary and confidential information and creative works owned by Acuant and its respective licensors, if any. Any use, copying, publication, distribution, display, modification, or transmission of such technology, in whole or in part, in any form or by any means, without the prior express written permission of Acuant is strictly prohibited. Except where expressly provided by Acuant in writing, possession of this information shall not be construed to confer any license or rights under any Acuant intellectual property rights, whether by estoppel, implication, or otherwise.
diff --git a/acuantcamera/build.gradle b/acuantcamera/build.gradle
index 019dafa..bcc4d05 100644
--- a/acuantcamera/build.gradle
+++ b/acuantcamera/build.gradle
@@ -3,11 +3,11 @@ apply plugin: 'kotlin-android'
apply plugin: 'maven-publish'
android {
- compileSdkVersion 32
+ compileSdkVersion 33
defaultConfig {
minSdkVersion 21
- targetSdkVersion 32
+ targetSdkVersion 33
}
buildTypes {
@@ -33,11 +33,11 @@ android {
}
dependencies {
// Kotlin lang
- implementation 'androidx.core:core-ktx:1.8.0'
- implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
+ implementation 'androidx.core:core-ktx:1.9.0'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1'
// App compat and UI things
- implementation 'androidx.appcompat:appcompat:1.4.2'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
@@ -49,9 +49,9 @@ dependencies {
implementation "androidx.camera:camera-view:$camerax_version"
//acuant specific stuff
- implementation 'androidx.exifinterface:exifinterface:1.3.3'
+ implementation 'androidx.exifinterface:exifinterface:1.3.5'
implementation 'com.google.mlkit:barcode-scanning:17.0.2'
implementation 'com.rmtheis:tess-two:9.1.0'
- implementation 'com.acuant:acuantcommon:11.5.4'
- implementation 'com.acuant:acuantimagepreparation:11.5.4'
+ implementation 'com.acuant:acuantcommon:11.6.0'
+ implementation 'com.acuant:acuantimagepreparation:11.6.0'
}
diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantBaseCameraFragment.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantBaseCameraFragment.kt
index c029a14..82fea31 100644
--- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantBaseCameraFragment.kt
+++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantBaseCameraFragment.kt
@@ -354,14 +354,20 @@ abstract class AcuantBaseCameraFragment: Fragment() {
val exif = Exif.createFromFileString(savedUri)
val rotation = exif.rotation
+ val file = File(savedUri)
+
if (captureType != null) {
- addExif(File(savedUri), captureType, rotation)
+ addExif(file, captureType, rotation)
} else {
- addExif(File(savedUri), "NOT SPECIFIED (implementer used deprecated constructor that lacks this data)", rotation)
+ addExif(file, "NOT SPECIFIED (implementer used deprecated constructor that lacks this data)", rotation)
}
fullyDone = true
- listener.onSaved(savedUri)
+
+ val bytes = file.readBytes()
+ file.delete()
+
+ listener.onSaved(bytes)
}
override fun onError(exception: ImageCaptureException) {
diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantCameraActivity.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantCameraActivity.kt
index 98372a2..7971d1b 100644
--- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantCameraActivity.kt
+++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantCameraActivity.kt
@@ -1,6 +1,7 @@
package com.acuant.acuantcamera.camera
import android.content.Intent
+import android.os.Build
import android.os.Bundle
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
@@ -24,14 +25,17 @@ class AcuantCameraActivity: AppCompatActivity(), ICameraActivityFinish {
super.onCreate(savedInstanceState)
binding = ActivityCameraBinding.inflate(layoutInflater)
- val unserializedOptions = intent.getSerializableExtra(ACUANT_EXTRA_CAMERA_OPTIONS)
- val options: AcuantCameraOptions = if (unserializedOptions == null) {
- AcuantCameraOptions.DocumentCameraOptionsBuilder().build()
+ var options = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ intent?.getSerializableExtra(ACUANT_EXTRA_CAMERA_OPTIONS, AcuantCameraOptions::class.java)
} else {
- unserializedOptions as AcuantCameraOptions
+ @Suppress("DEPRECATION")
+ intent?.getSerializableExtra(ACUANT_EXTRA_CAMERA_OPTIONS) as AcuantCameraOptions?
}
+ if (options == null)
+ options = AcuantCameraOptions.DocumentCameraOptionsBuilder().build()
+
if (options.preventScreenshots) {
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
}
@@ -59,10 +63,11 @@ class AcuantCameraActivity: AppCompatActivity(), ICameraActivityFinish {
}
}
- //Camera Responses
- override fun onCameraDone(imageUrl: String, barCodeString: String?) {
+ override fun onCameraDone(imageBytes: ByteArray, barCodeString: String?) {
val intent = Intent()
- intent.putExtra(ACUANT_EXTRA_IMAGE_URL, imageUrl)
+ //We can not do this as the maximum transaction size is way too small.
+// intent.putExtra(ACUANT_EXTRA_IMAGE_BYTES, imageBytes)
+ AcuantCameraActivity.imageBytes = imageBytes
intent.putExtra(ACUANT_EXTRA_PDF417_BARCODE, barCodeString)
this@AcuantCameraActivity.setResult(RESULT_OK, intent)
this@AcuantCameraActivity.finish()
@@ -109,4 +114,23 @@ class AcuantCameraActivity: AppCompatActivity(), ICameraActivityFinish {
super.onResume()
hideTopMenu()
}
+
+ companion object {
+ private var imageBytes: ByteArray? = null
+
+ @JvmStatic
+ @Synchronized
+ fun getLatestCapturedBytes(clearBytesAfterRead: Boolean): ByteArray? {
+ val bytes = imageBytes?.clone()
+ if (clearBytesAfterRead) {
+ if (imageBytes != null) {
+ for (i in imageBytes!!.indices) {
+ imageBytes!![i] = (0).toByte()
+ }
+ imageBytes = null
+ }
+ }
+ return bytes
+ }
+ }
}
\ No newline at end of file
diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantCameraOptions.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantCameraOptions.kt
index 7bd3b87..0a46f36 100644
--- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantCameraOptions.kt
+++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantCameraOptions.kt
@@ -139,6 +139,7 @@ internal constructor(
}
@Deprecated("No longer reliant on GMS, option is ignored", ReplaceWith(""))
+ @Suppress("unused")
fun setUseGms(value: Boolean) : DocumentCameraOptionsBuilder {
return this
}
diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/AcuantDocCameraFragment.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/AcuantDocCameraFragment.kt
index 9ab63b4..ed241f6 100644
--- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/AcuantDocCameraFragment.kt
+++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/AcuantDocCameraFragment.kt
@@ -27,7 +27,7 @@ import kotlin.math.pow
import kotlin.math.roundToInt
import kotlin.math.sqrt
-enum class DocumentCameraState { Align, MoveCloser, MoveBack, HoldSteady, CountingDown, Capturing }
+enum class DocumentCameraState { Align, NotInFrame, MoveCloser, MoveBack, HoldSteady, CountingDown, Capturing }
class AcuantDocCameraFragment: AcuantBaseCameraFragment() {
@@ -78,7 +78,7 @@ class AcuantDocCameraFragment: AcuantBaseCameraFragment() {
if (!firstThreeTimings.contains((-1).toLong())) {
hasFinishedTest = true
- if (firstThreeTimings.minOrNull() ?: (TOO_SLOW_FOR_AUTO_THRESHOLD + 10) > TOO_SLOW_FOR_AUTO_THRESHOLD) {
+ if ((firstThreeTimings.minOrNull() ?: (TOO_SLOW_FOR_AUTO_THRESHOLD + 10)) > TOO_SLOW_FOR_AUTO_THRESHOLD) {
setTapToCapture()
}
}
@@ -96,8 +96,8 @@ class AcuantDocCameraFragment: AcuantBaseCameraFragment() {
detectedPoints = PointsUtils.fixPoints(PointsUtils.scalePoints(detectedPoints, camContainer, analyzerSize, previewSize, rectangleView))
realDpi = PointsUtils.scaleDpi(analyzerDPI, analyzerSize, imageCapture?.resolutionInfo?.resolution)
if (previewSize != null) {
- val mult = 0.02f
- val view = Rect((previewSize.left * (1 + mult)).toInt(), (previewSize.top * (1 + mult)).toInt(), (previewSize.right * (1 - mult)).toInt(), (previewSize.bottom * (1 - mult)).toInt())
+ val insetFromEdges = 0.02f
+ val view = Rect((previewSize.left * (1 + insetFromEdges)).toInt(), (previewSize.top * (1 + insetFromEdges)).toInt(), (previewSize.right * (1 - insetFromEdges)).toInt(), (previewSize.bottom * (1 - insetFromEdges)).toInt())
var isContained = true
detectedPoints.forEach {
if (!view.contains(it.y, it.x)) {
@@ -107,7 +107,7 @@ class AcuantDocCameraFragment: AcuantBaseCameraFragment() {
if (isContained) {
docState
} else {
- DocumentState.NoDocument
+ DocumentState.OutOfBounds
}
} else {
docState
@@ -122,6 +122,11 @@ class AcuantDocCameraFragment: AcuantBaseCameraFragment() {
setTextFromState(DocumentCameraState.Align)
resetWorkflow()
}
+ DocumentState.OutOfBounds -> {
+ rectangleView?.setViewFromState(DocumentCameraState.NotInFrame)
+ setTextFromState(DocumentCameraState.NotInFrame)
+ resetWorkflow()
+ }
DocumentState.TooClose -> {
rectangleView?.setViewFromState(DocumentCameraState.MoveBack)
setTextFromState(DocumentCameraState.MoveBack)
@@ -160,8 +165,8 @@ class AcuantDocCameraFragment: AcuantBaseCameraFragment() {
else -> {
val middle = PointsUtils.findMiddleForCamera(points, fragmentCameraBinding?.root?.width, fragmentCameraBinding?.root?.height)
captureImage(object : IAcuantSavedImage {
- override fun onSaved(uri: String) {
- cameraActivityListener.onCameraDone(uri, latestBarcode)
+ override fun onSaved(bytes: ByteArray) {
+ cameraActivityListener.onCameraDone(bytes, latestBarcode)
}
override fun onError(error: AcuantError) {
@@ -246,7 +251,16 @@ class AcuantDocCameraFragment: AcuantBaseCameraFragment() {
context?.resources?.getDimension(R.dimen.cam_info_width)?.toInt() ?: 300
textView.textSize =
context?.resources?.getDimension(R.dimen.cam_doc_font) ?: 24f
- textView.text = getString(R.string.acuant_camera_not_in_frame)
+ textView.text = getString(R.string.acuant_camera_too_close)
+ textView.setTextColor(Color.WHITE)
+ }
+ DocumentCameraState.NotInFrame -> {
+ textView.background = defaultTextDrawable
+ textView.layoutParams?.width =
+ context?.resources?.getDimension(R.dimen.cam_info_width)?.toInt() ?: 300
+ textView.textSize =
+ context?.resources?.getDimension(R.dimen.cam_doc_font) ?: 24f
+ textView.text = getString(R.string.acuant_camera_out_of_bounds)
textView.setTextColor(Color.WHITE)
}
DocumentCameraState.CountingDown -> {
@@ -350,8 +364,8 @@ class AcuantDocCameraFragment: AcuantBaseCameraFragment() {
textView?.setBackgroundColor(greenTransparent)
textView?.text = getString(R.string.acuant_camera_capturing)
captureImage(object : IAcuantSavedImage {
- override fun onSaved(uri: String) {
- cameraActivityListener.onCameraDone(uri, latestBarcode)
+ override fun onSaved(bytes: ByteArray) {
+ cameraActivityListener.onCameraDone(bytes, latestBarcode)
}
override fun onError(error: AcuantError) {
diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/constant/Constants.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/constant/Constants.kt
index c3934f1..8346c1b 100644
--- a/acuantcamera/src/main/java/com/acuant/acuantcamera/constant/Constants.kt
+++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/constant/Constants.kt
@@ -2,6 +2,8 @@
package com.acuant.acuantcamera.constant
+@Suppress("unused")
+@Deprecated("This form of returning an image has been removed", ReplaceWith("AcuantCameraActivity.getLatestCapturedBytes()"))
const val ACUANT_EXTRA_IMAGE_URL = "imageUrl"
const val ACUANT_EXTRA_PDF417_BARCODE = "barCodeString"
const val ACUANT_EXTRA_CAMERA_OPTIONS = "cameraOptions"
diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/DocumentFrameResult.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/DocumentFrameResult.kt
index b6d8b93..1c19dcc 100644
--- a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/DocumentFrameResult.kt
+++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/DocumentFrameResult.kt
@@ -4,7 +4,7 @@ import android.graphics.Point
enum class DocumentType {Id, Passport, Other}
-enum class DocumentState { NoDocument, TooFar, TooClose, GoodDocument }
+enum class DocumentState { NoDocument, TooFar, TooClose, GoodDocument, OutOfBounds }
class DocumentFrameResult (val points: Array?,
val currentDistRatio: Float?,
diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/initializer/MrzCameraInitializer.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/initializer/MrzCameraInitializer.kt
index e66f4f9..8f436c1 100644
--- a/acuantcamera/src/main/java/com/acuant/acuantcamera/initializer/MrzCameraInitializer.kt
+++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/initializer/MrzCameraInitializer.kt
@@ -15,20 +15,16 @@ import java.lang.Exception
class MrzCameraInitializer : IAcuantPackage {
override fun initialize(credential: Credential, context: Context, callback: IAcuantPackageCallback) {
- if(credential.secureAuthorizations != null) {
- try {
- initializeOcr(context)
- } catch (e: Exception) {
- e.printStackTrace()
- callback.onInitializeFailed(
- listOf(AcuantError(ErrorCodes.ERROR_FailedToLoadOcrFiles,
- ErrorDescriptions.ERROR_DESC_FailedToLoadOcrFiles, e.toString())))
- return
- }
- callback.onInitializeSuccess()
- } else {
- callback.onInitializeFailed(listOf(AcuantError(ErrorCodes.ERROR_InvalidCredentials, ErrorDescriptions.ERROR_DESC_InvalidCredentials)))
+ try {
+ initializeOcr(context)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ callback.onInitializeFailed(
+ listOf(AcuantError(ErrorCodes.ERROR_FailedToLoadOcrFiles,
+ ErrorDescriptions.ERROR_DESC_FailedToLoadOcrFiles, e.toString())))
+ return
}
+ callback.onInitializeSuccess()
}
@Throws(IOException::class)
diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/interfaces/IAcuantSavedImage.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/interfaces/IAcuantSavedImage.kt
index 7b997d4..57d99e7 100644
--- a/acuantcamera/src/main/java/com/acuant/acuantcamera/interfaces/IAcuantSavedImage.kt
+++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/interfaces/IAcuantSavedImage.kt
@@ -3,5 +3,5 @@ package com.acuant.acuantcamera.interfaces
import com.acuant.acuantcommon.background.AcuantListener
interface IAcuantSavedImage: AcuantListener {
- fun onSaved(uri: String)
+ fun onSaved(bytes: ByteArray)
}
\ No newline at end of file
diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/interfaces/ICameraActivityFinish.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/interfaces/ICameraActivityFinish.kt
index 566e27e..2eaa96f 100644
--- a/acuantcamera/src/main/java/com/acuant/acuantcamera/interfaces/ICameraActivityFinish.kt
+++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/interfaces/ICameraActivityFinish.kt
@@ -3,8 +3,8 @@ package com.acuant.acuantcamera.interfaces
import com.acuant.acuantcamera.helper.MrzResult
import com.acuant.acuantcommon.background.AcuantListener
-interface ICameraActivityFinish : AcuantListener{
- fun onCameraDone(imageUrl: String, barCodeString: String?)
+interface ICameraActivityFinish : AcuantListener {
+ fun onCameraDone(imageBytes: ByteArray, barCodeString: String?)
fun onCameraDone(mrzResult: MrzResult)
fun onCameraDone(barCodeString: String)
fun onCancel()
diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/BaseRectangleView.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/BaseRectangleView.kt
index 4236939..1ddb067 100644
--- a/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/BaseRectangleView.kt
+++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/BaseRectangleView.kt
@@ -104,8 +104,8 @@ abstract class BaseRectangleView(context: Context, attr: AttributeSet?) : View(c
textureViewWidth = width
}
- fun setAndDrawPoints(points:Array?){
- if(this.points != null) {
+ fun setAndDrawPoints(points:Array?) {
+ if (this.points != null) {
oldPoints = this.points
}
this.points = points
@@ -128,7 +128,7 @@ abstract class BaseRectangleView(context: Context, attr: AttributeSet?) : View(c
private fun calcPath() {
path = Path()
- if(points != null && points!!.size == 4) {
+ if (points != null && points!!.size == 4) {
path.moveTo(points!![3].y.toFloat(), points!![3].x.toFloat())
path.lineTo(points!![0].y.toFloat(), points!![0].x.toFloat())
@@ -139,10 +139,11 @@ abstract class BaseRectangleView(context: Context, attr: AttributeSet?) : View(c
}
internal fun setDrawBox(drawBox : Boolean) {
- if(allowBox)
+ if (allowBox) {
this.drawBox = drawBox
- else
+ } else {
this.drawBox = false
+ }
}
override fun onDraw(canvas: Canvas) {
@@ -181,7 +182,7 @@ abstract class BaseRectangleView(context: Context, attr: AttributeSet?) : View(c
frame!!.top.toFloat(), frame!!.right.toFloat(), frame!!.top.toFloat(), frame!!.left.toFloat())
} else {
- if(oldPoints == null || oldPoints!!.size != 4) {
+ if (oldPoints == null || oldPoints!!.size != 4) {
drawBracketsFromCords(canvas, points!![0].x.toFloat(), points!![0].y.toFloat(), points!![1].x.toFloat(), points!![1].y.toFloat(),
points!![2].x.toFloat(), points!![2].y.toFloat(), points!![3].x.toFloat(), points!![3].y.toFloat())
} else {
diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/DocRectangleView.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/DocRectangleView.kt
index 8331e72..0c9cccb 100644
--- a/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/DocRectangleView.kt
+++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/DocRectangleView.kt
@@ -7,25 +7,16 @@ import com.acuant.acuantcamera.camera.document.DocumentCameraState
class DocRectangleView(context: Context, attr: AttributeSet?) : BaseRectangleView(context, attr) {
fun setViewFromState(state: DocumentCameraState) {
when(state) {
- DocumentCameraState.MoveCloser -> {
+ DocumentCameraState.MoveCloser,
+ DocumentCameraState.MoveBack,
+ DocumentCameraState.NotInFrame -> {
setDrawBox(false)
paint.color = paintColorCloser
paintBracket.color = paintColorBracketCloser
animateTarget = false
}
- DocumentCameraState.MoveBack -> {
- setDrawBox(false)
- paint.color = paintColorCloser
- paintBracket.color = paintColorBracketCloser
- animateTarget = false
- }
- DocumentCameraState.CountingDown -> {
- setDrawBox(true)
- paint.color = paintColorHold
- paintBracket.color = paintColorBracketHold
- animateTarget = true
- }
- DocumentCameraState.HoldSteady -> {
+ DocumentCameraState.CountingDown,
+ DocumentCameraState.HoldSteady-> {
setDrawBox(true)
paint.color = paintColorHold
paintBracket.color = paintColorBracketHold
diff --git a/acuantcamera/src/main/res/values/strings.xml b/acuantcamera/src/main/res/values/strings.xml
index 98114b4..2b0430b 100644
--- a/acuantcamera/src/main/res/values/strings.xml
+++ b/acuantcamera/src/main/res/values/strings.xml
@@ -25,7 +25,8 @@
ALIGN
ALIGN AND TAP
MOVE CLOSER
- TOO CLOSE!
+ TOO CLOSE
+ NOT IN FRAME
HOLD STEADY
CAPTURING
@@ -35,10 +36,10 @@
Try Repositioning
Read Successful
Camera Load Error
- ok
+ OK
This application cannot run because it does not have the camera permission. The application will now exit.
The camera is required to capture documents. If the permission has been declined you will need to manually go to the app settings to enable it.
An error occurred while starting the camera, possibly in use by another application.
- Capturing...
+ Capturing…
Capture Barcode
diff --git a/acuantfacecapture/build.gradle b/acuantfacecapture/build.gradle
index 10969e1..2cf78f2 100644
--- a/acuantfacecapture/build.gradle
+++ b/acuantfacecapture/build.gradle
@@ -3,11 +3,11 @@ apply plugin: 'kotlin-android'
apply plugin: 'maven-publish'
android {
- compileSdkVersion 32
+ compileSdkVersion 33
defaultConfig {
minSdkVersion 21
- targetSdkVersion 32
+ targetSdkVersion 33
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
@@ -29,11 +29,11 @@ android {
}
dependencies {
// Kotlin lang
- implementation 'androidx.core:core-ktx:1.8.0'
+ implementation 'androidx.core:core-ktx:1.9.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1'
// App compat and UI things
- implementation 'androidx.appcompat:appcompat:1.4.2'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
@@ -46,8 +46,8 @@ dependencies {
//acuant specific stuff
implementation 'com.google.mlkit:face-detection:16.1.5'
- implementation 'com.acuant:acuantcommon:11.5.4'
- implementation 'com.acuant:acuantimagepreparation:11.5.4'
+ implementation 'com.acuant:acuantcommon:11.6.0'
+ implementation 'com.acuant:acuantimagepreparation:11.6.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
diff --git a/acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/camera/AcuantFaceCameraActivity.kt b/acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/camera/AcuantFaceCameraActivity.kt
index 89798ba..db06769 100644
--- a/acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/camera/AcuantFaceCameraActivity.kt
+++ b/acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/camera/AcuantFaceCameraActivity.kt
@@ -1,6 +1,7 @@
package com.acuant.acuantfacecapture.camera
import android.content.Intent
+import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
@@ -30,14 +31,16 @@ class AcuantFaceCameraActivity: AppCompatActivity(), IFaceCameraActivityFinish {
setContentView(binding.root)
hideTopMenu()
- val unserializedOptions = intent.getSerializableExtra(ACUANT_EXTRA_FACE_CAPTURE_OPTIONS)
-
- val options: FaceCaptureOptions = if (unserializedOptions == null) {
- FaceCaptureOptions()
+ var options = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ intent?.getSerializableExtra(ACUANT_EXTRA_FACE_CAPTURE_OPTIONS, FaceCaptureOptions::class.java)
} else {
- unserializedOptions as FaceCaptureOptions
+ @Suppress("DEPRECATION")
+ intent?.getSerializableExtra(ACUANT_EXTRA_FACE_CAPTURE_OPTIONS) as FaceCaptureOptions?
}
+ if (options == null)
+ options = FaceCaptureOptions()
+
//start the camera if this is the first time the activity is created (camera already exists otherwise)
if (savedInstanceState == null) {
val cameraFragment: AcuantBaseFaceCameraFragment = when (options.cameraMode) {
diff --git a/acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/camera/facecapture/AcuantFaceCaptureFragment.kt b/acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/camera/facecapture/AcuantFaceCaptureFragment.kt
index 7b52440..128e53e 100644
--- a/acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/camera/facecapture/AcuantFaceCaptureFragment.kt
+++ b/acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/camera/facecapture/AcuantFaceCaptureFragment.kt
@@ -36,6 +36,7 @@ class AcuantFaceCaptureFragment: AcuantBaseFaceCameraFragment() {
private var userHasBlinked = false
private var userHasHadOpenEyes = false
private var lastState: FaceState = FaceState.NoFace
+ private var isMovingCloser = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -95,7 +96,7 @@ class AcuantFaceCaptureFragment: AcuantBaseFaceCameraFragment() {
startTime = System.currentTimeMillis()
}
- private fun onFaceDetected(rect: Rect?, state: FaceState) {
+ private fun onFaceDetected(rect: Rect?, state: FaceState, sizeRatio: Float) {
if (capturing || !isAdded)
return
val analyzerSize = imageAnalyzer?.resolutionInfo?.resolution
@@ -126,6 +127,7 @@ class AcuantFaceCaptureFragment: AcuantBaseFaceCameraFragment() {
}
FaceState.FaceTooFar -> {
mFacialGraphicOverlay?.setState(FaceCameraState.MoveCloser)
+ isMovingCloser = true
mFacialGraphic?.updateLiveFaceDetails(boundingBox, FaceCameraState.MoveCloser)
resetTimer()
}
@@ -140,40 +142,47 @@ class AcuantFaceCaptureFragment: AcuantBaseFaceCameraFragment() {
resetTimer()
}
else -> { //good face or closed eyes
- when {
- didFaceMove(boundingBox, lastFacePosition) -> {
- mFacialGraphicOverlay?.setState(FaceCameraState.KeepSteady)
- mFacialGraphic?.updateLiveFaceDetails(boundingBox, FaceCameraState.KeepSteady)
- resetTimer()
- }
- requireBlink && !userHasBlinked -> {
- if (userHasHadOpenEyes && lastState == FaceState.EyesClosed && realState == FaceState.GoodFace) {
- userHasBlinked = true
+ if (isMovingCloser && sizeRatio < FaceFrameAnalyzer.TOO_FAR_THRESH_WITH_BUFFER) {
+ mFacialGraphicOverlay?.setState(FaceCameraState.MoveCloser)
+ mFacialGraphic?.updateLiveFaceDetails(boundingBox, FaceCameraState.MoveCloser)
+ resetTimer()
+ } else {
+ isMovingCloser = false
+ when {
+ didFaceMove(boundingBox, lastFacePosition) -> {
+ mFacialGraphicOverlay?.setState(FaceCameraState.KeepSteady)
+ mFacialGraphic?.updateLiveFaceDetails(boundingBox, FaceCameraState.KeepSteady)
+ resetTimer()
}
- if (realState == FaceState.GoodFace) {
- userHasHadOpenEyes = true
+ requireBlink && !userHasBlinked -> {
+ if (userHasHadOpenEyes && lastState == FaceState.EyesClosed && realState == FaceState.GoodFace) {
+ userHasBlinked = true
+ }
+ if (realState == FaceState.GoodFace) {
+ userHasHadOpenEyes = true
+ }
+ mFacialGraphicOverlay?.setState(FaceCameraState.Blink)
+ mFacialGraphic?.updateLiveFaceDetails(boundingBox, FaceCameraState.Blink)
+ resetTimer(resetBlinkState = false)
}
- mFacialGraphicOverlay?.setState(FaceCameraState.Blink)
- mFacialGraphic?.updateLiveFaceDetails(boundingBox, FaceCameraState.Blink)
- resetTimer(resetBlinkState = false)
- }
- System.currentTimeMillis() - startTime < acuantOptions.totalCaptureTime * 1000 -> {
- mFacialGraphicOverlay?.setState(FaceCameraState.Hold, acuantOptions.totalCaptureTime - ceil(((System.currentTimeMillis() - startTime) / 1000).toDouble()).toInt())
- mFacialGraphic?.updateLiveFaceDetails(boundingBox, FaceCameraState.Hold)
- }
- else -> {
- mFacialGraphicOverlay?.setState(FaceCameraState.Capturing)
- mFacialGraphic?.updateLiveFaceDetails(boundingBox, FaceCameraState.Capturing)
- if (!capturing) {
- captureImage(object : IAcuantSavedImage {
- override fun onSaved(uri: String) {
- cameraActivityListener.onCameraDone(uri)
- }
-
- override fun onError(error: AcuantError) {
- cameraActivityListener.onError(error)
- }
- })
+ System.currentTimeMillis() - startTime < acuantOptions.totalCaptureTime * 1000 -> {
+ mFacialGraphicOverlay?.setState(FaceCameraState.Hold, acuantOptions.totalCaptureTime - ceil(((System.currentTimeMillis() - startTime) / 1000).toDouble()).toInt())
+ mFacialGraphic?.updateLiveFaceDetails(boundingBox, FaceCameraState.Hold)
+ }
+ else -> {
+ mFacialGraphicOverlay?.setState(FaceCameraState.Capturing)
+ mFacialGraphic?.updateLiveFaceDetails(boundingBox, FaceCameraState.Capturing)
+ if (!capturing) {
+ captureImage(object : IAcuantSavedImage {
+ override fun onSaved(uri: String) {
+ cameraActivityListener.onCameraDone(uri)
+ }
+
+ override fun onError(error: AcuantError) {
+ cameraActivityListener.onError(error)
+ }
+ })
+ }
}
}
}
@@ -185,8 +194,8 @@ class AcuantFaceCaptureFragment: AcuantBaseFaceCameraFragment() {
override fun buildImageAnalyzer(screenAspectRatio: Int, rotation: Int) {
val frameAnalyzer = try {
- FaceFrameAnalyzer { boundingBox, state ->
- onFaceDetected(boundingBox, state)
+ FaceFrameAnalyzer { boundingBox, state, sizeRatio ->
+ onFaceDetected(boundingBox, state, sizeRatio)
}
} catch (e: IllegalStateException) {
cameraActivityListener.onError(AcuantError(ErrorCodes.ERROR_UnexpectedError, ErrorDescriptions.ERROR_DESC_UnexpectedError, e.toString()))
@@ -203,7 +212,7 @@ class AcuantFaceCaptureFragment: AcuantBaseFaceCameraFragment() {
}
companion object {
- private const val MOVEMENT_THRESHOLD = 22
+ private const val MOVEMENT_THRESHOLD = 32
private fun didFaceMove(facePosition: Rect?, lastFacePosition: Rect?): Boolean {
if (facePosition == null || lastFacePosition == null)
diff --git a/acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/detector/FaceFrameAnalyzer.kt b/acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/detector/FaceFrameAnalyzer.kt
index 4287466..81aebc7 100644
--- a/acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/detector/FaceFrameAnalyzer.kt
+++ b/acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/detector/FaceFrameAnalyzer.kt
@@ -11,7 +11,7 @@ import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
-typealias FaceFrameListener = (boundingBox: Rect?, state: FaceState) -> Unit
+typealias FaceFrameListener = (boundingBox: Rect?, state: FaceState, sizeRatio: Float) -> Unit
enum class FaceState {
NoFace, FaceTooFar, FaceTooClose, FaceAngled, EyesClosed, GoodFace
@@ -32,6 +32,7 @@ class FaceFrameAnalyzer internal constructor(private val listener: FaceFrameList
}
var faceState = FaceState.NoFace
var detectedBounds: Rect? = null
+ var sizeRatio = 0f
running = true
val mediaImage = image.image //don't close this one
@@ -49,7 +50,7 @@ class FaceFrameAnalyzer internal constructor(private val listener: FaceFrameList
// like the shape of a face
bounds = Rect((bounds.centerX() - bounds.width() * 0.5f * 0.85f).toInt(), bounds.top, (bounds.centerX() + bounds.width() * 0.5f * 0.85f).toInt(), bounds.bottom)
detectedBounds = bounds
- val sizeRatio = sizeRatio(bounds, origSize)
+ sizeRatio = sizeRatio(bounds, origSize)
val rotY = face.headEulerAngleY
val rotZ = face.headEulerAngleZ
@@ -63,7 +64,8 @@ class FaceFrameAnalyzer internal constructor(private val listener: FaceFrameList
abs(rotY) > Y_ROT_ANGLE || abs(rotZ) > Z_ROT_ANGLE -> {
faceState = FaceState.FaceAngled
}
- face.rightEyeOpenProbability ?: 1f < EYE_CLOSED_THRESHOLD && face.leftEyeOpenProbability ?: 1f < EYE_CLOSED_THRESHOLD -> {
+ (face.rightEyeOpenProbability ?: 1f) < EYE_CLOSED_THRESHOLD &&
+ (face.leftEyeOpenProbability ?: 1f) < EYE_CLOSED_THRESHOLD -> {
faceState = FaceState.EyesClosed
}
else -> {
@@ -76,13 +78,13 @@ class FaceFrameAnalyzer internal constructor(private val listener: FaceFrameList
// .addOnFailureListener { e -> //catch
// }
.addOnCompleteListener { //finally
- listener(detectedBounds, faceState)
+ listener(detectedBounds, faceState, sizeRatio)
running = false
image.close()
}
} else {
running = false
- listener(detectedBounds, faceState)
+ listener(detectedBounds, faceState, sizeRatio)
image.close()
}
}
@@ -94,8 +96,9 @@ class FaceFrameAnalyzer internal constructor(private val listener: FaceFrameList
.setMinFaceSize(0.5f)
.build())
- private const val TOO_CLOSE_THRESH = 0.815f
- private const val TOO_FAR_THRESH = 0.635f
+ const val TOO_CLOSE_THRESH = 0.785f
+ const val TOO_FAR_THRESH = 0.5125f
+ const val TOO_FAR_THRESH_WITH_BUFFER = TOO_FAR_THRESH + 0.05f
private const val EYE_CLOSED_THRESHOLD = 0.3f
private const val Y_ROT_ANGLE = 10
private const val Z_ROT_ANGLE = 15
diff --git a/acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/overlays/FacialGraphic.kt b/acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/overlays/FacialGraphic.kt
index af86892..f060300 100644
--- a/acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/overlays/FacialGraphic.kt
+++ b/acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/overlays/FacialGraphic.kt
@@ -1,6 +1,8 @@
package com.acuant.acuantfacecapture.overlays
+import android.animation.ValueAnimator
import android.graphics.*
+import android.view.animation.LinearInterpolator
import com.acuant.acuantfacecapture.camera.facecapture.FaceCameraState
import com.acuant.acuantfacecapture.model.FaceCaptureOptions
@@ -15,6 +17,9 @@ internal class FacialGraphic(overlay: FacialGraphicOverlay) : FacialGraphicOverl
private var state = FaceCameraState.Align
private val mFaceRectPaint: Paint = Paint()
private var width = 0
+ private var bracketAnimator : ValueAnimator? = null
+ private var distanceMoved : Float = 0f
+ private var oldPoints: Rect? = null
init {
mFaceRectPaint.color = options?.colorGood ?: Color.GREEN
@@ -30,6 +35,19 @@ internal class FacialGraphic(overlay: FacialGraphicOverlay) : FacialGraphicOverl
fun updateLiveFaceDetails(faceBounds: Rect?, state: FaceCameraState) {
this.faceBounds = faceBounds
this.state = state
+ if (this.faceBounds != null) {
+ oldPoints = this.faceBounds
+ }
+ bracketAnimator?.cancel()
+ bracketAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
+ addUpdateListener {
+ distanceMoved = it.animatedValue as Float
+ postInvalidate()
+ }
+ duration = 250L
+ interpolator = LinearInterpolator()
+ start()
+ }
postInvalidate()
}
@@ -49,19 +67,57 @@ internal class FacialGraphic(overlay: FacialGraphicOverlay) : FacialGraphicOverl
else -> mFaceRectPaint.color = options?.colorGood ?: Color.GREEN
}
- if (options != null && options!!.showOval) {
- drawFaceOval(canvas)
- } else {
- drawFaceBrackets(canvas)
+ val options = options
+ if (options != null) {
+ drawFace(canvas, options.showOval)
}
canvas.restore()
}
- private fun drawFaceBrackets(canvas: Canvas) {
+ private fun drawFace(canvas: Canvas, oval: Boolean) {
val position = faceBounds
+ val oldPoints = oldPoints
if (position != null)
{
- drawBracketsFromCords(canvas, position.bottom.toFloat(), position.left.toFloat(), position.bottom.toFloat(), position.right.toFloat(), position.top.toFloat(), position.right.toFloat(), position.top.toFloat(), position.left.toFloat())
+ if (oldPoints == null) {
+ if (oval) {
+ drawFaceOval(canvas, position.left.toFloat(),
+ position.top.toFloat(), position.right.toFloat(),
+ position.bottom.toFloat())
+ } else {
+ drawBracketsFromCords(
+ canvas,
+ position.bottom.toFloat(),
+ position.left.toFloat(),
+ position.bottom.toFloat(),
+ position.right.toFloat(),
+ position.top.toFloat(),
+ position.right.toFloat(),
+ position.top.toFloat(),
+ position.left.toFloat()
+ )
+ }
+ } else {
+ if (oval) {
+ val bottom = oldPoints.bottom + (position.bottom - oldPoints.bottom) * distanceMoved
+ val left = oldPoints.left + (position.left - oldPoints.left) * distanceMoved
+ val right = oldPoints.right + (position.right - oldPoints.right) * distanceMoved
+ val top = oldPoints.top + (position.top - oldPoints.top) * distanceMoved
+
+ drawFaceOval(canvas, left, top, right, bottom)
+ } else {
+ val x0 = oldPoints.bottom + (position.bottom - oldPoints.bottom) * distanceMoved
+ val y0 = oldPoints.left + (position.left - oldPoints.left) * distanceMoved
+ val x1 = oldPoints.bottom + (position.bottom - oldPoints.bottom) * distanceMoved
+ val y1 = oldPoints.right + (position.right - oldPoints.right) * distanceMoved
+ val x2 = oldPoints.top + (position.top - oldPoints.top) * distanceMoved
+ val y2 = oldPoints.right + (position.right - oldPoints.right) * distanceMoved
+ val x3 = oldPoints.top + (position.top - oldPoints.top) * distanceMoved
+ val y3 = oldPoints.left + (position.left - oldPoints.left) * distanceMoved
+
+ drawBracketsFromCords(canvas, x0, y0, x1, y1, x2, y2, x3, y3)
+ }
+ }
}
}
@@ -85,12 +141,10 @@ internal class FacialGraphic(overlay: FacialGraphicOverlay) : FacialGraphicOverl
}
- private fun drawFaceOval(canvas: Canvas) {
+ private fun drawFaceOval(canvas: Canvas, left: Float, top: Float, right: Float, bottom: Float) {
val position = faceBounds
if (position != null) {
- canvas.drawOval(position.left.toFloat(),
- position.top.toFloat(), position.right.toFloat(),
- position.bottom.toFloat(), mFaceRectPaint)
+ canvas.drawOval(left, top, right, bottom, mFaceRectPaint)
}
}
diff --git a/app/build.gradle b/app/build.gradle
index 39d8404..f3d920e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -5,13 +5,13 @@ repositories {
maven { url 'https://raw.githubusercontent.com/iProov/android/master/maven/' }
}
dependencies {
- implementation "androidx.core:core-ktx:1.8.0"
- implementation 'androidx.appcompat:appcompat:1.4.2'
+ implementation "androidx.core:core-ktx:1.9.0"
+ implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
- implementation 'androidx.exifinterface:exifinterface:1.3.3'
+ implementation 'androidx.exifinterface:exifinterface:1.3.5'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
- implementation 'com.google.android.material:material:1.6.1'
- implementation "androidx.activity:activity-ktx:1.5.1"
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation "androidx.activity:activity-ktx:1.6.1"
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
@@ -19,26 +19,26 @@ dependencies {
//If you do not plan to modify the camera code you can use these two regular maven dependencies
// in place of the project ones.
-// implementation 'com.acuant:acuantfacecapture:11.5.4'
-// implementation 'com.acuant:acuantcamera:11.5.4'
+// implementation 'com.acuant:acuantfacecapture:11.6.0'
+// implementation 'com.acuant:acuantcamera:11.6.0'
implementation project(path: ':acuantcamera')
implementation project(path: ':acuantfacecapture')
- implementation 'com.acuant:acuantcommon:11.5.4'
- implementation 'com.acuant:acuantimagepreparation:11.5.4'
- implementation 'com.acuant:acuantdocumentprocessing:11.5.4'
- implementation 'com.acuant:acuantechipreader:11.5.4'
- implementation 'com.acuant:acuantfacematch:11.5.4'
- implementation 'com.acuant:acuantipliveness:11.5.4'
- implementation 'com.acuant:acuantpassiveliveness:11.5.4'
+ implementation 'com.acuant:acuantcommon:11.6.0'
+ implementation 'com.acuant:acuantimagepreparation:11.6.0'
+ implementation 'com.acuant:acuantdocumentprocessing:11.6.0'
+ implementation 'com.acuant:acuantechipreader:11.6.0'
+ implementation 'com.acuant:acuantfacematch:11.6.0'
+ implementation 'com.acuant:acuantipliveness:11.6.0'
+ implementation 'com.acuant:acuantpassiveliveness:11.6.0'
}
android {
- compileSdkVersion 32
- buildToolsVersion '32'
+ compileSdkVersion 33
+ buildToolsVersion '33'
defaultConfig {
applicationId "com.acuant.sampleapp"
minSdkVersion 21
- targetSdkVersion 32
+ targetSdkVersion 33
versionCode 1
versionName
multiDexEnabled true
diff --git a/app/src/main/java/com/acuant/sampleapp/MainActivity.kt b/app/src/main/java/com/acuant/sampleapp/MainActivity.kt
index 1f1d7ab..a7b58a5 100644
--- a/app/src/main/java/com/acuant/sampleapp/MainActivity.kt
+++ b/app/src/main/java/com/acuant/sampleapp/MainActivity.kt
@@ -32,9 +32,12 @@ import com.acuant.acuantcommon.model.*
import com.acuant.acuantcommon.helper.CredentialHelper
import com.acuant.acuantdocumentprocessing.AcuantDocumentProcessor
import com.acuant.acuantdocumentprocessing.model.*
-import com.acuant.acuantdocumentprocessing.resultmodel.*
+import com.acuant.acuantdocumentprocessing.documentresultmodel.*
+import com.acuant.acuantdocumentprocessing.documentresultmodel.enums.DocumentDataType
+import com.acuant.acuantdocumentprocessing.documentresultmodel.enums.DocumentSide
+import com.acuant.acuantdocumentprocessing.documentresultmodel.enums.LightSource
+import com.acuant.acuantdocumentprocessing.healthinsuranceresultmodel.HealthInsuranceCardResult
import com.acuant.acuantdocumentprocessing.service.listener.*
-import com.acuant.acuantechipreader.initializer.EchipInitializer
import com.acuant.acuantfacecapture.camera.AcuantFaceCameraActivity
import com.acuant.acuantfacecapture.constant.Constants.ACUANT_EXTRA_FACE_CAPTURE_OPTIONS
import com.acuant.acuantfacecapture.constant.Constants.ACUANT_EXTRA_FACE_IMAGE_URL
@@ -46,7 +49,6 @@ import com.acuant.acuantfacematchsdk.model.FacialMatchResult
import com.acuant.acuantfacematchsdk.service.FacialMatchListener
import com.acuant.acuantimagepreparation.AcuantImagePreparation
import com.acuant.acuantimagepreparation.background.EvaluateImageListener
-import com.acuant.acuantimagepreparation.initializer.ImageProcessorInitializer
import com.acuant.acuantimagepreparation.model.AcuantImage
import com.acuant.acuantimagepreparation.model.CroppingData
import com.acuant.acuantipliveness.AcuantIPLiveness
@@ -59,6 +61,8 @@ import com.acuant.acuantpassiveliveness.AcuantPassiveLiveness
import com.acuant.acuantpassiveliveness.model.PassiveLivenessData
import com.acuant.acuantpassiveliveness.model.PassiveLivenessResult
import com.acuant.acuantpassiveliveness.service.PassiveLivenessListener
+import com.acuant.sampleapp.NfcResultActivity.Companion.FACE_LIVENESS_RESULT
+import com.acuant.sampleapp.NfcResultActivity.Companion.FACE_MATCH_RESULT
import com.acuant.sampleapp.backgroundtasks.AcuantTokenService
import com.acuant.sampleapp.backgroundtasks.AcuantTokenServiceListener
import com.acuant.sampleapp.utils.CommonUtils
@@ -66,8 +70,6 @@ import com.google.android.material.switchmaterial.SwitchMaterial
import java.io.*
import java.net.HttpURLConnection
import java.net.URL
-import java.util.*
-import kotlin.collections.HashMap
import kotlin.concurrent.thread
class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
@@ -78,8 +80,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
private var isHealthCard: Boolean = false
private var capturingImageData: Boolean = true
private var capturingSelfieImage: Boolean = false
- private var capturingFacialMatch: Boolean = false
- private var facialResultString: String? = null
+ private var faceMatchResultString: String? = null
private var facialLivelinessResultString: String? = null
private var documentIdInstance: AcuantIdDocumentInstance? = null
private var documentHealthInstance: AcuantHealthInsuranceDocumentInstance? = null
@@ -89,6 +90,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
private var livenessSelected = 0
private var isKeyless = false
private var processingFacialLiveness = false
+ private var processingFacialMatch: Boolean = false
private var usingPassive = true
private var recentImage: AcuantImage? = null
private val useTokenInit = true
@@ -126,7 +128,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
}
fun cleanUpTransaction() {
- facialResultString = null
+ faceMatchResultString = null
capturedFrontImage = null
capturedBackImage = null
capturedSelfieImage = null
@@ -193,15 +195,18 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
private fun initializeAcuantSdk(callback: IAcuantPackageCallback) {
try {
// Or, if required to initialize without a config file , then can be done the following way
- /*Credential.init("**username**",
- "**password**",
- "**subscription**",
- "https://frm.acuant.net",
- "https://services.assureid.net",
- "https://medicscan.acuant.net",
- "https://us.passlive.acuant.net",
- "https://acas.acuant.net",
- "https://ozone.acuant.net")
+ /*Credential.init(
+ username: String,
+ password: String,
+ subscription: String?,
+ acasEndpoint: String,
+ assureIdEndpoint: String? = null,
+ frmEndpoint: String? = null,
+ passiveLivenessEndpoint: String? = null,
+ ipLivenessEndpoint: String? = null,
+ ozoneEndpoint: String? = null,
+ healthInsuranceEndpoint: String? = null
+ )
AcuantInitializer.initialize(null, ...proceed as normal from here...
*/
@@ -224,7 +229,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
override fun onInitializeSuccess() {
isInitialized = true
- if(Credential.get().subscription == null || Credential.get().subscription.isEmpty() ){
+ if (Credential.get().subscription?.isEmpty() != false) {
isKeyless = true
livenessSpinner.visibility = View.INVISIBLE
callback.onInitializeSuccess()
@@ -259,7 +264,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
val task = AcuantInitializer.initializeWithToken("acuant.config.xml",
token,
this@MainActivity,
- listOf(ImageProcessorInitializer(), EchipInitializer(), MrzCameraInitializer()),
+ listOf(MrzCameraInitializer()),
initCallback)
if (task != null)
backgroundTasks.add(task)
@@ -281,26 +286,31 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
if (useTokenInit) {
Toast.makeText(this@MainActivity, "Using Token Init", Toast.LENGTH_SHORT).show()
Credential.initFromXml("acuant.config.xml", this)
- if (Credential.get().token != null && Credential.get().token.isValid) {
- finishTokenInit(Credential.get().token.value)
+ val token = Credential.get().token
+ if (token != null && token.isValid) {
+ finishTokenInit(token.value)
} else {
Credential.removeToken()
- val task = AcuantTokenService(object : AcuantTokenServiceListener {
- override fun onSuccess(token: String) {
- finishTokenInit(token)
- }
+ if (Credential.get().endpoints.isAcasEndpointValid) {
+ val task = AcuantTokenService(object : AcuantTokenServiceListener {
+ override fun onSuccess(token: String) {
+ finishTokenInit(token)
+ }
- override fun onError(error: AcuantError) {
- initCallback.onInitializeFailed(listOf(error))
- }
- }).execute()
- backgroundTasks.add(task)
+ override fun onError(error: AcuantError) {
+ initCallback.onInitializeFailed(listOf(error))
+ }
+ }).execute()
+ backgroundTasks.add(task)
+ } else {
+ initCallback.onInitializeFailed(listOf(AcuantError(ErrorCodes.ERROR_InvalidEndpoint, ErrorDescriptions.ERROR_DESC_InvalidEndpoint)))
+ }
}
} else {
Toast.makeText(this@MainActivity, "Using Credential Init", Toast.LENGTH_SHORT).show()
val task = AcuantInitializer.initialize("acuant.config.xml",
this@MainActivity,
- listOf(ImageProcessorInitializer(), EchipInitializer(), MrzCameraInitializer()),
+ listOf(MrzCameraInitializer()),
initCallback)
if (task != null)
backgroundTasks.add(task)
@@ -466,7 +476,6 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
}
override fun onError(error: AcuantError) {
- capturingSelfieImage = false
facialLivelinessResultString = "Facial Liveliness Failed"
showAcuDialog(error, "Error Retrieving Facial Data")
}
@@ -499,7 +508,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
if (!hasInternetConnection()) {
showAcuDialog("No internet connection available.")
} else {
- if (isInitialized && (!useTokenInit || Credential.get()?.token?.isValid == true)) {
+ if (isInitialized && (!useTokenInit || Credential.get().token?.isValid == true)) {
cleanUpTransaction()
showDocumentCaptureCamera()
} else {
@@ -526,7 +535,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
if (!hasInternetConnection()) {
showAcuDialog("No internet connection available.")
} else {
- if (isInitialized && (!useTokenInit || Credential.get()?.token?.isValid == true)) {
+ if (isInitialized && (!useTokenInit || Credential.get().token?.isValid == true)) {
cleanUpTransaction()
isHealthCard = true
showDocumentCaptureCamera()
@@ -556,7 +565,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
if (!hasInternetConnection()) {
showAcuDialog("No internet connection available.")
} else {
- if (isInitialized && (!useTokenInit || Credential.get()?.token?.isValid == true)) {
+ if (isInitialized && (!useTokenInit || Credential.get().token?.isValid == true)) {
cleanUpTransaction()
showMrzHelpScreen()
} else {
@@ -592,11 +601,11 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
private var docCameraLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
val data: Intent? = result.data
- val url = data?.getStringExtra(ACUANT_EXTRA_IMAGE_URL)
+ val bytes = AcuantCameraActivity.getLatestCapturedBytes(clearBytesAfterRead = true)
capturedBarcodeString = data?.getStringExtra(ACUANT_EXTRA_PDF417_BARCODE)
- if (url != null) {
+ if (bytes != null) {
setProgress(true, "Cropping...")
- AcuantImagePreparation.evaluateImage(this, CroppingData(url), object : EvaluateImageListener {
+ AcuantImagePreparation.evaluateImage(this, CroppingData(bytes), object : EvaluateImageListener {
override fun onSuccess(image: AcuantImage) {
@@ -621,14 +630,19 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
}
})
} else {
- showAcuDialog("Camera failed to return valid image path")
+ showAcuDialog("Camera failed to return valid image bytes")
}
} else if (result.resultCode == RESULT_CANCELED) {
Log.d(TAG, "User canceled document capture")
} else {
val data: Intent? = result.data
- val error = data?.getSerializableExtra(ACUANT_EXTRA_ERROR)
- if (error is AcuantError) {
+ val error = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ data?.getSerializableExtra(ACUANT_EXTRA_ERROR, AcuantError::class.java)
+ } else {
+ @Suppress("DEPRECATION")
+ data?.getSerializableExtra(ACUANT_EXTRA_ERROR) as AcuantError?
+ }
+ if (error != null) {
showAcuDialog(error)
}
}
@@ -655,16 +669,18 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
private var mrzCameraLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
val data: Intent? = result.data
- val mrzResult = data?.getSerializableExtra(ACUANT_EXTRA_MRZ_RESULT) as MrzResult?
-
- val confirmNFCDataActivity = Intent(this, NfcConfirmationActivity::class.java)
- confirmNFCDataActivity.putExtra("DOB", mrzResult?.dob)
- confirmNFCDataActivity.putExtra("DOE", mrzResult?.passportExpiration)
- confirmNFCDataActivity.putExtra("DOCNUMBER", mrzResult?.passportNumber)
- confirmNFCDataActivity.putExtra("COUNTRY", mrzResult?.country)
- confirmNFCDataActivity.putExtra("THREELINE", mrzResult?.threeLineMrz)
+ val mrzResult = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ data?.getSerializableExtra(ACUANT_EXTRA_MRZ_RESULT, MrzResult::class.java)
+ } else {
+ @Suppress("DEPRECATION")
+ data?.getSerializableExtra(ACUANT_EXTRA_MRZ_RESULT) as MrzResult?
+ }
- this.startActivity(confirmNFCDataActivity)
+ if (mrzResult != null) {
+ showNfcConfirmation(mrzResult)
+ } else {
+ showAcuDialog("MRZ Read Error", "MRZ was returned blank, or missformatted.")
+ }
} else if (result.resultCode == RESULT_CANCELED) {
Log.d(TAG, "User canceled mrz capture")
}
@@ -683,6 +699,52 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
mrzCameraLauncher.launch(cameraIntent)
}
+ private var nfcLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ if (result.resultCode == Activity.RESULT_OK) {
+ val nfcData = NfcStore.cardDetails
+ if (nfcData != null) {
+
+ facialLivelinessResultString = null
+ faceMatchResultString = null
+ capturingImageData = false
+
+ if (livenessSelected != 0) {
+ setProgress(true, "Getting Data...")
+ capturedDocumentFaceImage = nfcData.faceImage
+ showFaceCamera()
+ }
+
+ thread {
+ while (capturingSelfieImage || processingFacialLiveness || processingFacialMatch) {
+ Thread.sleep(100)
+ }
+
+ setProgress(false)
+
+ val intent = Intent(this@MainActivity, NfcResultActivity::class.java)
+ intent.putExtra(FACE_LIVENESS_RESULT, facialLivelinessResultString)
+ intent.putExtra(FACE_MATCH_RESULT, faceMatchResultString)
+ startActivity(intent)
+ }
+ } else {
+ //this shouldn't really happen
+ showAcuDialog("NFC Data was null", "Activity returned OK code, but no Nfc Data.")
+ }
+ } //else cancelled, we don't care
+ }
+
+ private fun showNfcConfirmation(mrzResult: MrzResult) {
+
+ val confirmNFCDataActivity = Intent(this, NfcConfirmationActivity::class.java)
+ confirmNFCDataActivity.putExtra("DOB", mrzResult.dob)
+ confirmNFCDataActivity.putExtra("DOE", mrzResult.passportExpiration)
+ confirmNFCDataActivity.putExtra("DOCNUMBER", mrzResult.passportNumber)
+ confirmNFCDataActivity.putExtra("COUNTRY", mrzResult.country)
+ confirmNFCDataActivity.putExtra("THREELINE", mrzResult.threeLineMrz)
+
+ nfcLauncher.launch(confirmNFCDataActivity)
+ }
+
private var barcodeCameraLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
@@ -728,7 +790,6 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
alert.setNegativeButton("CANCEL") { dialog, _ ->
setProgress(true, "Getting Data...")
facialLivelinessResultString = "Facial Liveliness Failed"
- capturingSelfieImage = false
getData()
dialog.dismiss()
}
@@ -901,6 +962,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
}
private var faceCameraLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ capturingSelfieImage = false
when (result.resultCode) {
RESULT_OK -> {
val data = result.data
@@ -909,14 +971,12 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
if (url == null) {
showFaceCaptureError()
} else {
- processingFacialLiveness = true
-
val bytes = readFromFile(url)
capturedSelfieImage = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
val plData = PassiveLivenessData(capturedSelfieImage as Bitmap)
AcuantPassiveLiveness.processFaceLiveness(plData, object : PassiveLivenessListener {
override fun passiveLivenessFinished(result: PassiveLivenessResult) {
- facialLivelinessResultString = when (result.livenessAssessment) {
+ facialLivelinessResultString = when (result.livenessResult?.livenessAssessment) {
AcuantPassiveLiveness.LivenessAssessment.Live -> {
"Facial Liveliness: live"
}
@@ -965,6 +1025,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
}
private var hgCameraLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ capturingSelfieImage = false
when (result.resultCode) {
RESULT_OK -> {
val data = result.data
@@ -1003,6 +1064,8 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
when (livenessSelected) {
1 -> {
capturingSelfieImage = true
+ processingFacialMatch = true
+ processingFacialLiveness = true
if (usingPassive) {
if (!isKeyless) {
showFaceCapture()
@@ -1015,6 +1078,8 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
}
else -> {
capturingSelfieImage = false
+ processingFacialMatch = false
+ processingFacialLiveness = false
//just go to results
}
}
@@ -1041,10 +1106,12 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
}
override fun onSuccess(userId: String, token: String, frame: Bitmap?) {
+ capturingSelfieImage = false
startFacialLivelinessRequest(token, userId)
}
override fun onFail(error: AcuantError) {
+ capturingSelfieImage = false
showFaceCaptureError()
}
@@ -1055,6 +1122,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
}
override fun onError(error: AcuantError) {
+ capturingSelfieImage = false
showAcuDialog(error)
}
})
@@ -1193,45 +1261,56 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
setProgress(true, "Classifying...")
val task = instance.getClassification(object : ClassificationListener {
override fun documentClassified(classified: Boolean, classification: Classification) {
- setProgress(false)
- if (classified) {
- frontCaptured = true
- barcodeExpected = classification.type?.referenceDocumentDataTypes?.contains(0) ?: false
- if (isBackSideRequired(classification)) {
- this@MainActivity.runOnUiThread {
- showAcuDialog(R.string.scan_back_side_id, "Message", { dialog, _ ->
- dialog.dismiss()
- showDocumentCaptureCamera()
- }, { dialog, _ ->
- dialog.dismiss()
- })
- }
- } else {
- val alert = AlertDialog.Builder(this@MainActivity)
- alert.setTitle("Message")
- if (livenessSelected != 0) {
- alert.setMessage("Capture Selfie Image")
+ runOnUiThread {
+ setProgress(false)
+ if (classified) {
+ frontCaptured = true
+ capturedFrontImage = null
+ barcodeExpected =
+ classification.type?.referenceDocumentDataTypes?.contains(
+ DocumentDataType.Barcode2D
+ ) ?: false
+ if (isBackSideRequired(classification)) {
+ this@MainActivity.runOnUiThread {
+ showAcuDialog(
+ R.string.scan_back_side_id,
+ "Message",
+ { dialog, _ ->
+ dialog.dismiss()
+ showDocumentCaptureCamera()
+ },
+ { dialog, _ ->
+ dialog.dismiss()
+ })
+ }
} else {
- alert.setMessage("Continue")
- }
- alert.setPositiveButton("OK") { dialog, _ ->
- dialog.dismiss()
- setProgress(true, "Getting Data...")
- showFaceCamera()
- getData()
- }
- if (livenessSelected != 0) {
- alert.setNegativeButton("CANCEL") { dialog, _ ->
- facialLivelinessResultString = "Facial Liveliness Failed"
+ val alert = AlertDialog.Builder(this@MainActivity)
+ alert.setTitle("Message")
+ if (livenessSelected != 0) {
+ alert.setMessage("Capture Selfie Image")
+ } else {
+ alert.setMessage("Continue")
+ }
+ alert.setPositiveButton("OK") { dialog, _ ->
+ dialog.dismiss()
setProgress(true, "Getting Data...")
+ showFaceCamera()
getData()
- dialog.dismiss()
}
+ if (livenessSelected != 0) {
+ alert.setNegativeButton("CANCEL") { dialog, _ ->
+ facialLivelinessResultString =
+ "Facial Liveliness Failed"
+ setProgress(true, "Getting Data...")
+ getData()
+ dialog.dismiss()
+ }
+ }
+ alert.show()
}
- alert.show()
+ } else {
+ showClassificationError()
}
- } else {
- showClassificationError()
}
}
@@ -1264,6 +1343,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
val task = instance.uploadBackImage(backData, object : UploadImageListener {
override fun imageUploaded() {
+ capturedBackImage = null
if (barcodeExpected && capturedBarcodeString != null) {
val task = instance.uploadBarcode(BarcodeData(capturedBarcodeString!!), object : UploadBarcodeListener {
override fun barcodeUploaded() {
@@ -1294,7 +1374,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
if (instance != null) {
val task = instance.getData(object : GetIdDataListener {
override fun processingResultReceived(result: IDResult) {
- if (result.fields == null || result.fields.dataFieldReferences == null) {
+ if (result.fields.isEmpty()) {
showAcuDialog("Unknown error happened.\nCould not extract data")
return
}
@@ -1305,7 +1385,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
var signImageUri: String? = null
var faceImageUri: String? = null
var resultString: String? = ""
- val fieldReferences = result.fields.dataFieldReferences
+ val fieldReferences = result.fields
for (reference in fieldReferences) {
if (reference.key == "Document Class Name" && reference.type == "string") {
if (reference.value == "Driver License") {
@@ -1314,7 +1394,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
cardType = "ID3"
}
} else if (reference.key == "Document Number" && reference.type == "string") {
- docNumber = reference.value
+ docNumber = reference.value ?: ""
} else if (reference.key == "Photo" && reference.type == "uri") {
faceImageUri = reference.value
} else if (reference.key == "Signature" && reference.type == "uri") {
@@ -1322,22 +1402,24 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
}
}
- for (image in result.images.images) {
- if (image.side == 0) {
+ val images = result.images
+ for (image in images) {
+ if (image.side == DocumentSide.Front) {
frontImageUri = image.uri
- } else if (image.side == 1) {
+ } else if (image.side == DocumentSide.Back) {
backImageUri = image.uri
}
}
for (reference in fieldReferences) {
if (reference.type == "string") {
- resultString = resultString + reference.key + ":" + reference.value + System.lineSeparator()
+ resultString =
+ resultString + reference.key + ":" + reference.value + System.lineSeparator()
}
}
resultString = "Authentication Result : " +
- AuthenticationResult.getString(Integer.parseInt(result.result)) +
+ result.result +
System.lineSeparator() +
System.lineSeparator() +
resultString
@@ -1364,7 +1446,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
}
})
- showResults(result.biographic.birthDate, result.biographic.expirationDate, docNumber, frontImage, backImage, faceImage, signImage, resultString, cardType)
+ showResults(result.biographic?.birthDate, result.biographic?.expirationDate, docNumber, frontImage, backImage, faceImage, signImage, resultString, cardType)
}
}
}
@@ -1381,7 +1463,6 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
//process Facial Match
fun processFacialMatch() {
- //MainActivity@ capturingFacialMatch = true
thread {
while (capturingImageData) {
Thread.sleep(100)
@@ -1394,26 +1475,23 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
if (facialMatchData.faceImageOne != null && facialMatchData.faceImageTwo != null) {
val task = AcuantFaceMatch.processFacialMatch(facialMatchData, object : FacialMatchListener {
override fun facialMatchFinished(result: FacialMatchResult) {
- capturingSelfieImage = false
- capturingFacialMatch = false
this@MainActivity.runOnUiThread {
- facialResultString = "isMatch: ${result.isMatch}\n"
- facialResultString += "score: ${result.score}\n"
- facialResultString += "transactionId: ${result.transactionId}\n"
+ faceMatchResultString = "isMatch: ${result.isMatch}\n"
+ faceMatchResultString += "score: ${result.score}\n"
+ faceMatchResultString += "transactionId: ${result.transactionId}\n"
}
+ processingFacialMatch = false
}
override fun onError(error: AcuantError) {
- capturingSelfieImage = false
- capturingFacialMatch = false
+ processingFacialMatch = false
showAcuDialog(error)
}
})
backgroundTasks.add(task)
} else {
- capturingSelfieImage = false
- capturingFacialMatch = false
+ processingFacialMatch = false
}
}
}
@@ -1446,12 +1524,12 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
ProcessedData.documentNumber = documentNumber
ProcessedData.cardType = cardType
thread {
- while (capturingFacialMatch || processingFacialLiveness) {
+ while (capturingSelfieImage || processingFacialLiveness || processingFacialMatch) {
Thread.sleep(100)
}
this@MainActivity.runOnUiThread {
- facialResultString = if (facialResultString == null) "Facial Match Failed" else facialResultString
- ProcessedData.formattedString = (facialLivelinessResultString ?: "No Liveness Test Performed") + System.lineSeparator() + facialResultString+ System.lineSeparator() + resultString
+ faceMatchResultString = if (faceMatchResultString == null) "Facial Match Failed" else faceMatchResultString
+ ProcessedData.formattedString = (facialLivelinessResultString ?: "No Liveness Test Performed") + System.lineSeparator() + faceMatchResultString+ System.lineSeparator() + resultString
val resultIntent = Intent(
this@MainActivity,
ResultActivity::class.java
@@ -1478,13 +1556,14 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
fun isBackSideRequired(classification : Classification?): Boolean {
var isBackSideScanRequired = false
- if (classification?.type != null && classification.type.supportedImages != null) {
- val list = classification.type.supportedImages as ArrayList>
- for (i in list.indices) {
- val map = list[i]
- if (map["Light"] == 0) {
- if (map["Side"] == 1) {
- isBackSideScanRequired = true
+ if (classification?.type != null && classification.type?.supportedImages != null) {
+ val list = classification.type?.supportedImages
+ if (list != null) {
+ for (supportedImage in list) {
+ if (supportedImage.light == LightSource.White) {
+ if (supportedImage.side == DocumentSide.Back) {
+ isBackSideScanRequired = true
+ }
}
}
}
diff --git a/app/src/main/java/com/acuant/sampleapp/NfcConfirmationActivity.kt b/app/src/main/java/com/acuant/sampleapp/NfcConfirmationActivity.kt
index f53baa7..6de9c50 100644
--- a/app/src/main/java/com/acuant/sampleapp/NfcConfirmationActivity.kt
+++ b/app/src/main/java/com/acuant/sampleapp/NfcConfirmationActivity.kt
@@ -209,14 +209,15 @@ class NfcConfirmationActivity : AppCompatActivity(), NfcTagReadingListener {
}
override fun tagReadSucceeded(nfcData: NfcData) {
- setProgress(false)
- error = false
- val intent = Intent(this, NfcResultActivity::class.java)
- NfcStore.cardDetails = nfcData
- startActivity(intent)
if (this.nfcAdapter != null) {
this.nfcAdapter!!.disableForegroundDispatch(this)
}
+ setProgress(false)
+ error = false
+ val intent = Intent()
+ NfcStore.cardDetails = nfcData
+ this.setResult(RESULT_OK, intent)
+ this.finish()
}
override fun tagReadStatus(status: String) {
diff --git a/app/src/main/java/com/acuant/sampleapp/NfcResultActivity.kt b/app/src/main/java/com/acuant/sampleapp/NfcResultActivity.kt
index 7ad03ee..d8d6a50 100644
--- a/app/src/main/java/com/acuant/sampleapp/NfcResultActivity.kt
+++ b/app/src/main/java/com/acuant/sampleapp/NfcResultActivity.kt
@@ -16,6 +16,11 @@ import com.acuant.acuantechipreader.model.NfcData
class NfcResultActivity : AppCompatActivity() {
+ companion object {
+ const val FACE_MATCH_RESULT = "FaceMatchResult"
+ const val FACE_LIVENESS_RESULT = "FaceLivenessResult"
+ }
+
private var progressDialog: LinearLayout? = null
private var progressText: TextView? = null
private var resultLayout: RelativeLayout? = null
@@ -56,11 +61,17 @@ class NfcResultActivity : AppCompatActivity() {
val intent = intent
if (intent != null) {
- val cardDetails = NfcStore.cardDetails
+ val cardDetails: NfcData? = NfcStore.cardDetails
+ val faceMatch = intent.getStringExtra(FACE_MATCH_RESULT)
+ val faceLiveness = intent.getStringExtra(FACE_LIVENESS_RESULT)
if (cardDetails != null) {
setProgress(false)
+ if (faceLiveness != null && faceMatch != null) {
+ addField("Face Liveness", faceLiveness)
+ addField("Face Match", faceMatch)
+ }
setData(cardDetails)
- val image = cardDetails.image
+ val image = cardDetails.faceImage
if (image != null) {
imageView!!.setImageBitmap(image)
}
diff --git a/app/src/main/java/com/acuant/sampleapp/NfcStore.kt b/app/src/main/java/com/acuant/sampleapp/NfcStore.kt
index 9c9d899..b2786cf 100644
--- a/app/src/main/java/com/acuant/sampleapp/NfcStore.kt
+++ b/app/src/main/java/com/acuant/sampleapp/NfcStore.kt
@@ -3,5 +3,7 @@ package com.acuant.sampleapp
import com.acuant.acuantechipreader.model.NfcData
object NfcStore {
+ //This is a bit sloppy, but NfcData is not fully serializable due to Bitmaps, so this is a
+ // simple solution. Either parcelable or implementing serialized bitmaps might be neater.
var cardDetails: NfcData? = null
}
diff --git a/app/src/main/java/com/acuant/sampleapp/ResultActivity.kt b/app/src/main/java/com/acuant/sampleapp/ResultActivity.kt
index 4ac0496..821ba3d 100644
--- a/app/src/main/java/com/acuant/sampleapp/ResultActivity.kt
+++ b/app/src/main/java/com/acuant/sampleapp/ResultActivity.kt
@@ -30,28 +30,28 @@ class ResultActivity : AppCompatActivity() {
textViewCardInfo = findViewById(R.id.textViewLicenseCardInfo)
nfcScanningBtn = findViewById(R.id.buttonNFC)
- if(ProcessedData.cardType.equals("ID3",true) && (Credential.get().secureAuthorizations.ozoneAuth || Credential.get().secureAuthorizations.chipExtract)){
+ if (ProcessedData.cardType.equals("ID3",true) && (Credential.get().secureAuthorizations.ozoneAuth || Credential.get().secureAuthorizations.chipExtract)) {
nfcScanningBtn.visibility = View.VISIBLE
- }else{
+ } else {
nfcScanningBtn.visibility = View.GONE
}
- if(ProcessedData.frontImage != null){
+ if (ProcessedData.frontImage != null) {
frontSideCardImageView.setImageBitmap(ProcessedData.frontImage)
}
- if(ProcessedData.backImage != null){
+ if (ProcessedData.backImage != null) {
backSideCardImageView.setImageBitmap(ProcessedData.backImage)
}
- if(ProcessedData.faceImage != null){
+ if (ProcessedData.faceImage != null) {
imgFaceViewer.setImageBitmap(ProcessedData.faceImage)
}
- if(ProcessedData.capturedFaceImage != null){
+ if (ProcessedData.capturedFaceImage != null) {
imgCapturedFaceViewer.setImageBitmap(ProcessedData.capturedFaceImage)
}
- if(ProcessedData.signImage != null){
+ if (ProcessedData.signImage != null) {
imgSignatureViewer.setImageBitmap(ProcessedData.signImage)
}
- if(ProcessedData.formattedString != null){
+ if (ProcessedData.formattedString != null) {
textViewCardInfo.text = ProcessedData.formattedString
}
@@ -69,11 +69,11 @@ class ResultActivity : AppCompatActivity() {
private fun formatDateForNfc(date: String) : String {
var pattern = Regex("[0-9]{2}-[0-9]{2}-[0-9]{4}")
var out = ""
- if(pattern.matches(date)) {
+ if (pattern.matches(date)) {
out = date.substring(8,10) + date.substring(0,2) + date.substring(3,5)
}
pattern = Regex("[0-9]{2}-[0-9]{2}-[0-9]{2}")
- if(pattern.matches(date)) {
+ if (pattern.matches(date)) {
out = date.substring(6,8) + date.substring(0,2) + date.substring(3,5)
}
return out
diff --git a/app/src/main/java/com/acuant/sampleapp/utils/CommonUtils.java b/app/src/main/java/com/acuant/sampleapp/utils/CommonUtils.java
index 35a0100..57dfa75 100644
--- a/app/src/main/java/com/acuant/sampleapp/utils/CommonUtils.java
+++ b/app/src/main/java/com/acuant/sampleapp/utils/CommonUtils.java
@@ -1,12 +1,17 @@
package com.acuant.sampleapp.utils;
-import com.acuant.acuantdocumentprocessing.resultmodel.HealthInsuranceCardResult;
+import com.acuant.acuantdocumentprocessing.healthinsuranceresultmodel.HealthInsuranceCardResult;
+
import java.lang.reflect.Field;
import java.util.Objects;
public class CommonUtils {
+
+ //This method is just a quick example of how to get some of the basic info. There are many
+ // fields that would not be covered by this. In a real implementations you should pick each
+ // field individually
public static String stringFromHealthInsuranceResult(HealthInsuranceCardResult result){
- String str = "";
+ StringBuilder str = new StringBuilder();
Field [] allFields = null;
if (result != null) {
allFields = HealthInsuranceCardResult.class.getDeclaredFields();
@@ -15,10 +20,10 @@ public static String stringFromHealthInsuranceResult(HealthInsuranceCardResult r
for (Field field : allFields) {
try {
field.getName();
- if (!field.getName().startsWith("kCard") && !field.getName().startsWith("kDriversCard") && !field.getName().startsWith("kAuth") && String.class.isAssignableFrom(field.getType())) {
+ if (!field.getName().startsWith("rawText") && !field.getName().startsWith("frontImageString") && !field.getName().startsWith("backImageString") && String.class.isAssignableFrom(field.getType())) {
field.setAccessible(true);
if (field.get(result) != null && Objects.requireNonNull(field.get(result)).toString().trim().length()>0) {
- str = str + field.getName() + ":" + field.get(result) + System.lineSeparator();
+ str.append(field.getName()).append(": ").append(field.get(result)).append(System.lineSeparator());
}
}
} catch (IllegalAccessException e) {
@@ -26,6 +31,6 @@ public static String stringFromHealthInsuranceResult(HealthInsuranceCardResult r
}
}
}
- return str;
+ return str.toString();
}
}
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index a8cc384..24b86e8 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -9,7 +9,8 @@
ALINEAR
ALINEAR Y TOCAR
MUÉVETE MAS CERCA
- DEMASIADO CERCA!
+ DEMASIADO CERCA
+ FUERA DE CUADRO
MANTENER ESTABLE
CAPTURA
diff --git a/app/src/main/res/values-fil/strings.xml b/app/src/main/res/values-fil/strings.xml
deleted file mode 100644
index e155fff..0000000
--- a/app/src/main/res/values-fil/strings.xml
+++ /dev/null
@@ -1,95 +0,0 @@
-
- SampleApp
-
-
- Info
- This sample needs camera permission.
- This device does not support Camera2 API.
-
- ITAPAT SA ID
- ITAPAT AT I-TAP
- ILAPIT ANG CAMERA
- MASYADONG MALAPIT!
- HUWAG IGALAW
- KINUKUHA
-
- Sinusuri ang MRZ
- Itapat sa pasaporte
- Ilapit sa pasaporte
- Muling ihanay
- Tapos na ang analisis
-
- Kinukuha...
- Ihanay ang barcode
-
-
- Itapat ang mukha\nupang magsimula
- Masyadong malapit!\nIlayo ang mukha
- Ilapit ang mukha sa screen
- May anggulo ang mukha.\nHuwag ikiling
- Itapat ang mukha\n sa frame
- Huwag igalaw
- Ikurap ang iyong mata
- Kinukuha…
-
- - Kinukuha\n%d…
- - Kinukuha\n%d…
-
-
-
- fil
- Itapat ang mukha sa oblong
- Punan ang oblong ng iyong mukha
- Ilagay ang mukha sa frame
- Nagkokonekta
- I-tap ang screen para magsimula
- Ilapit ang mukha sa screen
- Masyadong maliwanag
- Nagsimula na ang streaming…
- Streaming, mabagal ang network…
- Sinusuri…
- Kinikilala ang mukha…
- Kinukumpirma ang pagkakakilanlan
- Sinusuri ang presenya…
- Sinusuri ang liveness…
- Loading…
- Naglilikha ng pagkakakilanlan…
- Sinusuri ang mukha…
- Authenticate
- Enrol
- %1$s to %2$s
- Masyadong malapit ang mukha
- Sorry, hindi tiyak ang resulta
- Huwag gumalaw
- Maliwanag ang paligid or madilim ang screen
- Maliwanag ang ilaw sa likuran
- Ang iyong paligid ay madilim
- Masyadong maliwanag ang iyong mukha
- Huwag magsalita habang kinukuha ang mukha
- Sorry, nag-time out ang sesyon
- %1$s as %2$s to %3$s
- Huwag ikiling ang ulo
- Huwag ikiling ang ulo
- Iharap ang ulo konti sa kanan
- Iharap ang ulo konti sa kaliwa
- Ihanay ang device sa antas ng mata
- Ihanay ang device sa antas ng mata
- Tapos na ang scan
- Humanda…
- Loading…
- Camera error
- Camera permission denied
- Sorry, your device is currently not supported
- Face detector error
- An existing capture is already in progress
-
-
- OK
- Access to the camera is needed for detection
- This application cannot run because it does not have the camera permission. The application will now exit.
- Face detector dependencies cannot be downloaded due to low device storage
- Could not classify the document
-
- Kuhanan ng larawan ang likod ng ID
- Kuhanan ng larawan ang likod ng health insurance ID
-
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 395d6ae..89c1f02 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -12,7 +12,8 @@
ALIGN
ALIGN AND TAP
MOVE CLOSER
- TOO CLOSE!
+ TOO CLOSE
+ NOT IN FRAME
HOLD STEADY
CAPTURING
diff --git a/build.gradle b/build.gradle
index f2c15cf..c8dc20f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -19,7 +19,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.0.4'
+ classpath 'com.android.tools.build:gradle:7.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
diff --git a/docs/MigrationDetails.md b/docs/MigrationDetails.md
index ec6e35e..0958570 100644
--- a/docs/MigrationDetails.md
+++ b/docs/MigrationDetails.md
@@ -1,4 +1,472 @@
# Migration Details
+----------
+## v11.6.0
+
+**February 2023**
+
+### Updated String Keys
+
+One camera string key has been removed, and two keys have been added. Localizations need to add the following two keys to maintain full localization:
+
+ TOO CLOSE
+ NOT IN FRAME
+
+### Changes to Document Camera and Image Evaluation
+
+This release includes changes to the way images are returned from the camera. These changes affect all implementations. In the past, the camera returned a path to a cache file that contained the image and any metadata. EvaluateImage created a similar cache to store the cropped image and metadata. Although cache images are regularly cleared out by the OS, the images could still last longer than needed. The SDK code could not delete the cached images because they had to remain for an arbitrary amount of time based on client workflow, and most implementations did not manually clear the cache or call the delete method provided with `AcuantImage`.
+
+Certain operations require us to cache the image, so cached images are still in use. With this release, cached images are created and deleted internally with the implementer now receiving a reference to a byte array that contains the data, which was previously contained in the file. As a result, there is less time for an interruption to the application that could leave images in the internal cache. However, because caches are still used (although for a much briefer period of time), Acuant still recommends manually clearing the cache when the application is paused, destroyed, or otherwise interrupted.
+
+Changes necessary to maintain the current workflow are as follows:
+
+Replace
+
+ val url = data?.getStringExtra(ACUANT_EXTRA_IMAGE_URL)
+
+with
+
+ val bytes = AcuantCameraActivity.getLatestCapturedBytes(clearBytesAfterRead = true)
+
+Because the bytes of the image would be too large to pass as part of a parcel, they are accessed through a static method in `AcuantCameraActivity`. Acuant recommends, for security and memory use, to set`clearBytesAfterRead` to true.
+
+ CroppingData(imageUrlString: String)
+
+has been replaced with
+
+ CroppingData(imageBytes: ByteArray)
+
+### Updated models returned by web calls
+
+The Document, Classification, Health Insurance, and Passive Liveness result models have been updated. These models are now in Kotlin resulting in more explicit nullability. Fields that are nullable in the web request are nullable in the Kotlin model, while those that are not nullable in the web request are not nullable in the Kotlin model. The structure of these models has been modified to more closely match the structure of the model returned through the web call. This modification reduces ambiguity and enables future fields in the web model to be mapped more easily to the Kotlin model. Fields also will more closely match their type (int to int, boolean to boolean, etc.) whereas, in the past, most fields were read as strings. Fields that represent an emun are now parsed into the appropriate enum. The unparsed value is still exposed if a new enum value is added in the web result and not yet mapped to the Kotlin model.
+
+Breakdowns of the new Kotlin models are shown below. Although these models might seem overwhelming, large portions remain unchanged from the previous version.
+
+Document + Classification:
+
+ IDResult
+ val alerts: List
+ val unparsedAuthenticationSensitivity: Int
+ val authenticationSensitivity: AuthenticationSensitivity?
+ val biographic: DocumentBiographic?
+ val classification: Classification?
+ val dataFields: List
+ val device: Device?
+ val engineVersion: String?
+ val fields: List
+ val images: List
+ val instanceID: String
+ val libraryVersion: String?
+ val unparsedProcessMode: Int
+ val processMode: DocumentProcessMode?
+ val regions: List
+ val unparsedResult: Int
+ val result: AuthenticationResult?
+ val subscription: Subscription?
+ val unparsedTamperResult: Int
+ val tamperResult: AuthenticationResult?
+ val unparsedTamperSensitivity: Int
+ val tamperSensitivity: AuthenticationSensitivity?
+
+ DocumentAlert
+ val actions: String?
+ val description: String?
+ val disposition: String?
+ val id: String
+ val information: String?
+ val key: String?
+ val model: String?
+ val name: String?
+ val unparsedResult: Int
+ val result: AuthenticationResult?
+
+ DocumentBiographic
+ val age: Int
+ val birthDate: String?
+ val expirationDate: String?
+ val fullName: String?
+ val unparsedGender
+ val gender: GenderType?
+ val photo: String?
+
+ Classification
+ val unparsedMode: Int
+ val mode: ClassificationMode?
+ val orientationChanged: Boolean
+ val presentationChanged: Boolean
+ val classificationDetails: ClassificationDetails?
+ val type: DocumentType?
+
+ ClassificationDetails
+ val front: DocumentType?
+ val back: DocumentType?
+
+ DocumentType
+ val unparsedDocumentClass: Int
+ val documentClass: DocumentClass?
+ val classCode: String?
+ val className: String?
+ val countryCode: String?
+ val unparsedDocumentDataTypes: List
+ val documentDataTypes: List
+ val geographicRegions: List
+ val id: String
+ val isGeneric: Boolean
+ val issue: String?
+ val issueType: String?
+ val issuerCode: String?
+ val issuerName: String?
+ val unparsedIssuerType: Int
+ val issuerType: IssuerType?
+ val keesingCode: String?
+ val name: String?
+ val unparsedReferenceDocumentDataTypes: List
+ val referenceDocumentDataTypes: List
+ val unparsedSize: Int
+ val size: DocumentSize?
+ val supportedImages: List
+
+ DocumentImageType
+ val unparsedLight: Int
+ val light: LightSource?
+ val unparsedSide: Int
+ val side: DocumentSide?
+
+ DocumentDataField
+ val unparsedDataSource: Int
+ val dataSource: DocumentDataSource?
+ val description: String?
+ val id: String
+ val isImage: Boolean
+ val key: String?
+ val name: String?
+ val regionOfInterest: Rectangle
+ val regionReference: String
+ val reliability: Double
+ val type: String?
+ val value: String?
+
+ Rectangle
+ val height: Int
+ val width: Int
+ val x: Int
+ val y: Int
+
+ Device
+ val hasContactlessChipReader: Boolean
+ val hasMagneticStripeReader: Boolean
+ val serialNumber: String?
+ val type: DeviceType?
+
+ DeviceType
+ val manufacturer: String?
+ val model: String?
+ val unparsedSensorType: Int
+ val sensorType: SensorType?
+
+ DocumentField
+ val dataFieldReferences: List
+ val unparsedDataSource: Int
+ val dataSource: DocumentDataSource?
+ val description: String?
+ val id: String
+ val isImage: Boolean
+ val key: String?
+ val name: String?
+ val regionReference: String
+ val type: String?
+ val value: String?
+
+ DocumentImage
+ val glareMetric: Int?
+ val horizontalResolution: Int
+ val id: String
+ val isCropped: Boolean?
+ val isTampered: Boolean?
+ val unparsedLight: Int
+ val light: LightSource?
+ val mimeType: String?
+ val sharpnessMetric: Int?
+ val unparsedSide: Int
+ val side: DocumentSide?
+ val uri: String?
+ val verticalResolution: Int
+
+ DocumentRegion
+ val unparsedDocumentElement: Int
+ val documentElement: DocumentElement?
+ val id: String
+ val imageReference: String
+ val key: String?
+ val rectangle: Rectangle
+
+ Subscription
+ val unparsedDocumentProcessMode: Int
+ val documentProcessMode: DocumentProcessMode?
+ val id: String
+ val isActive: Boolean
+ val isDevelopment: Boolean
+ val isTrial: Boolean
+ val name: String?
+ val storePII: Boolean
+
+Document + Classification Enums:
+
+ AuthenticationResult
+ Unknown(0),
+ Passed(1),
+ Failed(2),
+ Skipped(3),
+ Caution(4),
+ Attention(5)
+
+ AuthenticationSensitivity
+ Normal(0),
+ High(1),
+ Low(2)
+
+ ClassificationMode
+ Automatic(0),
+ Manual(1)
+
+ DocumentClass
+ Unknown(0),
+ Passport(1),
+ Visa(2),
+ DriversLicense(3),
+ IdentificationCard(4),
+ Permit(5),
+ Currency(6),
+ ResidenceDocument(7),
+ TravelDocument(8),
+ BirthCertificate(9),
+ VehicleRegistration(10),
+ Other(11),
+ WeaponLicense(12),
+ TribalIdentification(13),
+ VoterIdentification(14),
+ Military(15),
+ ConsularIdentification(16)
+
+ DocumentDataSource
+ None(0),
+ Barcode1D(1),
+ Barcode2D(2),
+ ContactlessChip(3),
+ MachineReadableZone(4),
+ MagneticStripe(5),
+ VisualInspectionZone(6),
+ Other(7)
+
+ DocumentDataType
+ Barcode2D(0),
+ MachineReadableZone(1),
+ MagneticStripe(2)
+
+ DocumentElement
+ Unknown(0),
+ None(1),
+ Photo(2),
+ Data(3),
+ Substrate(4),
+ Overlay(5)
+
+ DocumentProcessMode
+ Default(0),
+ CaptureData(1),
+ Authenticate(2),
+ Barcode(3)
+
+ DocumentSide
+ Front(0),
+ Back(1)
+
+ DocumentSize
+ Unknown(0),
+ ID1(1),
+ ID2(2),
+ ID3(3),
+ Letter(4),
+ CheckCurrency(5),
+ Custom(6)
+
+ GenderType
+ Unspecified(0),
+ Male(1),
+ Female(2),
+ Unknown(3)
+
+ IssuerType
+ Unknown(0),
+ Country(1),
+ StateProvince(2),
+ Tribal(3),
+ Municipality(4),
+ Business(5),
+ Other(6)
+
+ LightSource
+ White(0),
+ NearInfrared(1),
+ UltravioletA(2),
+ CoaxialWhite(3),
+ CoaxialNearInfrared(4)
+
+ SensorType
+ Unknown(0),
+ Camera(1),
+ Scanner(2),
+ Mobile(3)
+
+Additionally `CardSide` was removed from `AcuantCommon` in favor of `DocumentSide` in `AcuantDocumentProcessing`
+
+Health Insurance:
+
+ HealthInsuranceCardResult
+ var instanceID: String
+ val copayEr: String?
+ val copayOv: String?
+ val copaySp: String?
+ val copayUc: String?
+ val coverage: String?
+ val contractCode: String?
+ val dateOfBirth: String?
+ val deductible: String?
+ val effectiveDate: String?
+ val employer: String?
+ val expirationDate: String?
+ val firstName: String?
+ val groupName: String?
+ val groupNumber: String?
+ val issuerNumber: String?
+ val lastName: String?
+ val memberId: String?
+ val memberName: String?
+ val middleName: String?
+ val namePrefix: String?
+ val nameSuffix: String?
+ val other: String?
+ val payerId: String?
+ val planAdmin: String?
+ val planProvider: String?
+ val planType: String?
+ val frontImage: Bitmap?
+ val rawText: String?
+ val rxBin: String?
+ val rxGroup: String?
+ val rxId: String?
+ val rxPcn: String?
+ val backImage: Bitmap?
+ val listAddress: List
+ val listPlanCode: List
+ val listTelephone: List
+ val listEmail: List
+ val listWeb: List)
+ val transactionTimestamp: String?
+
+ Address
+ val fullAddress: String?
+ val street: String?
+ val city: String?
+ val state: String?
+ val zip: String?
+
+ PlanCode
+ val planCode: String?
+
+ Telephone
+ val label: String?
+ val value: String?
+
+ Email
+ val label: String?
+ val value: String?
+
+ WebAddress
+ val label: String?
+ val value: String?
+
+Passive Liveness:
+
+ PassiveLivenessResult
+ val livenessResult: LivenessResult?
+ val transactionId: String?
+ val errorDesc: String?
+ val unparsedErrorCode: String?
+ val errorCode: PassiveLivenessErrorCode?
+
+ LivenessResult
+ val unparsedLivenessAssessment: String?
+ val livenessAssessment: LivenessAssessment?
+ val score: Int
+
+Passive Liveness Enums:
+
+ LivenessAssessment
+ Error,
+ PoorQuality,
+ Live,
+ NotLive
+
+ PassiveLivenessErrorCode
+ Unknown,
+ FaceTooClose,
+ FaceNotFound,
+ FaceTooSmall,
+ FaceAngleTooLarge,
+ FailedToReadImage,
+ InvalidRequest,
+ InvalidRequestSettings,
+ Unauthorized,
+ NotFound
+
+### Notes on possible backwards incompatibility in Initialization
+
+This release contains minor changes to initialization that might cause backwards incompatibility in some implementations. Most implementations will not be affected by these changes.
+
+The changes are as follows:
+
+- The two main functions used to initialize the SDK, `initializeWithToken` and `initialize`, remain unchanged. The recommended function to create a `Credential` prior to initializing the SDK, `initFromXml` also remains unchanged.
+
+- The `Credential` and `Endpoint` classes were migrated to Kotlin. This means that fields that might have been nullable in the past might no longer be nullable. Some fields in the credential that used to be freely modifiable are now vals (set once during the creation of the object). Most implementations do not set or access values from these classes and, therefore, should be unaffected.
+
+- The names of some endpoints inside the `Endpoint` class have changed. The old names have been left accessible with a deprecated flag. The names of the endpoints within the XML file remain unchanged. Most implementations do not need to set or access these values.
+
+- The `Credential` class used to have many manual overloads of the static `init` and `initWithToken` methods. These methods were combined into singular Kotlin functions with some default parameters. Implementations that load the credential from an XML file (the recommended workflow) will be unaffected by this change. Implementations that load the credential using one of these two methods should be reviewed because the signatures have changed. In Kotlin implementations it is encouraged to explicitly set parameter names within the calls to the respective functions. The new signatures of the functions are shown below.
+
+ fun init(
+ username: String,
+ password: String,
+ subscription: String?,
+ acasEndpoint: String,
+ assureIdEndpoint: String? = null,
+ frmEndpoint: String? = null,
+ passiveLivenessEndpoint: String? = null,
+ ipLivenessEndpoint: String? = null,
+ ozoneEndpoint: String? = null,
+ healthInsuranceEndpoint: String? = null
+ )
+
+ fun initWithToken(
+ token: String,
+ acasEndpoint: String,
+ assureIdEndpoint: String? = null,
+ frmEndpoint: String? = null,
+ passiveLivenessEndpoint: String? = null,
+ ipLivenessEndpoint: String? = null,
+ ozoneEndpoint: String? = null,
+ healthInsuranceEndpoint: String? = null
+ )
+
+- The `parseXml` method in `AcuantInitializer` has been made internal. Most implementations do not use this method. The recommended replacement for it (if in use) is the previously existing static `initFromXml` within the `Credential` class.
+
+- The list of packages to initialize no longer needs to contains packages that don't have content to initialize. `EchipInitializer` and `ImageProcessorInitializer` are no longer needed because those packages will now just check the initialization state of the active credential. The `MrzCameraInitializer` is still required (if MRZ reading is in use) because that class loads OCR data.
+
+### Initialization Behavioral changes
+
+- Previously all methods for creating the Credential and initializing the SDK would function only once. Future calls to these methods would be ignored.
+
+- Now the Credential can be recreated at will and used to reinitialize the SDK. This allows use-cases where the implementer wants to swap credentials or endpoints after having performed one or more workflows.
+
----------
## v11.5.0
@@ -10,7 +478,7 @@
- Instances in which credentials had to be specified by the implementer have been removed. The SDK will always use the result of Credential.get().
-- The Error class in AcuantCommon has been renamed to AcuantError. The renaming helps prevent confusion between `kotlin.Error`, `java.lang.Error`, and `acuant.common.model.Error` because IDEs would always default to the first or second. Additionally, AcuantError now contains and additionalDetails field. This nullable field will in some circumstances contain information that is helpful in debugging, such as a stack trace or server response code, but follows no standard format. Error codes for network requests have been simplified to `ERROR_Network`, whereas previously, there were approximately 20 error codes that all meant the network request failed.
+- The Error class in AcuantCommon has been renamed to AcuantError. The renaming helps prevent confusion between `kotlin.Error`, `java.lang.Error`, and `acuant.common.model.Error` because IDEs would always default to the first or second. Additionally, AcuantError now contains an additionalDetails field. This nullable field will in some circumstances contain information that is helpful in debugging, such as a stack trace or server response code, but follows no standard format. Error codes for network requests have been simplified to `ERROR_Network`, whereas previously, there were approximately 20 error codes that all meant the network request failed.
class AcuantError(
val errorCode: Int,
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index b000d2d..99618d7 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Tue Dec 07 11:00:37 PST 2021
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME