diff --git a/README.md b/README.md index 06ea6fd..e9b41ad 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# Acuant Android SDK v11.4.7 -**November 2020** +# Acuant Android SDK v11.4.8 +**December 2020** See [https://github.com/Acuant/AndroidSDKV11/releases](https://github.com/Acuant/AndroidSDKV11/releases) for release notes. @@ -90,9 +90,9 @@ The SDK includes the following modules: - Processes a single photo using our web service to determine liveness. +---------- - -### Setup ### +## Setup ## 1. Specify the permissions in the App manifest file: @@ -189,18 +189,18 @@ The SDK includes the following modules: - Add the following dependencies - implementation 'com.acuant:acuantcommon:11.4.7' - implementation 'com.acuant:acuantcamera:11.4.7' - implementation 'com.acuant:acuantimagepreparation:11.4.7' - implementation 'com.acuant:acuantdocumentprocessing:11.4.7' - implementation 'com.acuant:acuantechipreader:11.4.7' - implementation 'com.acuant:acuantfacematch:11.4.7' - implementation 'com.acuant:acuanthgliveness:11.4.7' - implementation ('com.acuant:acuantipliveness:11.4.7'){ + implementation 'com.acuant:acuantcommon:11.4.8' + implementation 'com.acuant:acuantcamera:11.4.8' + implementation 'com.acuant:acuantimagepreparation:11.4.8' + implementation 'com.acuant:acuantdocumentprocessing:11.4.8' + implementation 'com.acuant:acuantechipreader:11.4.8' + implementation 'com.acuant:acuantfacematch:11.4.8' + implementation 'com.acuant:acuanthgliveness:11.4.8' + implementation ('com.acuant:acuantipliveness:11.4.8'){ transitive = true } - implementation 'com.acuant:acuantfacecapture:11.4.7' - implementation 'com.acuant:acuantpassiveliveness:11.4.7' + implementation 'com.acuant:acuantfacecapture:11.4.8' + implementation 'com.acuant:acuantpassiveliveness:11.4.8' - Acuant also relies on Google Play services dependencies, which are pre-installed on almost all Android devices. @@ -257,8 +257,10 @@ The SDK includes the following modules: 1. Save the file to the application assets directory: {PROJECT_ROOT_DIRECTORY} => app => src => main => assets => PATH/TO/CONFIG/FILENAME.XML + +---------- -### Initializing the SDK ### +## Initializing the SDK ## Before you use the SDK, you need to initialize it either by using the credentials saved on the device or by using bearer tokens (provided by an external server). @@ -328,9 +330,11 @@ Here is the interface for the initialize listener: 3. The captured images can be exported from the SDK. See the **onActivityResult** in the following section. +---------- -### Capture a document image using AcuantCamera ### +## AcuantCamera ## +### Capturing a document ### **Note:** **AcuantCamera** is dependent on **AcuantImagePreparation** and **AcuantCommon**. 1. Start camera activity: @@ -342,7 +346,7 @@ Here is the interface for the initialize listener: startActivityForResult(cameraIntent, REQUEST_CODE) -Alternatively use the new options object. This method allows you to configure much more about the camera (see **AcuantCameraOptions**): + Alternatively use the new options object. This method allows you to configure much more about the camera (see **AcuantCameraOptions**): val cameraIntent = Intent(this, AcuantCameraActivity::class.java) @@ -356,8 +360,12 @@ Alternatively use the new options object. This method allows you to configure mu ) startActivityForResult(cameraIntent, REQUEST_CODE) - -2. Get activity result: +**Note:** When the camera is launched, the image processing speed is automatically checked. + + - Live document detection and auto capture features are enabled if the device supports a speed of at least 130ms. + - For devices that don't meet the processing threshold, tap to capture will be enabled. Live document detection and auto capture features are disabled and switched to tap to capture. The user will have to manually capture the document. + +1. Get activity result: override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) @@ -368,46 +376,48 @@ Alternatively use the new options object. This method allows you to configure mu } } -### Capture MRZ data using AcuantCamera ### +### Capturing MRZ data in a passport document ### **Note:** To use the MRZ features, your credentials must be enabled to use Ozone. - **Initialization** - Must have included MrzCameraInitializer() in initialization (See **Initializing the SDK**). Important note, by this point the user has to have granted external storage permissions since this initializer is saving OCRB information to the phone. The MRZ camera will not function without this initialization. + **MrzCameraInitializer()** must be included in initialization (see **Initializing the SDK**). + + **Important Note:** You must grant external storage permissions in order to save the OCRB information to the phone. Otherwise, the MRZ camera will not function. - **Capturing the MRZ data** -Capturing the MRZ data using AcuantCamera is similar to document capture. + Capturing the MRZ data using AcuantCamera is similar to document capture. -1. Start camera activity: + 1. Start camera activity: - val cameraIntent = Intent( - this@MainActivity, - AcuantCameraActivity::class.java - ) - - cameraIntent.putExtra(ACUANT_EXTRA_CAMERA_OPTIONS, - AcuantCameraOptions - .MrzCameraOptionsBuilder() - .build() - /*Please note that this uses a different builder than the document camera. - * This is how the camera knows that it is being launched in MRZ mode.*/ - ) - - startActivityForResult(cameraIntent, REQUEST_CODE) - -2. Get activity result: - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) + val cameraIntent = Intent( + this@MainActivity, + AcuantCameraActivity::class.java + ) + + cameraIntent.putExtra(ACUANT_EXTRA_CAMERA_OPTIONS, + AcuantCameraOptions + .MrzCameraOptionsBuilder() + .build() + /*Please note that this uses a different builder than the document camera. + * This is how the camera knows that it is being launched in MRZ mode.*/ + ) - if (requestCode == REQUEST_CODE && resultCode == AcuantCameraActivity.RESULT_SUCCESS_CODE) { - val result = data?.getSerializableExtra(ACUANT_EXTRA_MRZ_RESULT) as MrzResult + startActivityForResult(cameraIntent, REQUEST_CODE) + + 1. Get activity result: + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + + if (requestCode == REQUEST_CODE && resultCode == AcuantCameraActivity.RESULT_SUCCESS_CODE) { + val result = data?.getSerializableExtra(ACUANT_EXTRA_MRZ_RESULT) as MrzResult + } } - } -#### AcuantImagePreparation #### +## AcuantImagePreparation ## **Note:** **AcuantImagePreparation** uses @Keep annotations. These are supported by the default Android configuration. If you override or modify the Android ProGuard file, then support for these annotations must be included. @@ -447,7 +457,9 @@ This section describes how to use **AcuantImagePreparation**. val rawBytes: ByteArray } -### AcuantDocumentProcessing ### +------------------------------------- + +## AcuantDocumentProcessing ## After you capture a document image is captured, use the following steps to process the image. @@ -493,8 +505,9 @@ After you capture a document image is captured, use the following steps to proce public void instanceDeleted(boolean success); } - -### AcuantIPLiveness ### +------------------------------------- + +## AcuantIPLiveness ## **Important Note:** The following must be in your root level gradle in the android{} section otherwise a runtime failure may occur: @@ -555,6 +568,7 @@ After you capture a document image is captured, use the following steps to proce } ) +------------------------------------- ### AcuantHGLiveness ### @@ -586,7 +600,7 @@ This module checks for liveness (whether the subject is a live person) by using } -### AcuantFaceMatch ### +## AcuantFaceMatch ## This module is used to match two facial images: @@ -596,10 +610,11 @@ This module is used to match two facial images: public void facialMatchFinished(FacialMatchResult result); } -### AcuantEChipReader ### +------------------------------------- +## AcuantEChipReader ## -- **Initialization** +**Initialization** Must include EchipInitializer() in initialization (See **Initializing the SDK**). @@ -647,7 +662,9 @@ Must include EchipInitializer() in initialization (See **Initializing the SDK**) **Important Note:** All the data in nfcData is directly read from the passport chip except for *age* and *isExpired*. These two fields are extrapolated from the data read from the chip and the current date (obtained via Calendar.getInstance().time). This can potentially lead to inaccuracy due to either the device time being wrong or the DOB or DOE being calculated incorrectly from the data on the chip. This is an unfortunate restraint of passport chips as the DOE and DOB are stored in YYMMDD format and therefore suffers from the y2k issue (given a year of 22 we can not with 100% certainty determine if it stands for 1922 or 2022 or even theoretically 2122). The way we work around this is as follows: For age we use the current year as the breakpoint (eg. in 2020, 25 would be interpreted as 1925 but in 2030 25 would be interpreted as 2025). For isExpired we do the same but going forward 20 years from the current year. -### AcuantFaceCapture ### +------------------------------------- + +## AcuantFaceCapture ## This module is used to automate capturing an image of a face appropriate for use with passive liveness. @@ -684,8 +701,10 @@ This module is used to automate capturing an image of a face appropriate for use } } } + +------------------------------------- -### AcuantPassiveLiveness ### +## AcuantPassiveLiveness ## This module is used to determine liveness from a single selfie image. @@ -734,6 +753,7 @@ Relevant Enums: InternalError, InvalidJson } +------------------------------------- ### Error codes ### @@ -874,6 +894,7 @@ Relevant Enums: public AuthenticationSensitivity authenticationSensitivity; } +------------------------------------- ## Frequently Asked Questions ## 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 3ebee1e..53aeb6e 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantCameraActivity.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantCameraActivity.kt @@ -143,7 +143,7 @@ class AcuantCameraActivity : AppCompatActivity(), ICameraActivityFinish { when(requestCode){ DOC_REQUEST -> { if(resultCode == RESULT_SUCCESS_CODE && data != null){ - onActivityFinish(data.getStringExtra(ACUANT_EXTRA_IMAGE_URL), data.getStringExtra(ACUANT_EXTRA_PDF417_BARCODE)) + onActivityFinish(data.getStringExtra(ACUANT_EXTRA_IMAGE_URL) ?: "-1", data.getStringExtra(ACUANT_EXTRA_PDF417_BARCODE)) } else{ this@AcuantCameraActivity.finish() 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 3b96c84..000ad92 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 @@ -30,6 +30,8 @@ class AcuantDocCameraFragment : AcuantBaseCameraFragment(), private var redTransparent: Int = 0 private var grayTransparent: Int = 0 private var greenTransparent: Int = 0 + private var firstThreeTimings: Array = arrayOf(-1, -1, -1) + private var hasFinishedTest = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -87,71 +89,96 @@ class AcuantDocCameraFragment : AcuantBaseCameraFragment(), */ override fun onDetected(croppedImage: com.acuant.acuantcommon.model.Image?, cropDuration: Long) { activity?.runOnUiThread { - var detectedPoints = croppedImage?.points - if(detectedPoints != null && croppedImage?.points != null){ - detectedPoints = DocRectangleView.fixPoints(scalePoints(croppedImage.points)) - } - - when { - croppedImage == null || croppedImage.dpi < MINIMUM_DPI -> { - unlockFocus() - rectangleView.setViewFromState(CameraState.Align) - setTextFromState(CameraState.Align) - resetTimer() - } - !isDocumentInFrame(detectedPoints) -> { - unlockFocus() - rectangleView.setViewFromState(CameraState.NotInFrame) - setTextFromState(CameraState.NotInFrame) - resetTimer() + if (!hasFinishedTest) { + for (i in firstThreeTimings.indices) { + if (firstThreeTimings[i] == (-1).toLong()) { + firstThreeTimings[i] = cropDuration + break + } } - croppedImage.dpi < getTargetDpi(croppedImage.isPassport) -> { - unlockFocus() - rectangleView.setViewFromState(CameraState.MoveCloser) - setTextFromState(CameraState.MoveCloser) - resetTimer() + if (!firstThreeTimings.contains((-1).toLong())) { + hasFinishedTest = true + if (firstThreeTimings.min() ?: (TOO_SLOW_FOR_AUTO_THRESHOLD + 10) > TOO_SLOW_FOR_AUTO_THRESHOLD) { + isAutoCapture = false + setTapToCapture() + } } - !croppedImage.isCorrectAspectRatio -> { - unlockFocus() - rectangleView.setViewFromState(CameraState.Align) - setTextFromState(CameraState.Align) - resetTimer() + } + + if (!hasFinishedTest) { + rectangleView.setViewFromState(CameraState.Align) + setTextFromState(CameraState.Align) + this.isProcessing = false + } + + if (hasFinishedTest && isAutoCapture) { + var detectedPoints = croppedImage?.points + + if (detectedPoints != null && croppedImage?.points != null) { + detectedPoints = DocRectangleView.fixPoints(scalePoints(croppedImage.points)) } - else -> { - if(System.currentTimeMillis() - lastTime > (digitsToShow - currentDigit + 2) * timeInMsPerDigit) - --currentDigit - - var dist = 0 - if(oldPoints != null && oldPoints!!.size == 4 && detectedPoints != null && detectedPoints.size == 4) { - for (i in 0..3) { - dist += sqrt( ((oldPoints!![i].x-detectedPoints[i].x).toDouble().pow(2) + (oldPoints!![i].y - detectedPoints[i].y).toDouble().pow(2) )).toInt() - } - } - when { - dist > TOO_MUCH_MOVEMENT -> { - rectangleView.setViewFromState(CameraState.Steady) - setTextFromState(CameraState.Steady) - resetTimer() - } - System.currentTimeMillis() - lastTime < digitsToShow * timeInMsPerDigit -> { - rectangleView.setViewFromState(CameraState.Hold) - setTextFromState(CameraState.Hold) + when { + croppedImage == null || croppedImage.dpi < MINIMUM_DPI -> { + unlockFocus() + rectangleView.setViewFromState(CameraState.Align) + setTextFromState(CameraState.Align) + resetTimer() + } + !isDocumentInFrame(detectedPoints) -> { + unlockFocus() + rectangleView.setViewFromState(CameraState.NotInFrame) + setTextFromState(CameraState.NotInFrame) + resetTimer() + } + croppedImage.dpi < getTargetDpi(croppedImage.isPassport) -> { + unlockFocus() + rectangleView.setViewFromState(CameraState.MoveCloser) + setTextFromState(CameraState.MoveCloser) + resetTimer() + } + !croppedImage.isCorrectAspectRatio -> { + unlockFocus() + rectangleView.setViewFromState(CameraState.Align) + setTextFromState(CameraState.Align) + resetTimer() + } + else -> { + if (System.currentTimeMillis() - lastTime > (digitsToShow - currentDigit + 2) * timeInMsPerDigit) + --currentDigit + + var dist = 0 + if (oldPoints != null && oldPoints!!.size == 4 && detectedPoints != null && detectedPoints.size == 4) { + for (i in 0..3) { + dist += sqrt(((oldPoints!![i].x - detectedPoints[i].x).toDouble().pow(2) + (oldPoints!![i].y - detectedPoints[i].y).toDouble().pow(2))).toInt() + } } - else -> { - this.isCapturing = true - rectangleView.setViewFromState(CameraState.Capturing) - setTextFromState(CameraState.Capturing) - lockFocus() + + when { + dist > TOO_MUCH_MOVEMENT -> { + rectangleView.setViewFromState(CameraState.Steady) + setTextFromState(CameraState.Steady) + resetTimer() + } + System.currentTimeMillis() - lastTime < digitsToShow * timeInMsPerDigit -> { + rectangleView.setViewFromState(CameraState.Hold) + setTextFromState(CameraState.Hold) + } + else -> { + this.isCapturing = true + rectangleView.setViewFromState(CameraState.Capturing) + setTextFromState(CameraState.Capturing) + lockFocus() + } } } } - } - oldPoints = detectedPoints - drawBorder(detectedPoints) + oldPoints = detectedPoints + drawBorder(detectedPoints) - this.isProcessing = false + this.isProcessing = false + } } } @@ -255,6 +282,8 @@ class AcuantDocCameraFragment : AcuantBaseCameraFragment(), companion object { + internal const val TOO_SLOW_FOR_AUTO_THRESHOLD: Long = 130 + /** * Conversion from screen rotation to JPEG orientation. */ @@ -271,7 +300,7 @@ class AcuantDocCameraFragment : AcuantBaseCameraFragment(), /** * How much total x/y movement between frames is too much */ - private const val TOO_MUCH_MOVEMENT = 350 + internal const val TOO_MUCH_MOVEMENT = 350 @JvmStatic fun newInstance(): AcuantDocCameraFragment = AcuantDocCameraFragment() } diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/AcuantDocumentFeedback.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/AcuantDocumentFeedback.kt index a7a7def..9f61806 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/AcuantDocumentFeedback.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/AcuantDocumentFeedback.kt @@ -3,7 +3,7 @@ package com.acuant.acuantcamera.camera.document.cameraone import android.graphics.Point import android.util.Size -data class AcuantDocumentFeedback(val feedback: DocumentFeedback, val point: Array?, val frameSize: Size?, val barcode: String? = null) { +data class AcuantDocumentFeedback(val feedback: DocumentFeedback, val point: Array?, val frameSize: Size?, val barcode: String? = null, val detectTime: Long = -1) { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCameraSource.java b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCameraSource.java index 327cbf0..5edf376 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCameraSource.java +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCameraSource.java @@ -1126,7 +1126,7 @@ private class FrameProcessingRunnable implements Runnable { */ @SuppressLint("Assert") void release() { - assert (mProcessingThread.getState() == State.TERMINATED); + assert mProcessingThread == null || mProcessingThread.getState() == State.TERMINATED; mDetector.release(); mDetector = null; } diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCaptureActivity.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCaptureActivity.kt index 2f449a7..9354674 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCaptureActivity.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCaptureActivity.kt @@ -14,11 +14,6 @@ import android.hardware.Camera import android.os.Bundle import android.util.Log import android.util.Size -import android.view.Gravity -import android.view.OrientationEventListener -import android.view.View -import android.view.Window -import android.view.WindowManager import android.widget.LinearLayout import android.widget.RelativeLayout import android.widget.TextView @@ -33,6 +28,7 @@ import android.graphics.drawable.Drawable import android.support.v4.app.ActivityCompat import android.support.v7.app.AlertDialog import android.support.v7.app.AppCompatActivity +import android.view.* import com.acuant.acuantcamera.R import com.acuant.acuantcamera.camera.AcuantCameraActivity import com.acuant.acuantcamera.camera.AcuantCameraOptions @@ -40,6 +36,7 @@ import com.acuant.acuantcamera.constant.* import java.io.ByteArrayOutputStream import com.acuant.acuantcamera.overlay.DocRectangleView import com.acuant.acuantcamera.camera.AcuantBaseCameraFragment.CameraState +import com.acuant.acuantcamera.camera.document.AcuantDocCameraFragment import com.acuant.acuantcamera.detector.ImageSaver import java.io.File import java.io.FileOutputStream @@ -78,6 +75,9 @@ class DocumentCaptureActivity : AppCompatActivity(), DocumentCameraSource.Pictur private var digitsToShow: Int = 2 private lateinit var displaySize: Point private var lastOrientation = ORIENTATION_LANDSCAPE + private var firstThreeTimings: Array = arrayOf(-1, -1, -1) + private var hasFinishedTest = false + private lateinit var parent: RelativeLayout /** * Initializes the UI and creates the detector pipeline. @@ -90,7 +90,7 @@ class DocumentCaptureActivity : AppCompatActivity(), DocumentCameraSource.Pictur requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT supportActionBar?.title = "" supportActionBar?.hide() - val parent = RelativeLayout(this) + parent = RelativeLayout(this) parent.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT) parent.keepScreenOn = true @@ -132,15 +132,7 @@ class DocumentCaptureActivity : AppCompatActivity(), DocumentCameraSource.Pictur setOptions(intent.getSerializableExtra(ACUANT_EXTRA_CAMERA_OPTIONS) as AcuantCameraOptions? ?: AcuantCameraOptions()) - if (!autoCapture) { - instructionView.text = getString(R.string.acuant_camera_align_and_tap) - parent.setOnClickListener { - instructionView.setBackgroundColor(getColorWithAlpha(Color.GREEN, .50f)) - instructionView.text = getString(R.string.acuant_camera_capturing) - capturing = true - lockFocus() - } - } + setTapToCapture(parent) displaySize = Point() this.windowManager.defaultDisplay.getSize(displaySize) @@ -175,6 +167,18 @@ class DocumentCaptureActivity : AppCompatActivity(), DocumentCameraSource.Pictur } } + private fun setTapToCapture(parent: RelativeLayout) { + if (!autoCapture) { + instructionView.text = getString(R.string.acuant_camera_align_and_tap) + parent.setOnClickListener { + instructionView.setBackgroundColor(getColorWithAlpha(Color.GREEN, .50f)) + instructionView.text = getString(R.string.acuant_camera_capturing) + capturing = true + lockFocus() + } + } + } + private fun lockFocus() { documentCameraSource?.autoFocus { if (it) { @@ -288,82 +292,100 @@ class DocumentCaptureActivity : AppCompatActivity(), DocumentCameraSource.Pictur this.capturedbarcodeString = it.barcode } else { runOnUiThread { - var points = it.point - val frameSize = it.frameSize!! - var feedback = it.feedback - rectangleView.setWidth(mPreview!!.mSurfaceView.width.toFloat()) - if (points != null && points.size == 4) { - - points = scalePoints(points, frameSize) - points = fixPoints(points) - if (!isDocumentInPreviewFrame(points)) { - feedback = DocumentFeedback.NotInFrame + if (!hasFinishedTest) { + for (i in firstThreeTimings.indices) { + if (firstThreeTimings[i] == (-1).toLong()) { + firstThreeTimings[i] = it.detectTime + break + } + } + if (!firstThreeTimings.contains((-1).toLong())) { + hasFinishedTest = true + if (firstThreeTimings.min() ?: (AcuantDocCameraFragment.TOO_SLOW_FOR_AUTO_THRESHOLD + 10) > AcuantDocCameraFragment.TOO_SLOW_FOR_AUTO_THRESHOLD) { + autoCapture = false + setTapToCapture(parent) + } } } + if (hasFinishedTest && autoCapture) { + var points = it.point + val frameSize = it.frameSize!! + var feedback = it.feedback + rectangleView.setWidth(mPreview!!.mSurfaceView.width.toFloat()) + if (points != null && points.size == 4) { - if (!capturing && autoCapture) { - when (feedback) { - DocumentFeedback.NoDocument -> { - rectangleView.setViewFromState(CameraState.Align) - setTextFromState(CameraState.Align) - resetTimer() - } - DocumentFeedback.NotInFrame -> { - rectangleView.setViewFromState(CameraState.NotInFrame) - setTextFromState(CameraState.NotInFrame) - resetTimer() - } - DocumentFeedback.SmallDocument -> { - rectangleView.setViewFromState(CameraState.MoveCloser) - setTextFromState(CameraState.MoveCloser) - resetTimer() - } - DocumentFeedback.BadDocument -> { - rectangleView.setViewFromState(CameraState.Align) - setTextFromState(CameraState.MoveCloser) - resetTimer() + points = scalePoints(points, frameSize) + points = fixPoints(points) + + if (!isDocumentInPreviewFrame(points)) { + feedback = DocumentFeedback.NotInFrame } - else -> { + } - if (System.currentTimeMillis() - lastTime > (digitsToShow - currentDigit + 2) * timeInMsPerDigit) - --currentDigit - var dist = 0 - if (oldPoints != null && oldPoints!!.size == 4 && points != null && points.size == 4) { - for (i in 0..3) { - dist += sqrt(((oldPoints!![i].x - points[i].x).toDouble().pow(2) + (oldPoints!![i].y - points[i].y).toDouble().pow(2))).toInt() - } + if (!capturing && autoCapture) { + when (feedback) { + DocumentFeedback.NoDocument -> { + rectangleView.setViewFromState(CameraState.Align) + setTextFromState(CameraState.Align) + resetTimer() + } + DocumentFeedback.NotInFrame -> { + rectangleView.setViewFromState(CameraState.NotInFrame) + setTextFromState(CameraState.NotInFrame) + resetTimer() + } + DocumentFeedback.SmallDocument -> { + rectangleView.setViewFromState(CameraState.MoveCloser) + setTextFromState(CameraState.MoveCloser) + resetTimer() } + DocumentFeedback.BadDocument -> { + rectangleView.setViewFromState(CameraState.Align) + setTextFromState(CameraState.MoveCloser) + resetTimer() + } + else -> { - when { - dist > 350 -> { - rectangleView.setViewFromState(CameraState.Steady) - setTextFromState(CameraState.Steady) - resetTimer() + if (System.currentTimeMillis() - lastTime > (digitsToShow - currentDigit + 2) * timeInMsPerDigit) + --currentDigit + var dist = 0 + if (oldPoints != null && oldPoints!!.size == 4 && points != null && points.size == 4) { + for (i in 0..3) { + dist += sqrt(((oldPoints!![i].x - points[i].x).toDouble().pow(2) + (oldPoints!![i].y - points[i].y).toDouble().pow(2))).toInt() + } } - System.currentTimeMillis() - lastTime < digitsToShow * timeInMsPerDigit -> { - rectangleView.setViewFromState(CameraState.Hold) - setTextFromState(CameraState.Hold) - } - else -> { - rectangleView.setViewFromState(CameraState.Capturing) - setTextFromState(CameraState.Capturing) - capturing = true - lockFocus() + + when { + dist > AcuantDocCameraFragment.TOO_MUCH_MOVEMENT -> { + rectangleView.setViewFromState(CameraState.Steady) + setTextFromState(CameraState.Steady) + resetTimer() + + } + System.currentTimeMillis() - lastTime < digitsToShow * timeInMsPerDigit -> { + rectangleView.setViewFromState(CameraState.Hold) + setTextFromState(CameraState.Hold) + } + else -> { + rectangleView.setViewFromState(CameraState.Capturing) + setTextFromState(CameraState.Capturing) + capturing = true + lockFocus() + } } } } + oldPoints = points + rectangleView.setAndDrawPoints(points) } - oldPoints = points - rectangleView.setAndDrawPoints(points) - } + } } } - } } diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/LiveDocumentProcessor.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/LiveDocumentProcessor.kt index a2d70b0..da69f61 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/LiveDocumentProcessor.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/LiveDocumentProcessor.kt @@ -78,17 +78,19 @@ class LiveDocumentProcessor : DocumentGraphicTracker.BarcodeUpdateListener { data.image = frame val frameSize = Size(data.image.width, data.image.height) try { + val startTime = System.currentTimeMillis() val acuantImage = AcuantImagePreparation.detect(data) + val elapsed = System.currentTimeMillis() - startTime var feedback: AcuantDocumentFeedback? = null feedback = if (acuantImage?.points == null || acuantImage.dpi < 20) { - AcuantDocumentFeedback(DocumentFeedback.NoDocument, null, frameSize) + AcuantDocumentFeedback(DocumentFeedback.NoDocument, null, frameSize, null, elapsed) } else if ((!acuantImage.isPassport && acuantImage.dpi < (SMALL_DOC_DPI_SCALE_VALUE * frame!!.width)) || (acuantImage.isPassport && acuantImage.dpi < (LARGE_DOC_DPI_SCALE_VALUE * frame!!.width))) { - AcuantDocumentFeedback(DocumentFeedback.SmallDocument, acuantImage.points, frameSize) + AcuantDocumentFeedback(DocumentFeedback.SmallDocument, acuantImage.points, frameSize, null, elapsed) } else if (!acuantImage.isCorrectAspectRatio) { - AcuantDocumentFeedback(DocumentFeedback.BadDocument, acuantImage.points, frameSize) + AcuantDocumentFeedback(DocumentFeedback.BadDocument, acuantImage.points, frameSize, null, elapsed) } else { - AcuantDocumentFeedback(DocumentFeedback.GoodDocument, acuantImage.points, frameSize) + AcuantDocumentFeedback(DocumentFeedback.GoodDocument, acuantImage.points, frameSize, null, elapsed) } feedbackListener?.let { it(feedback) } diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentCameraSource.java b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentCameraSource.java index cf41b0a..0bb9bf4 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentCameraSource.java +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/DocumentCameraSource.java @@ -1128,7 +1128,7 @@ private class FrameProcessingRunnable implements Runnable { */ @SuppressLint("Assert") void release() { - assert (mProcessingThread.getState() == State.TERMINATED); + assert mProcessingThread == null || mProcessingThread.getState() == State.TERMINATED; mDetector.release(); mDetector = null; } diff --git a/acuantdocumentprocessing/acuantdocumentprocessing.aar b/acuantdocumentprocessing/acuantdocumentprocessing.aar index d1c85b0..a596673 100644 Binary files a/acuantdocumentprocessing/acuantdocumentprocessing.aar and b/acuantdocumentprocessing/acuantdocumentprocessing.aar differ diff --git a/acuantechipreader/acuantechipreader.aar b/acuantechipreader/acuantechipreader.aar index 05b88ce..d4e1c8d 100644 Binary files a/acuantechipreader/acuantechipreader.aar and b/acuantechipreader/acuantechipreader.aar differ diff --git a/acuantimagepreparation/acuantimagepreparation.aar b/acuantimagepreparation/acuantimagepreparation.aar index 5946eb9..a3d7fa2 100644 Binary files a/acuantimagepreparation/acuantimagepreparation.aar and b/acuantimagepreparation/acuantimagepreparation.aar differ