From e3943593706a04f6352955f205c6ea5f2ce5965a Mon Sep 17 00:00:00 2001 From: Sergey Maltsev Date: Tue, 4 Jan 2022 11:25:47 -0800 Subject: [PATCH] added v11.5.0 --- README.md | 777 ++++---- acuantcamera/build.gradle | 64 +- acuantcamera/src/main/AndroidManifest.xml | 33 +- .../com/acuant/acuantcamera/CapturedImage.kt | 54 - .../camera/AcuantBaseCameraFragment.kt | 1210 ++++--------- .../camera/AcuantCameraActivity.kt | 271 +-- .../camera/AcuantCameraOptions.kt | 41 +- .../barcode/AcuantBarcodeCameraFragment.kt | 225 ++- .../cameraone/AcuantBarcodeFeedback.kt | 31 - .../cameraone/BarcodeCameraSource.java | 1270 ------------- .../cameraone/BarcodeCameraSourcePreview.kt | 195 -- .../cameraone/BarcodeCaptureActivity.kt | 444 ----- .../barcode/cameraone/BarcodeDetector.kt | 37 - .../barcode/cameraone/BarcodeFeedback.kt | 6 - .../cameraone/BarcodeGraphicTracker.kt | 70 - .../cameraone/BarcodeTrackerFactory.kt | 34 - .../barcode/cameraone/LiveBarcodeProcessor.kt | 103 -- .../document/AcuantDocCameraFragment.kt | 538 +++--- .../cameraone/AcuantDocumentFeedback.kt | 31 - .../cameraone/DocumentCameraSource.java | 1268 ------------- .../cameraone/DocumentCameraSourcePreview.kt | 195 -- .../cameraone/DocumentCaptureActivity.kt | 687 ------- .../document/cameraone/DocumentDetector.kt | 44 - .../document/cameraone/DocumentFeedback.kt | 10 - .../cameraone/DocumentGraphicTracker.kt | 70 - .../cameraone/DocumentTrackerFactory.kt | 34 - .../cameraone/LiveDocumentProcessor.kt | 118 -- .../camera/mrz/AcuantMrzCameraFragment.kt | 350 ++-- .../camera/mrz/cameraone/AcuantMrzFeedback.kt | 31 - .../camera/mrz/cameraone/LiveMrzProcessor.kt | 76 - .../camera/mrz/cameraone/MrzCameraSource.java | 1267 ------------- .../mrz/cameraone/MrzCameraSourcePreview.kt | 188 -- .../mrz/cameraone/MrzCaptureActivity.kt | 538 ------ .../camera/mrz/cameraone/MrzDetector.kt | 44 - .../camera/mrz/cameraone/MrzFeedback.kt | 9 - .../camera/mrz/cameraone/MrzGraphicTracker.kt | 70 - .../camera/mrz/cameraone/MrzTrackerFactory.kt | 34 - .../acuant/acuantcamera/constant/Constants.kt | 18 +- .../detector/AcuantDetectorWorker.kt | 19 - .../detector/BaseAcuantDetector.kt | 14 - .../detector/DocumentFrameAnalyzer.kt | 102 ++ .../acuantcamera/detector/IAcuantDetector.kt | 11 - .../acuantcamera/detector/ImageSaver.kt | 180 -- .../acuantcamera/detector/MrzFrameAnalyzer.kt | 122 ++ .../detector/barcode/AcuantBarcodeDetector.kt | 48 - .../barcode/AcuantBarcodeDetectorHandler.kt | 5 - .../barcode/tracker/AcuantBarcodeTracker.kt | 41 - .../tracker/AcuantBarcodeTrackerFactory.kt | 36 - .../document/AcuantDocumentDetector.kt | 33 - .../document/AcuantDocumentDetectorHandler.kt | 11 - .../detector/ocr/AcuantOcrDetector.kt | 121 -- .../detector/ocr/AcuantOcrDetectorHandler.kt | 12 - .../acuantcamera/helper/AutoFitTextureView.kt | 48 - .../acuantcamera/helper/CompareSizesByArea.kt | 17 - .../acuantcamera/helper/ConfirmationDialog.kt | 28 - .../acuant/acuantcamera/helper/ErrorDialog.kt | 29 - .../acuant/acuantcamera/helper/MrzParser.kt | 7 +- .../acuant/acuantcamera/helper/MrzResult.kt | 39 +- .../acuant/acuantcamera/helper/PointsUtils.kt | 114 ++ .../initializer/MrzCameraInitializer.kt | 15 +- .../interfaces/IAcuantSavedImage.kt | 7 + .../interfaces/ICameraActivityFinish.kt | 11 + .../overlay/AcuantOrientationListener.kt | 33 - .../acuantcamera/overlay/BaseRectangleView.kt | 32 +- .../acuantcamera/overlay/DocRectangleView.kt | 54 +- .../acuantcamera/overlay/ISetFromState.kt | 9 - .../acuantcamera/overlay/MrzRectangleView.kt | 82 +- .../main/res/drawable-hdpi/ic_action_info.png | Bin 1025 -> 0 bytes .../main/res/drawable-hdpi/ic_launcher.png | Bin 4251 -> 0 bytes .../src/main/res/drawable-hdpi/tile.9.png | Bin 196 -> 0 bytes .../main/res/drawable-mdpi/ic_action_info.png | Bin 665 -> 0 bytes .../main/res/drawable-mdpi/ic_launcher.png | Bin 2622 -> 0 bytes .../res/drawable-xhdpi/ic_action_info.png | Bin 1355 -> 0 bytes .../main/res/drawable-xhdpi/ic_launcher.png | Bin 5911 -> 0 bytes .../res/drawable-xxhdpi/ic_action_info.png | Bin 2265 -> 0 bytes .../main/res/drawable-xxhdpi/ic_launcher.png | Bin 10488 -> 0 bytes .../layout/activity_acu_barcode_camera.xml | 42 - .../layout/activity_acu_document_camera.xml | 34 - .../res/layout/activity_acu_mrz_camera.xml | 49 - ...ity_acu_camera.xml => activity_camera.xml} | 0 .../main/res/layout/barcode_fragment_ui.xml | 40 + .../main/res/layout/document_fragment_ui.xml | 32 + .../src/main/res/layout/fragment_camera.xml | 19 + .../res/layout/fragment_camera2_basic.xml | 73 - .../src/main/res/layout/mrz_fragment_ui.xml | 50 + .../src/main/res/values/base-strings.xml | 30 - .../src/main/res/values/template-styles.xml | 1 - acuantcommon/acuantcommon.aar | Bin 49242 -> 0 bytes acuantcommon/build.gradle | 2 - .../acuantdocumentprocessing.aar | Bin 76588 -> 0 bytes acuantdocumentprocessing/build.gradle | 2 - acuantechipreader/acuantechipreader.aar | Bin 59114 -> 0 bytes acuantechipreader/build.gradle | 2 - acuantfacecapture/build.gradle | 59 +- .../src/main/AndroidManifest.xml | 3 +- .../acuantfacecapture/FaceCaptureActivity.kt | 340 ---- .../camera/AcuantBaseFaceCameraFragment.kt | 361 ++++ .../camera/AcuantFaceCameraActivity.kt | 98 + .../facecapture/AcuantFaceCaptureFragment.kt | 224 +++ .../acuantfacecapture/constant/Constants.kt | 8 + .../detector/FaceDetector.kt | 38 - .../detector/FaceFrameAnalyzer.kt | 113 ++ .../detector/FaceListener.kt | 7 - .../detector/FaceProcessor.kt | 251 --- .../acuantfacecapture/helper/RectHelper.kt | 32 + .../interfaces/IAcuantSavedImage.kt | 7 + .../interfaces/IFaceCameraActivityFinish.kt | 8 + .../model/FaceCaptureOptions.kt | 5 +- .../model/FaceDetailState.java | 11 - .../acuantfacecapture/model/FaceDetails.kt | 18 - .../overlays/CameraSourcePreview.kt | 178 -- .../overlays/FacialGraphic.kt | 103 +- .../overlays/FacialGraphicOverlay.kt | 117 +- .../main/res/layout/activity_face_camera.xml | 8 + ..._face_capture.xml => face_fragment_ui.xml} | 25 +- .../main/res/layout/fragment_face_camera.xml | 14 + .../src/main/res/values/strings.xml | 5 +- acuantfacematch/acuantfacematch.aar | Bin 18983 -> 0 bytes acuantfacematch/build.gradle | 2 - acuanthgliveness/acuanthgliveness.aar | Bin 31271 -> 0 bytes acuanthgliveness/build.gradle | 2 - .../acuantimagepreparation.aar | Bin 2820008 -> 0 bytes acuantimagepreparation/build.gradle | 2 - acuantipliveness/acuantipliveness.aar | Bin 42137 -> 0 bytes acuantipliveness/build.gradle | 2 - .../acuantpassiveliveness.aar | Bin 26544 -> 0 bytes acuantpassiveliveness/build.gradle | 2 - app/build.gradle | 122 +- app/src/main/AndroidManifest.xml | 18 +- .../java/com/acuant/sampleapp/AppInstance.kt | 9 - .../ClassificationFailureActivity.kt | 4 +- .../acuant/sampleapp/ConfirmationActivity.kt | 9 +- .../java/com/acuant/sampleapp/Constants.kt | 14 - .../sampleapp/FacialLivenessActivity.kt | 255 --- .../java/com/acuant/sampleapp/MainActivity.kt | 1584 +++++++++-------- .../com/acuant/sampleapp/MrzHelpActivity.kt | 7 +- .../sampleapp/NfcConfirmationActivity.kt | 39 +- .../com/acuant/sampleapp/NfcResultActivity.kt | 2 +- .../java/com/acuant/sampleapp/NfcStore.kt | 4 - .../com/acuant/sampleapp/ProcessedData.java | 3 - .../com/acuant/sampleapp/ResultActivity.kt | 2 +- .../com/acuant/sampleapp/TestMainActivity.kt | 15 - .../backgroundtasks/AcuantTokenService.kt | 105 +- .../AcuantTokenServiceListener.kt | 5 +- .../facecapture/CameraSourcePreview.kt | 176 -- .../sampleapp/facecapture/FacialGraphic.kt | 65 - .../facecapture/FacialGraphicOverlay.kt | 255 --- .../acuant/sampleapp/utils/CommonUtils.java | 95 +- .../com/acuant/sampleapp/utils/DialogUtils.kt | 8 +- .../acuant/sampleapp/utils/ImageUtils.java | 63 - .../res/layout/activity_facial_liveness.xml | 24 - .../main/res/layout/activity_front_camera.xml | 22 - app/src/main/res/layout/activity_main.xml | 6 +- app/src/main/res/layout/activity_mrz_help.xml | 4 +- .../res/layout/activity_nfcconfirmation.xml | 4 +- .../main/res/layout/activity_rear_camera.xml | 22 - .../res/layout/face_capture_custom_view.xml | 7 - .../layout/fragment_front_camera2_basic.xml | 29 - .../layout/fragment_rear_camera2_basic.xml | 30 - app/src/main/res/layout/main.xml | 23 - app/src/main/res/values-es/strings.xml | 33 +- app/src/main/res/values-fil/strings.xml | 33 +- app/src/main/res/values/strings.xml | 46 +- build.gradle | 32 +- docs/MigrationDetails.md | 91 +- gradle.properties | 4 +- gradle/wrapper/gradle-wrapper.properties | 6 +- settings.gradle | 2 +- 168 files changed, 3982 insertions(+), 13479 deletions(-) delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/CapturedImage.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/AcuantBarcodeFeedback.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeCameraSource.java delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeCameraSourcePreview.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeCaptureActivity.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeDetector.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeFeedback.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeGraphicTracker.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeTrackerFactory.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/LiveBarcodeProcessor.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/AcuantDocumentFeedback.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCameraSource.java delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCameraSourcePreview.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCaptureActivity.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentDetector.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentFeedback.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentGraphicTracker.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentTrackerFactory.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/LiveDocumentProcessor.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/AcuantMrzFeedback.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/LiveMrzProcessor.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCameraSource.java delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCameraSourcePreview.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCaptureActivity.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzDetector.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzFeedback.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzGraphicTracker.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzTrackerFactory.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/detector/AcuantDetectorWorker.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/detector/BaseAcuantDetector.kt create mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/detector/DocumentFrameAnalyzer.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/detector/IAcuantDetector.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/detector/ImageSaver.kt create mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/detector/MrzFrameAnalyzer.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/detector/barcode/AcuantBarcodeDetector.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/detector/barcode/AcuantBarcodeDetectorHandler.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/detector/barcode/tracker/AcuantBarcodeTracker.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/detector/barcode/tracker/AcuantBarcodeTrackerFactory.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/detector/document/AcuantDocumentDetector.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/detector/document/AcuantDocumentDetectorHandler.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/detector/ocr/AcuantOcrDetector.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/detector/ocr/AcuantOcrDetectorHandler.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/helper/AutoFitTextureView.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/helper/CompareSizesByArea.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/helper/ConfirmationDialog.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/helper/ErrorDialog.kt create mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/helper/PointsUtils.kt create mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/interfaces/IAcuantSavedImage.kt create mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/interfaces/ICameraActivityFinish.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/AcuantOrientationListener.kt delete mode 100644 acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/ISetFromState.kt delete mode 100644 acuantcamera/src/main/res/drawable-hdpi/ic_action_info.png delete mode 100644 acuantcamera/src/main/res/drawable-hdpi/ic_launcher.png delete mode 100644 acuantcamera/src/main/res/drawable-hdpi/tile.9.png delete mode 100644 acuantcamera/src/main/res/drawable-mdpi/ic_action_info.png delete mode 100644 acuantcamera/src/main/res/drawable-mdpi/ic_launcher.png delete mode 100644 acuantcamera/src/main/res/drawable-xhdpi/ic_action_info.png delete mode 100644 acuantcamera/src/main/res/drawable-xhdpi/ic_launcher.png delete mode 100644 acuantcamera/src/main/res/drawable-xxhdpi/ic_action_info.png delete mode 100644 acuantcamera/src/main/res/drawable-xxhdpi/ic_launcher.png delete mode 100644 acuantcamera/src/main/res/layout/activity_acu_barcode_camera.xml delete mode 100644 acuantcamera/src/main/res/layout/activity_acu_document_camera.xml delete mode 100644 acuantcamera/src/main/res/layout/activity_acu_mrz_camera.xml rename acuantcamera/src/main/res/layout/{activity_acu_camera.xml => activity_camera.xml} (100%) create mode 100644 acuantcamera/src/main/res/layout/barcode_fragment_ui.xml create mode 100644 acuantcamera/src/main/res/layout/document_fragment_ui.xml create mode 100644 acuantcamera/src/main/res/layout/fragment_camera.xml delete mode 100644 acuantcamera/src/main/res/layout/fragment_camera2_basic.xml create mode 100644 acuantcamera/src/main/res/layout/mrz_fragment_ui.xml delete mode 100644 acuantcamera/src/main/res/values/base-strings.xml delete mode 100644 acuantcommon/acuantcommon.aar delete mode 100644 acuantcommon/build.gradle delete mode 100644 acuantdocumentprocessing/acuantdocumentprocessing.aar delete mode 100644 acuantdocumentprocessing/build.gradle delete mode 100644 acuantechipreader/acuantechipreader.aar delete mode 100644 acuantechipreader/build.gradle delete mode 100644 acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/FaceCaptureActivity.kt create mode 100644 acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/camera/AcuantBaseFaceCameraFragment.kt create mode 100644 acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/camera/AcuantFaceCameraActivity.kt create mode 100644 acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/camera/facecapture/AcuantFaceCaptureFragment.kt create mode 100644 acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/constant/Constants.kt delete mode 100644 acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/detector/FaceDetector.kt create mode 100644 acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/detector/FaceFrameAnalyzer.kt delete mode 100644 acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/detector/FaceListener.kt delete mode 100644 acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/detector/FaceProcessor.kt create mode 100644 acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/helper/RectHelper.kt create mode 100644 acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/interfaces/IAcuantSavedImage.kt create mode 100644 acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/interfaces/IFaceCameraActivityFinish.kt delete mode 100644 acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/model/FaceDetailState.java delete mode 100644 acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/model/FaceDetails.kt delete mode 100644 acuantfacecapture/src/main/java/com/acuant/acuantfacecapture/overlays/CameraSourcePreview.kt create mode 100644 acuantfacecapture/src/main/res/layout/activity_face_camera.xml rename acuantfacecapture/src/main/res/layout/{activity_face_capture.xml => face_fragment_ui.xml} (64%) create mode 100644 acuantfacecapture/src/main/res/layout/fragment_face_camera.xml delete mode 100644 acuantfacematch/acuantfacematch.aar delete mode 100644 acuantfacematch/build.gradle delete mode 100644 acuanthgliveness/acuanthgliveness.aar delete mode 100644 acuanthgliveness/build.gradle delete mode 100644 acuantimagepreparation/acuantimagepreparation.aar delete mode 100644 acuantimagepreparation/build.gradle delete mode 100644 acuantipliveness/acuantipliveness.aar delete mode 100644 acuantipliveness/build.gradle delete mode 100644 acuantpassiveliveness/acuantpassiveliveness.aar delete mode 100644 acuantpassiveliveness/build.gradle delete mode 100644 app/src/main/java/com/acuant/sampleapp/FacialLivenessActivity.kt delete mode 100644 app/src/main/java/com/acuant/sampleapp/TestMainActivity.kt delete mode 100644 app/src/main/java/com/acuant/sampleapp/facecapture/CameraSourcePreview.kt delete mode 100644 app/src/main/java/com/acuant/sampleapp/facecapture/FacialGraphic.kt delete mode 100644 app/src/main/java/com/acuant/sampleapp/facecapture/FacialGraphicOverlay.kt delete mode 100644 app/src/main/java/com/acuant/sampleapp/utils/ImageUtils.java delete mode 100644 app/src/main/res/layout/activity_facial_liveness.xml delete mode 100644 app/src/main/res/layout/activity_front_camera.xml delete mode 100644 app/src/main/res/layout/activity_rear_camera.xml delete mode 100644 app/src/main/res/layout/face_capture_custom_view.xml delete mode 100644 app/src/main/res/layout/fragment_front_camera2_basic.xml delete mode 100644 app/src/main/res/layout/fragment_rear_camera2_basic.xml delete mode 100644 app/src/main/res/layout/main.xml diff --git a/README.md b/README.md index ba6049c..32ed4aa 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# Acuant Android SDK v11.4.16 -**September 2021** +# Acuant Android SDK v11.5.0 +**January 2022** See [https://github.com/Acuant/AndroidSDKV11/releases](https://github.com/Acuant/AndroidSDKV11/releases) for release notes. @@ -22,25 +22,21 @@ This document provides detailed information about the Acuant Android SDK. The Ac ## Updates -**v11.4.4:** Please review [Migration Details](docs/MigrationDetails.md) for migration details (last updated for v11.4.4). +**v11.5.0:** Please review [Migration Details](docs/MigrationDetails.md) for migration details (last updated for v11.5.0). ---------- ## AndroidX Support -In order to maintain backward compatibility, the Acuant SDK is currently not compiled using AndroidX support libraries. However, the SDK may be used with AndroidX by using [Jetifier](https://developer.android.com/jetpack/androidx/migrate). +As of 11.5.0 the SDK is compiled with AndroidX and CameraX. -**Note:** This should be enabled by default when you migrate your project to AndroidX. - -- If you are using Acuant’s compiled AAR files, or if you get the SDK from Maven, no additional action is required (aside from verifying that Jetifier is enabled). - -- If you are customizing any of Acuant’s open modules, you will need to build the library into an AAR file, and then use that AAR file in your app along with Jetifier. +Before 11.5.0, the SDK was not compiled with AndroidX. The SDK could still be used with AndroidX by using [Jetifier](https://developer.android.com/jetpack/androidx/migrate). ---------- ## Prerequisites ## -- Supports Android SDK versions 21-30 +- Supports Android SDK versions 21-31 (compiled with 31) ## Modules ## @@ -49,42 +45,39 @@ The SDK includes the following modules: **Acuant Common Library (AcuantCommon) :** -- Contains all shared internal models and supporting classes +- Contains shared internal models and supporting classes. **Acuant Camera Library (AcuantCamera) :** -- Implemented using Camera 2 API with Google Vision for reading PDF417 barcodes -- Encompasses two different versions of the camera, one for reading documents, the other for reading MRZ zones. -- Uses AcuantImagePreparation for cropping +- Implemented using CameraX API and uses Google ML Kit for barcode reading. ML Kit model is packaged in the SDK (no outbound call to download model from Google Play services). +- Encompasses three different versions of the camera for reading document and barcodes, reading MRZ zones, and a backup camera for reading only barcodes. +- Uses AcuantImagePreparation for document detection and cropping. **Acuant Image Preparation Library (AcuantImagePreparation) :** -- Contains all image processing including cropping and calculation of sharpness and glare +- Contains all image processing including document detection, cropping, and metrics calculation. **Acuant Document Processing Library (AcuantDocumentProcessing) :** -- Contains all the methods to upload the document images, process, and get results +- Contains all the methods to upload and process document images. **Acuant Face Match Library (AcuantFaceMatch) :** -- Contains a method to match two facial images +- Contains a method to match two face images. **Acuant EChip Reader Library (AcuantEChipReader):** -- Contains methods for e-Passport chip reading and authentication using Ozone +- Contains methods for e-Passport chip reading and authentication using Ozone. **Acuant IP Liveness Library (AcuantIPLiveness):** -- Uses library for capturing a facial image and calculating liveness -- Enhanced Face Liveness - -**Acuant HG Liveness Library (AcuantHGLiveness):** - -- Uses Camera 1 to capture facial liveness using a proprietary algorithm +- Uses library for capturing a facial image and calculating liveness. +- Enhanced Face Liveness. **Acuant Face Capture Library (AcuantFaceCapture):** -- Uses Camera 1 to capture a single face image for use with our passive liveness system +- Contains two CameraX implementations for capturing a user's face image. +- Intended to be used with with Acuant Passive Liveness to determine a user's liveness or as a standalone, primitive liveness check. **Acuant Passive Liveness Library (AcuantPassiveLiveness):** @@ -96,114 +89,42 @@ The SDK includes the following modules: 1. Specify the permissions in the App manifest file: - - - - + + + - + - - 1. Add the Acuant SDK dependency in **build.gradle**: - repositories { - //Face Capture and Barcode reading. Only add if using acuantcamera or acuanthgliveness - maven { url 'https://maven.google.com' } - maven { url 'https://raw.githubusercontent.com/iProov/android/master/maven/' } - } - - dependencies { - //if possible, use v7:28.0.0 for android support version - //implementation 'com.android.support:appcompat-v7:28' - //implementation 'com.android.support:support-v4:28.0.0' - //implementation 'com.android.support:appcompat-v7:28.0.0' - //implementation 'com.android.support:exifinterface:28.0.0' - - //Face Capture and Barcode reading. Only add if using acuantcamera or acuanthgliveness - implementation 'com.google.android.gms:play-services-vision:17.0.2' - - //External library for MRZ reading. Only add if using the MRZ part of acuantcamera - implementation 'com.rmtheis:tess-two:9.0.0' - - //external libraries for echip reading. Only add if using acuantechipreader - implementation group: 'com.github.mhshams', name: 'jnbis', version: '1.0.4' - implementation('org.jmrtd:jmrtd:0.7.11') { - transitive = true; - } - implementation('org.ejbca.cvc:cert-cvc:1.4.6') { - transitive = true; - } - implementation('org.bouncycastle:bcprov-jdk15on:1.61') { - transitive = true; + - Add the following under `android` (if not already present) + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } - implementation('net.sf.scuba:scuba-sc-android:0.0.18') { - transitive = true; + kotlinOptions { + jvmTarget = "1.8" } - //end echip reading - - //internal common library - implementation project(path: ':acuantcommon') - - //camera with autocapture - Uses camera 2 API - implementation project(path: ':acuantcamera') - //document parse, classification, authentication - implementation project(path: ':acuantdocumentprocessing') + - Add the following Maven URLs - //face match library - implementation project(path: ':acuantfacematchsdk') - - //for reading epassport chips - implementation project(path: ':acuantechipreader') - - //face capture and liveliness - implementation project(path: ':acuantipliveness') - implementation('com.iproov.sdk:iproov:5.2.1@aar') { - transitive = true - } - - //face capture and liveliness - implementation project(path: ':acuanthgliveness') - - //image processing (cropping, glare, sharpness) - implementation project(path: ':acuantimagepreparation') - - //face capture - implementation project(path: ':acuantfacecapture') - - //passive liveness - implementation project(path: ':acuantpassiveliveness') - } - -1. Add the Acuant SDK dependency in **build.gradle** if using Maven: - - - Add the following Maven URL - - maven { url 'https://raw.githubusercontent.com/Acuant/AndroidSdkMaven/main/maven/' } + maven { url 'https://maven.google.com' } + maven { url 'https://raw.githubusercontent.com/Acuant/AndroidSdkMaven/main/maven/' } maven { url 'https://raw.githubusercontent.com/iProov/android/master/maven/' } - Add the following dependencies - implementation 'com.acuant:acuantcommon:11.4.16' - implementation 'com.acuant:acuantcamera:11.4.16' - implementation 'com.acuant:acuantimagepreparation:11.4.16' - implementation 'com.acuant:acuantdocumentprocessing:11.4.16' - implementation 'com.acuant:acuantechipreader:11.4.16' - implementation 'com.acuant:acuantfacematch:11.4.16' - implementation 'com.acuant:acuanthgliveness:11.4.16' - implementation ('com.acuant:acuantipliveness:11.4.16'){ - transitive = true - } - implementation 'com.acuant:acuantfacecapture:11.4.16' - implementation 'com.acuant:acuantpassiveliveness:11.4.16' - - - Acuant also relies on Google Play services dependencies, which are pre-installed on almost all Android devices. - + implementation 'com.acuant:acuantcommon:11.5.0' + implementation 'com.acuant:acuantcamera:11.5.0' + implementation 'com.acuant:acuantimagepreparation:11.5.0' + implementation 'com.acuant:acuantdocumentprocessing:11.5.0' + implementation 'com.acuant:acuantechipreader:11.5.0' + implementation 'com.acuant:acuantipliveness:11.5.0' + implementation 'com.acuant:acuantfacematch:11.5.0' + implementation 'com.acuant:acuantfacecapture:11.5.0' + implementation 'com.acuant:acuantpassiveliveness:11.5.0' 1. Create an xml file with the following tags (If you plan to use bearer tokens to initialize, then username and password can be left blank): @@ -264,55 +185,51 @@ The SDK includes the following modules: Before you use the SDK, you must initialize it, either by using the credentials saved on the device or by using bearer tokens (provided by an external server). -**Note:** If you are *not* using a configuration file for initialization, then use the following statement (providing appropriate credentials for *username*, *password*, and *subscription ID*) and leave the "PATH/TO/CONFIG/FILENAME.XML" in the initialize method as "" - - 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") - -* Using credentials saved on a device: - +* Using bearer tokens: - //Specify the path to the previously created XML file, using “assets” as root - //Pass in Context from Application - //List the packages to initialize; only ImageProcessor is required + try { + AcuantInitializer.initializeWithToken("PATH/TO/CONFIG/FILENAME.XML", + token, + context, + listOf(ImageProcessorInitializer(), EchipInitializer(), MrzCameraInitializer()), + listener) + } catch(e: AcuantException) { + Log.e("Acuant Error", e.toString()) + } - try{ +* Using credentials saved on device in a config file: + + try { AcuantInitializer.initialize("PATH/TO/CONFIG/FILENAME.XML", - context, - listOf(ImageProcessorInitializer(), EchipInitializer(), MrzCameraInitializer() /\*Exclude the ones you don't use\*/), - listener) - } - catch(e: AcuantException){ + context, + listOf(ImageProcessorInitializer(), EchipInitializer(), MrzCameraInitializer()), + listener) + } catch(e: AcuantException) { Log.e("Acuant Error", e.toString()) } - -* Using bearer tokens: - - //Specify the path to the previously created XML file, using “assets” as root - //Pass in Context from Application - //List the packages to initialize; only ImageProcessor is required - - //having received the bearer token from your service - try{ - AcuantInitializer.initializeWithToken("PATH/TO/CONFIG/FILENAME.XML", - token, - context, - listOf(ImageProcessorInitializer(), EchipInitializer(), MrzCameraInitializer() /\*Exclude the ones you don't use\*/), - listener) - } - catch(e: AcuantException){ +* 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") + + AcuantInitializer.initialize(null, + context, + listOf(ImageProcessorInitializer(), EchipInitializer(), MrzCameraInitializer()), + listener) + } catch(e: AcuantException) { Log.e("Acuant Error", e.toString()) } -Here is the interface for the initialize listener: +* Here is the interface for the initialize listener: interface IAcuantPackageCallback{ fun onInitializeSuccess() @@ -320,15 +237,10 @@ Here is the interface for the initialize listener: fun onInitializeFailed(error: List) } -### Initialization without a Subscription ID ### - -**AcuantImagePreparation** may be initialized by providing only a username and a password. However, without providing a Subscription ID, the application can *only* capture an image and get the image. Without a Subscription ID: -1. Only the **AcuantCamera**, **AcuantImagePreparation**, and **AcuantHGLiveness** modules may be used. - -2. The SDK can be used to capture identity documents. +### Initialization without a Subscription ID ### -3. The captured images can be exported from the SDK. See the **onActivityResult** in the following section. +The SDK can be initialized by providing only a username and a password. However, without a Subscription ID, some features of the SDK are unavailable. In general, the SDK can capture images, but cannot make most outbound calls, such as uploading documents. ---------- @@ -343,7 +255,6 @@ Here is the interface for the initialize listener: this@MainActivity, AcuantCameraActivity::class.java ) - cameraIntent.putExtra(ACUANT_EXTRA_CAMERA_OPTIONS, AcuantCameraOptions .DocumentCameraOptionsBuilder() @@ -351,24 +262,30 @@ Here is the interface for the initialize listener: .build() ) - startActivityForResult(cameraIntent, REQUEST_CODE) - + //start activity for 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. + * Live document detection and auto capture features are enabled if the device supports a speed of at least 200ms. * 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) - - if (requestCode == REQUEST_CODE && AcuantCameraActivity.RESULT_SUCCESS_CODE) { - val capturedImageUrl = data?.getStringExtra(ACUANT_EXTRA_IMAGE_URL) - val capturedBarcodeString = data?.getStringExtra(ACUANT_EXTRA_PDF417_BARCODE) + if (result.resultCode == RESULT_OK) { + val data: Intent? = result.data + val url = data?.getStringExtra(ACUANT_EXTRA_IMAGE_URL) + val barcodeString = data?.getStringExtra(ACUANT_EXTRA_PDF417_BARCODE) + //... + } else if (result.resultCode == RESULT_CANCELED) { + //... + } else { + val data: Intent? = result.data + val error = data?.getSerializableExtra(ACUANT_EXTRA_ERROR) + if (error is AcuantError) { + //... } - } - + } + ### Capturing a document barcode ### **Note:** During regular capture of a document the camera will try to read the barcode. You should only launch this camera mode if the barcode is expected according to document classification and failed to read during normal capture of the relevant side. @@ -387,19 +304,23 @@ Here is the interface for the initialize listener: .build() ) - startActivityForResult(cameraIntent, REQUEST_CODE) - + //start activity for result + 1. Get activity result: - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - if (requestCode == REQUEST_CODE && AcuantCameraActivity.RESULT_SUCCESS_CODE) { - val capturedBarcodeString = data?.getStringExtra(ACUANT_EXTRA_PDF417_BARCODE) + if (result.resultCode == RESULT_OK) { + val data: Intent? = result.data + val capturedBarcodeString = data?.getStringExtra(ACUANT_EXTRA_PDF417_BARCODE) + //... + } else if (result.resultCode == RESULT_CANCELED) { + //... + } else { + val data: Intent? = result.data + val error = data?.getSerializableExtra(ACUANT_EXTRA_ERROR) + if (error is AcuantError) { + //... } - } - -**Note:** This camera is completely reliant on Google Vision. If Google Services are unavailable, the camera will not launch and onActivityResult will instantly be called along with a null value for the barcode. To keep your workflow neater, we recommend checking for Google Services before launching the camera. + } ### Capturing MRZ data in a passport document ### @@ -409,8 +330,6 @@ Here is the interface for the initialize listener: **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. @@ -429,18 +348,25 @@ Here is the interface for the initialize listener: .build() ) - startActivityForResult(cameraIntent, REQUEST_CODE) - + //start activity for result + 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 + + if (result.resultCode == RESULT_OK) { + val data: Intent? = result.data + val result = data?.getSerializableExtra(ACUANT_EXTRA_MRZ_RESULT) as MrzResult? + //... + } else if (result.resultCode == RESULT_CANCELED) { + //... + } else { + val data: Intent? = result.data + val error = data?.getSerializableExtra(ACUANT_EXTRA_ERROR) + if (error is AcuantError) { + //... } - } - + } + ---------- ## AcuantImagePreparation ## @@ -465,11 +391,16 @@ This section describes how to use **AcuantImagePreparation**. and a callback listener: - interface EvaluateImageListener { + interface EvaluateImageListener: AcuantListener { fun onSuccess(image: AcuantImage) - fun onError(error: Error) } - + + * **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) + } + 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)). @@ -502,147 +433,208 @@ After you capture a document image and completed crop, it can be processed using 1. Create an instance: - public static void createInstance(IdOptions options, CreateInstanceListener listener) + fun createInstance(options: IdInstanceOptions, listener: CreateIdInstanceListener) - public interface CreateInstanceListener { - void instanceCreated(String instanceId, Error error); + interface CreateIdInstanceListener : AcuantListener { + fun instanceCreated(instance: AcuantIdDocumentInstance) } - -2. Upload an image: - public static void uploadImage(String instanceID, EvaluatedImageData imageData, IdOptions options, UploadImageListener listener) +1. All further methods will be called on the instanced returned thorough the instanceCreated callback. You can run multiple instances simultaneously. Each instances tracks its own state independently. These are the available methods and relevant objects/interfaces: + + fun uploadFrontImage(imageData: EvaluatedImageData, listener: UploadImageListener) - class EvaluatedImageData ( - val imageBytes: ByteArray, - val barcodeString: String? = null - ) + fun uploadBackImage(imageData: EvaluatedImageData, listener: UploadImageListener) - public interface UploadImageListener { - void imageUploaded(Error error, Classification classification); - } + class EvaluatedImageData(imageBytes: ByteArray) + + interface UploadImageListener : AcuantListener { + fun imageUploaded() + } -**Important Note:** The image bytes in EvaluatedImageData should be the bytes from AcuantImage.rawBytes not the bytes from the bitmap stored within. Similarly if you are not using **AcuantDocumentProcessing** and uploading the image in some other way you should also be uploading these bytes. -3. Get the data: + fun uploadBarcode(barcodeData: BarcodeData, listener: UploadBarcodeListener) - public static void getData(String instanceID,boolean isHealthCard, GetDataListener listener) + interface UploadBarcodeListener : AcuantListener { + fun barcodeUploaded() + } - public interface GetDataListener { - void processingResultReceived(ProcessingResult result); - } - -4. Delete the instance: + fun getClassification(listener: ClassificationListener) + + interface ClassificationListener: AcuantListener { + fun documentClassified(classified: Boolean, classification: Classification) + } + + fun getData(listener: GetIdDataListener) + + interface GetIdDataListener : AcuantListener { + fun processingResultReceived(result: IDResult) + } + + fun deleteInstance(listener: DeleteListener) + + interface DeleteListener : AcuantListener { + fun instanceDeleted() + } +For most workflows, the steps resemble the following, with reuploads on error or failed classification: - public static void deleteInstance(String instanceId, DeleteType type, DeleteListener listener) + createInstance + uploadFrontImage + getClassification + uploadBackImage && uploadBarcode + getData + deleteInstance - public interface DeleteListener { - public void instanceDeleted(boolean success); - } - ------------------------------------- ## AcuantIPLiveness ## -**Important Note:** The following must be in your root level gradle in the android{} section otherwise a runtime failure may occur: - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = "1.8" - } - 1. Get the setup from the controller and begin Activity: - AcuantIPLiveness.getFacialSetup(object :FacialSetupLisenter{ - override fun onDataReceived(result: FacialSetupResult?) { - if(result != null) { - //start face capture activity - result.allowScreenshots = true //Set to false by default; set to true to enable allowScreenshots - AcuantIPLiveness.runFacialCapture(context, result, listener) - } - else { - //handle error - } + AcuantIPLiveness.getFacialSetup(object : FacialSetupListener { + override fun onDataReceived(result: FacialSetupResult) { + AcuantIPLiveness.runFacialCapture(this@MainActivity, result, object : IPLivenessListener { + override fun onConnecting() { + /... + } + + override fun onConnected() { + /... + } + + override fun onProgress(status: String, progress: Int) { + /... + } + + override fun onSuccess(userId: String, token: String, frame: Bitmap?) { + /... + } + + override fun onFail(error: AcuantError) { + /... + } + + override fun onCancel() { + /... + } + + override fun onError(error: AcuantError) { + /... + } + }) } - - override fun onError(errorCode: Int, description: String?) { - //handle error + + override fun onError(error: AcuantError) { + /... } }) - -2. Get the result: - - //implement the following listener - interface IPLivenessListener { - fun onProgress(status: String, progress: Int) // for displaying the progress of liveness analysis after capture, progress = 0 to 100, status = text description of current step - fun onSuccess(userId: String, token: String) - fun onFail(error: Error) - fun onCancel() // called when no error occurred but user canceled/backed out of the process - } - + 3. Get the facial capture result (call after onSuccess in IPLivenessListener): - //isPassed = true if face is live; otherwise false - //frame contains the base64 encoded image - data class FacialCaptureResult (isPassed: Boolean, frame: String) - AcuantIPLiveness.getFacialLiveness( token, userId, - object: FacialCaptureLisenter { - override fun onDataReceived(result: FacialCaptureResult) { - //use result + object: FacialCaptureListener { + override fun onDataReceived(result: FacialCaptureResult { + //... } - - override fun onError(errorCode:Int, errorDescription: String) { - //handle error + + override fun onError(error: AcuantError) { + //... } } ) ------------------------------------- - -### AcuantHGLiveness ### -This module checks for liveness (whether the subject is a live person) by using blink detection. +## AcuantFaceCapture ## -1. Begin Activity: +This module is used to automate capturing an image of a face appropriate for use with passive liveness. + +1. Start the face capture activity: val cameraIntent = Intent( this@MainActivity, - FacialLivenessActivity::class.java + AcuantFaceCameraActivity::class.java ) - startActivityForResult(cameraIntent, YOUR_REQUEST_CODE) - -2. Get the Activity result: + + cameraIntent.putExtra(ACUANT_EXTRA_FACE_CAPTURE_OPTIONS, FaceCaptureOptions()) + + //start activity for result + +2. Receive the result from the face capture activity: + + when (result.resultCode) { + RESULT_OK -> { + val data = result.data + val url = data?.getStringExtra(ACUANT_EXTRA_FACE_IMAGE_URL) + //... + } + RESULT_CANCELED -> { + //... + } + else -> { + //error... + } + +**Note:** HGLiveness/Blink Test Liveness can be accessed by modifying the options as follows: + + FaceCaptureOptions(cameraMode = CameraMode.HgLiveness) + +------------------------------------- + +## AcuantPassiveLiveness ## +This module is used to determine liveness from a single selfie image. + +1. Call and handle response: + + AcuantPassiveLiveness.processFaceLiveness(passiveLivenessData: PassiveLivenessData, listener: PassiveLivenessListener) - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) + class PassiveLivenessData(faceImage: Bitmap) + interface PassiveLivenessListener: AcuantListener { + fun passiveLivenessFinished(result: PassiveLivenessResult) + } + + class PassiveLivenessResult { + var livenessAssessment: LivenessAssessment? = null + var transactionId: String? = null + var score = 0 + var errorDesc: String? = null + var errorCode: PassiveLivenessErrorCode? = null + } - if (requestCode == YOUR_REQUEST_CODE) { - if(resultCode == FacialLivenessActivity.RESPONSE_SUCCESS_CODE){ - val faceImage = FaceCapturedImage.bitmapImage - } - else{ - //handle error - } - } + enum class LivenessAssessment { + Error, + PoorQuality, + Live, + NotLive } - + enum class PassiveLivenessErrorCode { + Unknown, + FaceTooClose, + FaceNotFound, + FaceTooSmall, + FaceAngleTooLarge, + FailedToReadImage, + InvalidRequest, + InvalidRequestSettings, + Unauthorized, + NotFound + } + +------------------------------------- + ## AcuantFaceMatch ## This module is used to match two facial images: - fun processFacialMatch(facialData: FacialMatchData, listener: FacialMatchListener?) + fun processFacialMatch(facialData: FacialMatchData, listener: FacialMatchListener) - public interface FacialMatchListener { - public void facialMatchFinished(FacialMatchResult result); + interface FacialMatchListener: AcuantListener { + fun facialMatchFinished(result: FacialMatchResult) } ------------------------------------- @@ -671,6 +663,7 @@ Must include EchipInitializer() in initialization (See **Initializing the SDK**) 3. Make sure that the NFC sensor on the device is turned on. 4. Initialize the Android NFC Adapter: + NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this) 5. Use the SDK to listen to NFC tags available in an ePassport: @@ -682,166 +675,69 @@ Must include EchipInitializer() in initialization (See **Initializing the SDK**) override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) - AcuantEchipReader.readNfcTag(this, intent, Credential.get(), docNumber, dateOfBirth, dateOfExpiry, listener) + AcuantEchipReader.readNfcTag(this, intent, docNumber, dateOfBirth, dateOfExpiry, listener) } + This is the interface for the listener: - interface NfcTagReadingListener { + interface NfcTagReadingListener: AcuantListener { fun tagReadSucceeded(nfcData: NfcData) - - fun tagReadFailed(error: Error) - + fun tagReadStatus(status: String) } - -**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 ## +**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 through Calendar.getInstance().time). Potentially, this can 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 that occurs because the DOE and DOB are stored in YYMMDD format, which is susceptible to the Y2K issue. Given a year of 22, we cannot determine with 100 percent certainty whether the 22 represents 1922 or 2022 or, theoretically, 2122. The workaround is as follows: For age, the current year is the breakpoint (for example, in 2020, 25 would be interpreted as 1925. However, in 2030, 25 would be interpreted as 2025. For isExpired, we use the same logic but going forward 20 years from the current year. -This module is used to automate capturing an image of a face appropriate for use with passive liveness. - -1. Start the face capture activity: - - val cameraIntent = Intent( - this@MainActivity, - FaceCaptureActivity::class.java - ) - - /\*Optional, should only be used if you are changing some of the options, pointless to pass default options \*/ - cameraIntent.putExtra(ACUANT_EXTRA_FACE_CAPTURE_OPTIONS, FaceCaptureOptions()) - - startActivityForResult(cameraIntent, YOUR_REQUEST_CODE) - -2. Receive the result from the face capture activity: - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - if (requestCode == YOUR_REQUEST_CODE) { - when (resultCode) { - FaceCaptureActivity.RESPONSE_SUCCESS_CODE -> { - val bytes = readFromFile(data?.getStringExtra(FaceCaptureActivity.OUTPUT_URL)) - val capturedSelfieImage = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) - //do whatever you want with the image - } - FaceCaptureActivity.RESPONSE_CANCEL_CODE -> { - //handle user canceling - } - else -> { - //handle error during capture - } - } - } - } - -------------------------------------- - -## AcuantPassiveLiveness ## - -This module is used to determine liveness from a single selfie image. - -1. Call and handle response: - - val plData = PassiveLivenessData(capturedSelfieImage) - AcuantPassiveLiveness.processFaceLiveness(plData, object : PassiveLivenessListener { - override fun passiveLivenessFinished(result: PassiveLivenessResult) { - when (result.livenessAssessment) { - AcuantPassiveLiveness.LivenessAssessment.Live -> { - //handle live person - } - AcuantPassiveLiveness.LivenessAssessment.NotLive -> { - //handle not live person - } - AcuantPassiveLiveness.LivenessAssessment.PoorQuality -> { - //handle input image being too poor quality - } - else -> { - //handle error - } - } - } - }) - -Relevant Enums: - - enum class LivenessAssessment { - Error, - PoorQuality, - Live, - NotLive - } - - enum class PassiveLivenessErrorCode { - Unknown, - FaceTooClose, - FaceNotFound, - FaceTooSmall, - FaceAngleTooLarge, - FailedToReadImage, - InvalidRequest, - InvalidRequestSettings, - Unauthorized, - NotFound, - InternalError, - InvalidJson - } ------------------------------------- ### Error codes ### - public class ErrorCodes - { - public final static int ERROR_InvalidCredentials = -1; - public final static int ERROR_InvalidLicenseKey = -2; - public final static int ERROR_InvalidEndpoint = -3; - public final static int ERROR_InitializationNotFinished = -4; - public final static int ERROR_Network = -5; - public final static int ERROR_InvalidJson = -6; - public final static int ERROR_CouldNotCrop = -7; - public final static int ERROR_NotEnoughMemory = -8; - public final static int ERROR_BarcodeCaptureFailed = -9; - public final static int ERROR_BarcodeCaptureTimedOut = -10; - public final static int ERROR_BarcodeCaptureNotAuthorized = -11; - public final static int ERROR_LiveFaceCaptureNotAuthorized = -12; - public final static int ERROR_CouldNotCreateConnectInstance = -13; - public final static int ERROR_CouldNotUploadConnectImage = -14; - public final static int ERROR_CouldNotUploadConnectBarcode = -15; - public final static int ERROR_CouldNotGetConnectData = -16; - public final static int ERROR_CouldNotProcessFacialMatch = -17; - public final static int ERROR_CardWidthNotSet = -18; - public final static int ERROR_CouldNotGetHealthCardData = -19; - public final static int ERROR_CouldNotClassifyDocument = -20; - public final static int ERROR_LowResolutionImage = -21; - public final static int ERROR_CAPTURING_FACIAL = -22; - public final static int ERROR_NETWORK_FACIAL = -23; - public final static int USER_CANCELED_FACIAL = -24; + object ErrorCodes { + const val ERROR_InvalidCredentials = -1 + const val ERROR_BlankBarcode = -2 + const val ERROR_InvalidEndpoint = -3 + const val ERROR_Network = -4 + const val ERROR_InvalidJson = -5 + const val ERROR_CouldNotCrop = -6 + const val ERROR_NotEnoughMemory = -7 + const val ERROR_LowResolutionImage = -8 + const val ERROR_Permissions = -9 + const val ERROR_SavingImage = -10 + const val ERROR_CAPTURING_FACIAL = -1001 + const val ERROR_SETUP_FACIAL = -1003 + const val ERROR_FailedToLoadOcrFiles = -2001 + const val ERROR_EChipReadError = -3001 + const val ERROR_InvalidNfcTag = -3002 + const val ERROR_InvalidNfcKeyFormatting = -3003 + const val ERROR_UnexpectedError = -9999 } + ### Error descriptions ### - public class ErrorDescriptions { - public final static String ERROR_DESC_InvalidCredentials = "Invalid credentials"; - public final static String ERROR_DESC_InvalidLicenseKey = "Invalid License Key"; - public final static String ERROR_DESC_InvalidEndpoint = "Invalid endpoint"; - public final static String ERROR_DESC_InitializationNotFinished = "Initialization not finished"; - public final static String ERROR_DESC_InvalidJson = "Invalid Json response"; - public final static String ERROR_DESC_CouldNotCrop = "Could not crop image"; - public final static String ERROR_DESC_BarcodeCaptureFailed = "Barcode capture failed"; - public final static String ERROR_DESC_BarcodeCaptureTimedOut = "Barcode capture timed out"; - public final static String ERROR_DESC_BarcodeCaptureNotAuthorized = "Barcode capture is not authorized"; - public final static String ERROR_DESC_LiveFaceCaptureNotAuthorized = "Live face capture is not authorized"; - public final static String ERROR_DESC_CouldNotCreateConnectInstance = "Could not create connect Instance"; - public final static String ERROR_DESC_CouldNotUploadConnectImage = "Could not upload image to connect instance"; - public final static String ERROR_DESC_CouldNotUploadConnectBarcode = "Could not upload barcode to connect instance"; - public final static String ERROR_DESC_CouldNotGetConnectData = "Could not get connect image data"; - public final static String ERROR_DESC_CardWidthNotSet = "Card width not set"; - public final static String ERROR_DESC_CouldNotGetHealthCardData = "Could not get health card data"; - public final static String ERROR_DESC_CouldNotClassifyDocument = "Could not classify document"; - public final static String ERROR_DESC_LowResolutionImage = "Low resolution image"; - public final static String ERROR_DESC_NETWORK_FACIAL_IPROOV = "Failed to connect to IProov"; + object ErrorDescriptions { + const val ERROR_DESC_InvalidCredentials = "Invalid credentials" + const val ERROR_DESC_BlankBarcode = "Blank barcode, skipped upload." + const val ERROR_DESC_InvalidEndpoint = "Invalid/unapproved endpoint" + const val ERROR_DESC_Network = "Network request failed" + const val ERROR_DESC_InvalidJson = "Invalid Json response" + const val ERROR_DESC_CouldNotCrop = "Could not crop image" + const val ERROR_DESC_NotEnoughMemory = "Ran out of memory" + const val ERROR_DESC_LowResolutionImage = "Low resolution image" + const val ERROR_DESC_Permissions = "Required permission was not granted" + const val ERROR_DESC_SavingImage = "Error while saving an image from the camera" + const val ERROR_DESC_CAPTURING_FACIAL_IPROOV = "Failed to capture during IProov" + const val ERROR_DESC_SETUP_FACIAL_IPROOV = "Failed to set up IProov" + const val ERROR_DESC_FailedToLoadOcrFiles = "Failed to load ocrb.traineddata" + const val ERROR_DESC_EChipReadError = + "Error reading eChip. Connection lost to passport or incorrect key." + const val ERROR_DESC_InvalidNfcTag = + "Tag Tech list was null. Most likely means unsupported passport/not a passport" + const val ERROR_DESC_InvalidNfcKeyFormatting = + "Decryption key formatted incorrectly. Check DOB, DOE, and doc number." + const val ERROR_DESC_UnexpectedError = + "Unexpected error occurred, usually indicates a try catch caught an error that was not expected to be hit." } ### Image ### @@ -858,10 +754,6 @@ Relevant Enums: ### AcuantCameraOptions ### - class AcuantCameraOptions () - -**Note:** The camera options should be built using one of the following builders. This will also tell the camera what capture mode to use (Document, MRZ, or Barcode): - class DocumentCameraOptionsBuilder { fun setTimeInMsPerDigit(value: Int) : DocumentCameraOptionsBuilder fun setDigitsToShow(value: Int) : DocumentCameraOptionsBuilder @@ -982,4 +874,3 @@ information regarding such designations and their registration status. **Acuant Inc.** **6080 Center Drive, Suite 850,** **Los Angeles, CA 90045** ---------------------------------------------------- - diff --git a/acuantcamera/build.gradle b/acuantcamera/build.gradle index 9568fa2..fb49ccb 100644 --- a/acuantcamera/build.gradle +++ b/acuantcamera/build.gradle @@ -1,22 +1,13 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' - -repositories { - google() -} +apply plugin: 'maven-publish' android { - compileSdkVersion 29 + compileSdkVersion 31 defaultConfig { minSdkVersion 21 - targetSdkVersion 29 - versionCode 1 - versionName "1.0" - - //testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - + targetSdkVersion 31 } buildTypes { @@ -26,19 +17,44 @@ android { } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + // For Kotlin projects + kotlinOptions { + jvmTarget = "1.8" + } + + buildFeatures { + viewBinding true + } } dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'com.android.support:exifinterface:28.0.0' - implementation 'com.android.support:appcompat-v7:28.0.0' - implementation 'com.android.support:support-v4:28.0.0' - implementation 'com.android.support:support-media-compat:28.0.0' - implementation 'com.android.support.constraint:constraint-layout:1.1.3' - implementation 'com.google.android.gms:play-services-vision:17.0.2' - implementation 'com.rmtheis:tess-two:9.0.0' - implementation project(path: ':acuantcommon') - implementation project(path: ':acuantimagepreparation') - + // Kotlin lang + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2' + + // App compat and UI things + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.2' + //noinspection GradleDependency + implementation 'androidx.window:window:1.0.0-alpha09' //newer versions changed the api, not updating yet + + // CameraX library + def camerax_version = '1.1.0-alpha12' + implementation "androidx.camera:camera-core:$camerax_version" + implementation "androidx.camera:camera-camera2:$camerax_version" + implementation "androidx.camera:camera-lifecycle:$camerax_version" + implementation "androidx.camera:camera-view:1.0.0-alpha32" + + //acuant specific stuff + implementation 'androidx.exifinterface:exifinterface:1.3.3' + implementation 'com.google.mlkit:barcode-scanning:17.0.1' + implementation 'com.rmtheis:tess-two:9.1.0' + implementation 'com.acuant:acuantcommon:11.5.0' + implementation 'com.acuant:acuantimagepreparation:11.5.0' } \ No newline at end of file diff --git a/acuantcamera/src/main/AndroidManifest.xml b/acuantcamera/src/main/AndroidManifest.xml index 6faefd7..05305e2 100644 --- a/acuantcamera/src/main/AndroidManifest.xml +++ b/acuantcamera/src/main/AndroidManifest.xml @@ -1,34 +1,23 @@ - - - - + - + + - - + android:name="com.acuant.acuantcamera.camera.AcuantCameraActivity" + android:screenOrientation="portrait" + android:rotationAnimation="seamless" + android:resizeableActivity="false" + tools:targetApi="29" /> - - - - - \ No newline at end of file diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/CapturedImage.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/CapturedImage.kt deleted file mode 100644 index 43a4ab7..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/CapturedImage.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.acuant.acuantcamera - -import android.graphics.Bitmap -import com.acuant.acuantcommon.model.Image - -/** - * Created by tapasbehera on 4/30/18. - */ -@Suppress("DEPRECATION") -@Deprecated("Not used by SDK, does not belong in this module, implement in app if desired") -class CapturedImage { - companion object { - @Deprecated("Not used by SDK, does not belong in this module, implement in app if desired") - var bitmapImage: Bitmap? = null - @Deprecated("Not used by SDK, does not belong in this module, implement in app if desired") - var acuantImage: Image? = null - @Suppress("unused") - @Deprecated("Not used by SDK, does not belong in this module, implement in app if desired") - var barcodeString : String? = null - @Suppress("unused") - @Deprecated("Not used by SDK, does not belong in this module, implement in app if desired") - var sharpnessScore : Int = 0 - @Suppress("unused") - @Deprecated("Not used by SDK, does not belong in this module, implement in app if desired") - var glareScore : Int = 0 - - @Suppress("DeprecatedCallableAddReplaceWith") - @Deprecated("Not used by SDK, does not belong in this module, implement in app if desired") - fun setImage(image:Bitmap?){ - bitmapImage = image - } - - @Suppress("DeprecatedCallableAddReplaceWith") - @Deprecated("Not used by SDK, does not belong in this module, implement in app if desired") - fun setImage(image:Image?){ - acuantImage = image - } - - @Deprecated("Not used by SDK, does not belong in this module, implement in app if desired") - fun clear(){ - if(bitmapImage!=null){ - if(!bitmapImage!!.isRecycled) { - bitmapImage!!.recycle() - } - } - if(acuantImage!=null && acuantImage!!.image!=null){ - if(!acuantImage!!.image.isRecycled) { - acuantImage!!.image.recycle() - } - } - } - } - -} \ No newline at end of file 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 1f2a454..66cf3d2 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantBaseCameraFragment.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantBaseCameraFragment.kt @@ -1,1014 +1,398 @@ package com.acuant.acuantcamera.camera import android.Manifest -import android.content.Context +import android.annotation.SuppressLint +import android.app.AlertDialog import android.content.pm.PackageManager -import android.content.res.Configuration -import android.graphics.* -import android.graphics.drawable.Drawable -import android.hardware.camera2.* -import android.media.Image -import android.media.ImageReader -import android.os.AsyncTask import android.os.Bundle -import android.os.Handler -import android.os.HandlerThread -import android.support.v4.app.Fragment -import android.support.v4.content.ContextCompat import android.util.Log -import android.util.Size -import android.util.SparseIntArray import android.view.* -import android.widget.ImageView -import android.widget.TextView +import androidx.activity.result.contract.ActivityResultContracts +import androidx.camera.core.* +import androidx.camera.core.impl.utils.Exif +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.core.content.ContextCompat +import androidx.exifinterface.media.ExifInterface +import androidx.fragment.app.Fragment +import androidx.window.WindowManager import com.acuant.acuantcamera.R -import com.acuant.acuantcamera.constant.ACUANT_EXTRA_BORDER_ENABLED -import com.acuant.acuantcamera.constant.ACUANT_EXTRA_IS_AUTO_CAPTURE -import com.acuant.acuantcamera.constant.REQUEST_CAMERA_PERMISSION -import com.acuant.acuantcamera.detector.AcuantDetectorWorker -import com.acuant.acuantcamera.detector.IAcuantDetector -import com.acuant.acuantcamera.helper.* -import com.acuant.acuantcamera.helper.CompareSizesByArea -import com.acuant.acuantcamera.detector.ImageSaveHandler -import com.acuant.acuantcamera.detector.ImageSaver +import com.acuant.acuantcamera.interfaces.IAcuantSavedImage +import com.acuant.acuantcamera.interfaces.ICameraActivityFinish +import com.acuant.acuantcamera.databinding.FragmentCameraBinding import com.acuant.acuantcamera.overlay.BaseRectangleView -import com.acuant.acuantcamera.overlay.AcuantOrientationListener -import java.io.File -import java.lang.Exception -import java.lang.ref.WeakReference -import java.util.* -import java.util.concurrent.Semaphore -import java.util.concurrent.TimeUnit -import kotlin.collections.ArrayList -import kotlin.math.abs -import kotlin.math.max -import kotlin.math.min - -abstract class AcuantBaseCameraFragment : Fragment() { - - enum class CameraState {Align, MoveCloser, Hold, Steady, Capturing, MrzNone, MrzAlign, MrzMoveCloser, MrzReposition, MrzTrying, MrzCapturing, NotInFrame} - - private var captureImageReader: ImageReader? = null - private var image: Image? = null - internal var options: AcuantCameraOptions? = null - internal var isAutoCapture = true - internal var isBorderEnabled = true - protected var capturingTextDrawable: Drawable? = null - protected var defaultTextDrawable: Drawable? = null - protected lateinit var rectangleView: BaseRectangleView - protected lateinit var textView: TextView - protected lateinit var imageView: ImageView - protected lateinit var detectors: List - protected var barcodeOnly = false - protected var pointXOffset = 0 - protected var pointYOffset = 0 - private lateinit var orientationListener: AcuantOrientationListener - protected var oldPoints : Array? = null - private lateinit var displaySize: Point - internal var barCodeString: String? = null - internal var isCapturing = false - /** - * This is the output file for our picture. - */ - internal lateinit var file: File - /** - * The current state of camera state for taking pictures. - * - * @see .captureCallback - */ - private var state = STATE_PREVIEW - - /** - * Approximate time that the camera should spend at each digit. - * - * Must be at least 0. If it is set to less than 200 digits might be skipped on slow phones. - */ - internal var timeInMsPerDigit: Int = 800 - /** - * The number of digits for the countdown to show until it starts a capture. - * - * Must be at least 0. If it is set to less than 2 accidental captures might occur. - */ - internal var digitsToShow: Int = 2 - /** - * An additional thread for running tasks that shouldn't block the UI. - */ - private var backgroundThread: HandlerThread? = null - /** - * A [Handler] for running tasks in the background. - */ - internal var backgroundHandler: Handler? = null - /** - * An [ImageReader] that handles still image capture. - */ - private var imageReader: ImageReader? = null - /** - * ID of the current [CameraDevice]. - */ - private lateinit var cameraId: String - /** - * An [AutoFitTextureView] for camera preview. - */ - protected lateinit var textureView: AutoFitTextureView - /** - * A [CameraCaptureSession] for camera preview. - */ - internal var captureSession: CameraCaptureSession? = null - /** - * A reference to the opened [CameraDevice]. - */ - internal var cameraDevice: CameraDevice? = null - /** - * The [android.util.Size] of camera preview. - */ - protected lateinit var previewSize: Size - /** - * [CaptureRequest.Builder] for the camera preview - */ - internal lateinit var previewRequestBuilder: CaptureRequest.Builder - /** - * [CaptureRequest] generated by [.previewRequestBuilder] - */ - internal lateinit var previewRequest: CaptureRequest - /** - * A [Semaphore] to prevent the app from exiting before closing the camera. - */ - internal val cameraOpenCloseLock = Semaphore(1) - /** - * Whether the current camera device supports Flash or not. - */ - private var flashSupported = false - /** - * Orientation of the camera sensor - */ - private var sensorOrientation = 0 - - protected abstract fun setTextFromState(state: CameraState) - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - isAutoCapture = arguments?.getBoolean(ACUANT_EXTRA_IS_AUTO_CAPTURE) ?: true - isBorderEnabled = arguments?.getBoolean(ACUANT_EXTRA_BORDER_ENABLED) ?: true - } - - private fun setOptions(options : AcuantCameraOptions?) { - if(options != null) { - this.timeInMsPerDigit = options.timeInMsPerDigit - this.digitsToShow = options.digitsToShow - isAutoCapture = options.autoCapture - rectangleView.allowBox = options.allowBox - rectangleView.bracketLengthInHorizontal = options.bracketLengthInHorizontal - rectangleView.bracketLengthInVertical = options.bracketLengthInVertical - rectangleView.defaultBracketMarginHeight = options.defaultBracketMarginHeight - rectangleView.defaultBracketMarginWidth = options.defaultBracketMarginWidth - rectangleView.paintColorCapturing = options.colorCapturing - rectangleView.paintColorHold = options.colorHold - rectangleView.paintColorBracketAlign = options.colorBracketAlign - rectangleView.paintColorBracketCapturing = options.colorBracketCapturing - rectangleView.paintColorBracketCloser = options.colorBracketCloser - rectangleView.paintColorBracketHold = options.colorBracketHold - @Suppress("DEPRECATION") - rectangleView.cardRatio = options.cardRatio +import com.acuant.acuantcommon.model.AcuantError +import com.acuant.acuantcommon.model.ErrorCodes +import com.acuant.acuantcommon.model.ErrorDescriptions +import com.acuant.acuantimagepreparation.AcuantImagePreparation +import org.json.JSONObject +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import java.io.* + + +abstract class AcuantBaseCameraFragment: Fragment() { + + private var displayId: Int = -1 + private var lensFacing: Int = CameraSelector.LENS_FACING_BACK + private var imageCapture: ImageCapture? = null + private var camera: Camera? = null + private var cameraProvider: ProcessCameraProvider? = null + private var orientationEventListener: OrientationEventListener? = null + private var preview: Preview? = null + private lateinit var windowManager: WindowManager + protected var capturing: Boolean = false + protected var fragmentCameraBinding: FragmentCameraBinding? = null + protected var imageAnalyzer: ImageAnalysis? = null //set up by implementations + protected lateinit var cameraExecutor: ExecutorService + protected lateinit var cameraActivityListener: ICameraActivityFinish + protected lateinit var acuantOptions: AcuantCameraOptions + + private val permissionRequest = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { granted -> + if (granted) { + setUpCamera() } else { - rectangleView.allowBox = isBorderEnabled - } - } - - - internal fun isDocumentInFrame(points: Array?) : Boolean{ - if (points != null) { - val minOffset = 0.025f - val startY = displaySize.x * minOffset//textureView.width - val startX = displaySize.y * minOffset //textureView.height.toFloat() / 2 - previewSize.width.toFloat() / 2 - val endY = displaySize.x * (1 - minOffset)//textureView.width - val endX = displaySize.y * (1 - minOffset)//textureView.height - -// Log.d("WTF", "start: $startX,$startY\tend: $endX,$endY") -// if (previewSize.width.toFloat()/displaySize.y < previewSize.height.toFloat()/displaySize.x) { -// endX = (previewSize.width * displaySize.x/previewSize.height.toFloat()).toInt() -// } else { -// endY = (previewSize.height * displaySize.y/previewSize.width.toFloat()).toInt() -// } -// Log.d("WTF", "start: $startX,$startY\tend: $endX,$endY") - - for (point in points) { - if (point.x < startX || point.y < startY || point.x > endX || point.y > endY) { - return false - } + val alertDialog = AlertDialog.Builder(requireContext()).create() + alertDialog.setMessage(getString(R.string.no_camera_permission)) + alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.ok)) { _, _ -> + cameraActivityListener.onError(AcuantError(ErrorCodes.ERROR_Permissions, ErrorDescriptions.ERROR_DESC_Permissions)) + alertDialog.dismiss() } + alertDialog.setOnCancelListener { + cameraActivityListener.onError(AcuantError(ErrorCodes.ERROR_Permissions, ErrorDescriptions.ERROR_DESC_Permissions)) + alertDialog.dismiss() + } + alertDialog.show() } - - return true - } - - override fun onDestroy() { - super.onDestroy() - detectors.forEach{ - it.clean() - } - image?.close() - } - - override fun onCreateView(inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? = inflater.inflate(R.layout.fragment_camera2_basic, container, false) - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - textView = view.findViewById(R.id.acu_display_text) - imageView = view.findViewById(R.id.acu_help_image) - orientationListener = if (!barcodeOnly) { - AcuantOrientationListener(activity!!.applicationContext, WeakReference(textView), WeakReference(imageView)) - } else { - AcuantOrientationListener(activity!!.applicationContext, WeakReference(textView)) - } - - setOptions(options) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - file = File(activity!!.cacheDir, "${UUID.randomUUID()}.jpg") } - override fun onResume() { - super.onResume() - startBackgroundThread() + override fun onDestroyView() { + fragmentCameraBinding = null + super.onDestroyView() - activity?.window?.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN) - orientationListener.enable() - - // When the screen is turned off and turned back on, the SurfaceTexture is already - // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open - // a camera and start preview from here (otherwise, we wait until the surface is ready in - // the SurfaceTextureListener). - if (textureView.isAvailable) { - openCamera(textureView.width, textureView.height) - } else { - textureView.surfaceTextureListener = surfaceTextureListener - } + // Shut down our background executor + cameraExecutor.shutdown() } - override fun onPause() { - rectangleView.end() - closeCamera() - stopBackgroundThread() - orientationListener.disable() - super.onPause() + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + fragmentCameraBinding = FragmentCameraBinding.inflate(inflater, container, false) + return fragmentCameraBinding!!.root } - private fun requestCameraPermission() { - if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) { - ConfirmationDialog().show(childFragmentManager, FRAGMENT_DIALOG) - } else { - requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION) - } + override fun onStart() { + super.onStart() + orientationEventListener?.enable() } - override fun onRequestPermissionsResult(requestCode: Int, - permissions: Array, - grantResults: IntArray) { - if (requestCode == REQUEST_CAMERA_PERMISSION) { - if (grantResults.size != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) { - ErrorDialog.newInstance(getString(R.string.request_permission)) - .show(fragmentManager!!, FRAGMENT_DIALOG) - } - } else { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - } + override fun onStop() { + super.onStop() + orientationEventListener?.disable() } - abstract fun setTapToCapture() - - /** - * [TextureView.SurfaceTextureListener] handles several lifecycle events on a - * [TextureView]. - */ - private val surfaceTextureListener = object : TextureView.SurfaceTextureListener { - - override fun onSurfaceTextureAvailable(texture: SurfaceTexture, width: Int, height: Int) { - openCamera(width, height) - } - - override fun onSurfaceTextureSizeChanged(texture: SurfaceTexture, width: Int, height: Int) { - configureTransform(width, height) - textureView.requestLayout() - } - - override fun onSurfaceTextureDestroyed(texture: SurfaceTexture) = true - - override fun onSurfaceTextureUpdated(texture: SurfaceTexture) = Unit - } + @SuppressLint("MissingPermission") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) - /** - * [CameraDevice.StateCallback] is called when [CameraDevice] changes its state. - */ - private val stateCallback = object : CameraDevice.StateCallback() { + cameraActivityListener = requireActivity() as ICameraActivityFinish - override fun onOpened(cameraDevice: CameraDevice) { - cameraOpenCloseLock.release() - this@AcuantBaseCameraFragment.cameraDevice = cameraDevice - createCameraPreviewSession() - } + val opts = requireArguments().getSerializable(INTERNAL_OPTIONS) as AcuantCameraOptions? - override fun onDisconnected(cameraDevice: CameraDevice) { - cameraOpenCloseLock.release() - cameraDevice.close() - this@AcuantBaseCameraFragment.cameraDevice = null + if (opts != null) { + acuantOptions = opts + } else { + cameraActivityListener.onError(AcuantError(ErrorCodes.ERROR_UnexpectedError, ErrorDescriptions.ERROR_DESC_UnexpectedError, "Options were unexpectedly null")) + return } - override fun onError(cameraDevice: CameraDevice, error: Int) { - onDisconnected(cameraDevice) - this@AcuantBaseCameraFragment.activity?.finish() - } - } + orientationEventListener = object : OrientationEventListener(requireContext()) { + override fun onOrientationChanged(orientation: Int) { + if (orientation == ORIENTATION_UNKNOWN) { + return + } - /** - * This a callback object for the [ImageReader]. "onImageAvailable" will be called when a - * still image is ready to be saved. - */ - private val onFrameImageAvailableListener = ImageReader.OnImageAvailableListener { - try { - val image:Image? = it.acquireLatestImage() - if (image != null) { - if (!isCapturing) { - try { - AcuantDetectorWorker(detectors, imageToBitmap(image), isAutoCapture).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + when (orientation) { + in 45 until 135 -> { + val rotation = Surface.ROTATION_270 + imageAnalyzer?.targetRotation = rotation + imageCapture?.targetRotation = rotation + rotateUi(270) } - catch(e:Exception){ - image.close() - e.printStackTrace() + in 225 until 315 -> { + val rotation = Surface.ROTATION_90 + imageAnalyzer?.targetRotation = rotation + imageCapture?.targetRotation = rotation + rotateUi(90) } } - else { - image.close() - } } - } catch (e: Exception) { - e.printStackTrace() } - } - private fun imageToBitmap(image: Image) : Bitmap { - val imageBytes = ImageSaver.imageToByteArray(image) - val bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) - image.close() - return bitmap - } + // Initialize our background executor + cameraExecutor = Executors.newSingleThreadExecutor() - /** - * Called only once when the camera deems the id to be properly positioned and - * is ready to take a photo - */ - private val onCaptureImageAvailableListener = ImageReader.OnImageAvailableListener { - try { - val image:Image? = it.acquireLatestImage() - if (image != null) { - if (isCapturing) { - this.isCapturing = false + //Initialize WindowManager to retrieve display metrics + windowManager = WindowManager(view.context) - val capturetype = if(isAutoCapture) "AUTO" else "TAP" + // Wait for the views to be properly laid out + fragmentCameraBinding?.viewFinder?.post { + val binding = fragmentCameraBinding - backgroundHandler?.post(ImageSaver(orientationListener.previousAngle, image, file, capturetype, object : ImageSaveHandler { - override fun onSave() { - if (activity is ICameraActivityFinish) { - (activity as ICameraActivityFinish).onActivityFinish(file.absolutePath, barCodeString) - } - } - })) - } else { - image.close() - } + if (binding != null) { + // Keep track of the display in which this view is attached + displayId = binding.viewFinder.display.displayId + + requestCameraPermissionIfNeeded() } - } catch (e: Exception) { - e.printStackTrace() } } - /** - * A [CameraCaptureSession.CaptureCallback] that handles events related to JPEG capture. - */ - private val captureCallback = object : CameraCaptureSession.CaptureCallback() { + abstract fun rotateUi(rotation: Int) - private fun process(result: CaptureResult) { - when (state) { - STATE_PREVIEW -> { - - } - STATE_WAITING_LOCK -> capturePicture(result) - STATE_WAITING_PRECAPTURE -> { - // CONTROL_AE_STATE can be null on some devices - val aeState = result.get(CaptureResult.CONTROL_AE_STATE) - if (aeState == null || - aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || - aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED || - aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) { - state = STATE_WAITING_NON_PRECAPTURE - } - } - STATE_WAITING_NON_PRECAPTURE -> { - // CONTROL_AE_STATE can be null on some devices - val aeState = result.get(CaptureResult.CONTROL_AE_STATE) - if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { - state = STATE_PICTURE_TAKEN - captureStillPicture() - } + private fun requestCameraPermissionIfNeeded() { + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) { + val alertDialog = AlertDialog.Builder(requireContext()).create() + alertDialog.setMessage(getString(R.string.cam_perm_request_text)) + alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.ok)) { _, _ -> + permissionRequest.launch(Manifest.permission.CAMERA) } - } - } - - private var focusStateCounter = 0 - private fun capturePicture(result: CaptureResult) { - val afState = result.get(CaptureResult.CONTROL_AF_STATE) - if (afState == null) { - if(focusStateCounter < 3){ - focusStateCounter++ + alertDialog.setOnCancelListener { + permissionRequest.launch(Manifest.permission.CAMERA) } - else{ - state = STATE_PICTURE_TAKEN - captureStillPicture() - } - } else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED - || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { - // CONTROL_AE_STATE can be null on some devices - runPrecaptureSequence() + alertDialog.show() + } else { + permissionRequest.launch(Manifest.permission.CAMERA) } - } - - override fun onCaptureProgressed(session: CameraCaptureSession, - request: CaptureRequest, - partialResult: CaptureResult) { - process(partialResult) - } - - override fun onCaptureCompleted(session: CameraCaptureSession, - request: CaptureRequest, - result: TotalCaptureResult) { - process(result) + } else { + setUpCamera() } } - /** - * Sets up member variables related to camera. - * - * @param width The width of available size for camera preview - * @param height The height of available size for camera preview - */ - private fun setUpCameraOutputs(width: Int, height: Int) { - val manager = activity!!.getSystemService(Context.CAMERA_SERVICE) as CameraManager - try { - setTapToCapture() - for (cameraId in manager.cameraIdList) { - val characteristics = manager.getCameraCharacteristics(cameraId) - - // We don't use a front facing camera in this sample. - val cameraDirection = characteristics.get(CameraCharacteristics.LENS_FACING) - if (cameraDirection != null && - cameraDirection == CameraCharacteristics.LENS_FACING_FRONT) { - continue - } + private fun setUpCamera() { + val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext()) + cameraProviderFuture.addListener({ - val map = characteristics.get( - CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: continue + // CameraProvider + cameraProvider = cameraProviderFuture.get() + // Select lensFacing depending on the available cameras + lensFacing = when { + hasBackCamera() -> CameraSelector.LENS_FACING_BACK + else -> { + cameraActivityListener.onError(AcuantError(ErrorCodes.ERROR_UnexpectedError, ErrorDescriptions.ERROR_DESC_UnexpectedError, "Phone does not have a camera/app can not see the camera")) + return@addListener + } + } + // Build and bind the camera use cases + bindCameraUseCases() + }, ContextCompat.getMainExecutor(requireContext())) + } - // Find out if we need to swap dimension to get the preview size relative to sensor - // coordinate. - val displayRotation = activity!!.windowManager.defaultDisplay.rotation - - sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!! - val swappedDimensions = areDimensionsSwapped(displayRotation) + private fun hasBackCamera(): Boolean { + return cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false + } - displaySize = Point() - activity!!.windowManager.defaultDisplay.getSize(displaySize) - val rotatedPreviewWidth = if (swappedDimensions) height else width - val rotatedPreviewHeight = if (swappedDimensions) width else height - val maxPreviewWidth = if (swappedDimensions) displaySize.y else displaySize.x - val maxPreviewHeight = if (swappedDimensions) displaySize.x else displaySize.y + /** Declare and bind preview, capture and analysis use cases */ + private fun bindCameraUseCases() { - val bestJpeg = Collections.max( - listOf(*map.getOutputSizes(ImageFormat.JPEG)), - CompareSizesByArea()) + val screenAspectRatio = aspectRatio() -// val bestJpeg = chooseBestCaptureSize(listOf(*map.getOutputSizes(ImageFormat.JPEG)), maxPreviewWidth, maxPreviewHeight) + var rotation = fragmentCameraBinding!!.viewFinder.display.rotation - // Danger, W.R.! Attempting to use too large a preview size could exceed the camera - // bus' bandwidth limitation, resulting in gorgeous previews but the storage of - // garbage capture data. - previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture::class.java), - rotatedPreviewWidth, rotatedPreviewHeight, - maxPreviewWidth, maxPreviewHeight, bestJpeg) + // CameraProvider + val cameraProvider = cameraProvider - imageReader = ImageReader.newInstance(previewSize.width , previewSize.height, - ImageFormat.YUV_420_888, /*maxImages*/ 3).apply { - setOnImageAvailableListener(onFrameImageAvailableListener, backgroundHandler) - } + if (cameraProvider == null) { + cameraActivityListener.onError(AcuantError(ErrorCodes.ERROR_UnexpectedError, ErrorDescriptions.ERROR_DESC_UnexpectedError, "Camera initialization failed.")) + return + } - captureImageReader = ImageReader.newInstance(bestJpeg.width, bestJpeg.height, - ImageFormat.JPEG, /*maxImages*/ 1).apply { - setOnImageAvailableListener(onCaptureImageAvailableListener, backgroundHandler) - } + // CameraSelector + val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build() - val scaledWidth: Int - val scaledHeight: Int + // Preview + preview = Preview.Builder() + // We request aspect ratio but no resolution + .setTargetAspectRatio(screenAspectRatio) + // Set initial target rotation + .setTargetRotation(rotation) + .build() - if (rotatedPreviewWidth/previewSize.width.toFloat() < rotatedPreviewHeight/previewSize.height.toFloat()) { - scaledWidth = rotatedPreviewWidth - scaledHeight = (previewSize.height * rotatedPreviewWidth/previewSize.width.toFloat()).toInt() - } else { - scaledWidth = (previewSize.width * rotatedPreviewHeight/previewSize.height.toFloat()).toInt() - scaledHeight = rotatedPreviewHeight - } + rotation = Surface.ROTATION_90 - val tmpWidth = (rotatedPreviewWidth - scaledWidth)/2 - pointXOffset = if (tmpWidth > 0) tmpWidth else pointXOffset - val tmpHeight = (rotatedPreviewHeight - scaledHeight)/2 - pointYOffset = if (tmpHeight > 0) tmpHeight else pointYOffset - - // We fit the aspect ratio of TextureView to the size of preview we picked. - if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { - //textureView.setMax(maxPreviewWidth, maxPreviewHeight) - textureView.setAspectRatio(previewSize.width, previewSize.height) - } else { - //textureView.setMax(maxPreviewHeight, maxPreviewWidth) - textureView.setAspectRatio(previewSize.height, previewSize.width) - } + // ImageCapture + imageCapture = ImageCapture.Builder() + .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) + // We request aspect ratio but no resolution to match preview config, but letting + // CameraX optimize for whatever specific resolution best fits our use cases + .setTargetAspectRatio(screenAspectRatio) + // Set initial target rotation, we will have to call this again if rotation changes + // during the lifecycle of this use case + .setTargetRotation(rotation) + .build() - // Check if the flash is supported. - flashSupported = - characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) == true + buildImageAnalyzer(screenAspectRatio, rotation) - this.cameraId = cameraId + // Must unbind the use-cases before rebinding them + cameraProvider.unbindAll() - // We've found a viable camera and finished setting up member variables, - // so we don't need to iterate through other available cameras. - return + try { + // A variable number of use-cases can be passed here - + // camera provides access to CameraControl & CameraInfo + camera = if (imageAnalyzer == null) { + cameraProvider.bindToLifecycle( + this, cameraSelector, preview, imageCapture) + } else { + cameraProvider.bindToLifecycle( + this, cameraSelector, preview, imageCapture, imageAnalyzer) } - } catch (e: CameraAccessException) { - Log.e(TAG, e.toString()) - } catch (e: NullPointerException) { - // Currently an NPE is thrown when the Camera2API is used but not supported on the - // device this code runs. - ErrorDialog.newInstance(getString(R.string.acuant_camera_error)) - .show(childFragmentManager, FRAGMENT_DIALOG) - } - } - /** - * Determines if the dimensions are swapped given the phone's current rotation. - * - * @param displayRotation The current rotation of the display - * - * @return true if the dimensions are swapped, false otherwise. - */ - private fun areDimensionsSwapped(displayRotation: Int): Boolean { - var swappedDimensions = false - when (displayRotation) { - Surface.ROTATION_0, Surface.ROTATION_180 -> { - if (sensorOrientation == 90 || sensorOrientation == 270) { - swappedDimensions = true - } - } - Surface.ROTATION_90, Surface.ROTATION_270 -> { - if (sensorOrientation == 0 || sensorOrientation == 180) { - swappedDimensions = true - } - } - else -> { - Log.e(TAG, "Display rotation is invalid: $displayRotation") - } + // Attach the viewfinder's surface provider to preview use case + preview?.setSurfaceProvider(fragmentCameraBinding!!.viewFinder.surfaceProvider) + observeCameraState(camera?.cameraInfo!!) + //todo camera.cameracontrol startFocusAndMetering to keep focus on the middle of the image and avoid focusing on reflections/background? + } catch (exc: Exception) { + Log.e(TAG, "Use case binding failed", exc) } - return swappedDimensions } - private fun isPermissionGranted(): Boolean{ - val permission = activity?.let { ContextCompat.checkSelfPermission(it, Manifest.permission.CAMERA) } - return permission == PackageManager.PERMISSION_GRANTED - } + abstract fun buildImageAnalyzer(screenAspectRatio: Int, rotation: Int) - /** - * Opens the camera. - */ - private fun openCamera(width: Int, height: Int) { - val permission = activity?.let { ContextCompat.checkSelfPermission(it, Manifest.permission.CAMERA) } - if (permission != PackageManager.PERMISSION_GRANTED) { - requestCameraPermission() - return - } - setUpCameraOutputs(width, height) - configureTransform(width, height) - val manager = activity!!.getSystemService(Context.CAMERA_SERVICE) as CameraManager - try { - // Wait for camera to open - 2.5 seconds is sufficient - if (!cameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { - throw RuntimeException("Time out waiting to lock camera opening.") - } - manager.openCamera(cameraId, stateCallback, backgroundHandler) - textureView.requestLayout() - } catch (e: CameraAccessException) { - Log.e(TAG, e.toString()) - } catch (e: InterruptedException) { - throw RuntimeException("Interrupted while trying to lock camera opening.", e) - } - } + fun captureImage (listener: IAcuantSavedImage, captureType: String? = null) { + if (!capturing) { + imageCapture?.let { imageCapture -> + capturing = true - /** - * Closes the current [CameraDevice]. - */ - private fun closeCamera() { - try { - cameraOpenCloseLock.acquire() - captureSession?.close() - captureSession = null - cameraDevice?.close() - cameraDevice = null - imageReader?.close() - imageReader = null - captureImageReader?.close() - captureImageReader = null - } catch (e: InterruptedException) { - throw RuntimeException("Interrupted while trying to lock camera closing.", e) - } finally { - cameraOpenCloseLock.release() - } - } + // Create output file to hold the image (will automatically add numbers to create a uuid) + val photoFile = + File.createTempFile("AcuantCameraImage", ".jpg", requireActivity().cacheDir) - /** - * Starts a background thread and its [Handler]. - */ - private fun startBackgroundThread() { - backgroundThread = HandlerThread("CameraBackground").also { it.start() } - backgroundHandler = Handler(backgroundThread?.looper ?: throw IllegalStateException("Background thread was null in a place where it can not/should not be null.")) - } + // Create output options object which contains file + metadata + val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() - /** - * Stops the background thread and its [Handler]. - */ - private fun stopBackgroundThread() { - backgroundThread?.quitSafely() - try { - backgroundThread?.join() - backgroundThread = null - backgroundHandler = null - } catch (e: InterruptedException) { - Log.e(TAG, e.toString()) - } - } + // Setup image capture listener which is triggered after photo has been taken + imageCapture.takePicture( + outputOptions, + cameraExecutor, + object : ImageCapture.OnImageSavedCallback { + @SuppressLint("RestrictedApi") + override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { - /** - * Creates a new [CameraCaptureSession] for camera preview. - */ - private fun createCameraPreviewSession() { - try { - val texture = textureView.surfaceTexture - - // We configure the size of default buffer to be the size of camera preview we want. - texture?.setDefaultBufferSize(previewSize.width, previewSize.height) - // This is the output Surface we need to start preview. - val surface = Surface(texture) - - // We set up a CaptureRequest.Builder with the output Surface. - previewRequestBuilder = cameraDevice!!.createCaptureRequest( - CameraDevice.TEMPLATE_PREVIEW - ) - previewRequestBuilder.addTarget(surface) - previewRequestBuilder.addTarget(imageReader!!.surface) - - // Here, we create a CameraCaptureSession for camera preview. - cameraDevice?.createCaptureSession(listOf(surface, imageReader?.surface, captureImageReader?.surface), - object : CameraCaptureSession.StateCallback() { - - override fun onConfigured(cameraCaptureSession: CameraCaptureSession) { - // The camera is already closed - if (cameraDevice == null) return - - // When the session is ready, we start displaying the preview. - captureSession = cameraCaptureSession - try { - // Auto focus should be continuous for camera preview. - previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, - CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) - - previewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO) - previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON) - previewRequestBuilder.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO) - previewRequestBuilder.set(CaptureRequest.JPEG_QUALITY, 100) - - // Finally, we start displaying the camera preview. - previewRequest = previewRequestBuilder.build() - captureSession?.setRepeatingRequest(previewRequest, - captureCallback, backgroundHandler) - } catch (e: CameraAccessException) { - Log.e(TAG, e.toString()) - } - catch(e:Exception){ - e.printStackTrace() + val savedUri = outputFileResults.savedUri?.path ?: photoFile.absolutePath + + val exif = Exif.createFromFileString(savedUri) + val rotation = exif.rotation + + if (captureType != null) { + addExif(File(savedUri), captureType, rotation) + } else { + addExif(File(savedUri), "NOT SPECIFIED (implementer used deprecated constructor that lacks this data)", rotation) } + listener.onSaved(savedUri) } - override fun onConfigureFailed(session: CameraCaptureSession) { - //configuration failed + override fun onError(exception: ImageCaptureException) { + listener.onError( + AcuantError( + ErrorCodes.ERROR_SavingImage, + ErrorDescriptions.ERROR_DESC_SavingImage, + exception.toString() + ) + ) } - }, null) - - - } catch (e: CameraAccessException) { - Log.e(TAG, e.toString()) - } - catch(e:Exception){ - e.printStackTrace() - } - } - - /** - * Configures the necessary [android.graphics.Matrix] transformation to `textureView`. - * This method should be called after the camera preview size is determined in - * setUpCameraOutputs and also the size of `textureView` is fixed. - * - * @param viewWidth The width of `textureView` - * @param viewHeight The height of `textureView` - */ - private fun configureTransform(viewWidth: Int, viewHeight: Int) { - activity ?: return - if(!isPermissionGranted()) return - - val rotation = activity!!.windowManager.defaultDisplay.rotation - val matrix = Matrix() - val viewRect = RectF(0f, 0f, viewWidth.toFloat(), viewHeight.toFloat()) - val bufferRect = RectF(0f, 0f, previewSize.height.toFloat(), previewSize.width.toFloat()) - val centerX = viewRect.centerX() - val centerY = viewRect.centerY() - - if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { - bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()) - val scale = max( - viewHeight.toFloat() / previewSize.height, - viewWidth.toFloat() / previewSize.width) - with(matrix) { - setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL) - postScale(scale, scale, centerX, centerY) - postRotate((90 * (rotation - 2)).toFloat(), centerX, centerY) + }) } - } else if (Surface.ROTATION_180 == rotation) { - matrix.postRotate(180f, centerX, centerY) } - textureView.setTransform(matrix) } - /** - * Lock the focus as the first step for a still image capture. - */ - internal fun lockFocus() { - try { - // This is how to tell the camera to lock focus. - previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, - CameraMetadata.CONTROL_AF_TRIGGER_START) - - // Tell #captureCallback to wait for the lock. - state = STATE_WAITING_LOCK - captureSession?.capture(previewRequestBuilder.build(), captureCallback, - backgroundHandler) - } catch (e: CameraAccessException) { - Log.e(TAG, e.toString()) - } - catch(e:Exception){ - e.printStackTrace() - } - } - - /** - * Run the precapture sequence for capturing a still image. This method should be called when - * we get a response in [.captureCallback] from [.lockFocus]. - */ - private fun runPrecaptureSequence() { - try { - // This is how to tell the camera to trigger. - previewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, - CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START) - // Tell #captureCallback to wait for the precapture sequence to be set. - state = STATE_WAITING_PRECAPTURE - captureSession?.capture(previewRequestBuilder.build(), captureCallback, - backgroundHandler) - } catch (e: CameraAccessException) { - Log.e(TAG, e.toString()) - } - catch(e:Exception){ - e.printStackTrace() - } + protected fun setOptions(rectangleView: BaseRectangleView?) { + if (rectangleView == null) + return + rectangleView.allowBox = acuantOptions.allowBox + rectangleView.bracketLengthInHorizontal = acuantOptions.bracketLengthInHorizontal + rectangleView.bracketLengthInVertical = acuantOptions.bracketLengthInVertical + rectangleView.defaultBracketMarginHeight = acuantOptions.defaultBracketMarginHeight + rectangleView.defaultBracketMarginWidth = acuantOptions.defaultBracketMarginWidth + rectangleView.paintColorCapturing = acuantOptions.colorCapturing + rectangleView.paintColorHold = acuantOptions.colorHold + rectangleView.paintColorBracketAlign = acuantOptions.colorBracketAlign + rectangleView.paintColorBracketCapturing = acuantOptions.colorBracketCapturing + rectangleView.paintColorBracketCloser = acuantOptions.colorBracketCloser + rectangleView.paintColorBracketHold = acuantOptions.colorBracketHold + rectangleView.cardRatio = acuantOptions.cardRatio } - /** - * Capture a still picture. This method should be called when we get a response in - * [.captureCallback] from both [.lockFocus]. - */ - private fun captureStillPicture() { - try { - if (activity == null || cameraDevice == null) return - activity!!.windowManager.defaultDisplay.rotation - - // This is the CaptureRequest.Builder that we use to take a picture. - val captureBuilder = cameraDevice!!.createCaptureRequest( - CameraDevice.TEMPLATE_STILL_CAPTURE).apply { - addTarget(captureImageReader!!.surface) - set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO) - set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON) - set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO) - set(CaptureRequest.JPEG_QUALITY, 100) - -// // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X) -// // We have to take that into account and rotate JPEG properly. -// // For devices with orientation of 90, we return our mapping from ORIENTATIONS. -// // For devices with orientation of 270, we need to rotate the JPEG 180 degrees. -// set(CaptureRequest.JPEG_ORIENTATION, -// (ORIENTATIONS.get(rotation) + sensorOrientation + 270) % 360) - - // Use the same AE and AF modes as the preview. - set(CaptureRequest.CONTROL_AF_MODE, - CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) - } - - val captureCallback = object : CameraCaptureSession.CaptureCallback() { - - override fun onCaptureFailed(session: CameraCaptureSession, - request: CaptureRequest , failure: CaptureFailure){ - unlockFocus() - } - override fun onCaptureCompleted(session: CameraCaptureSession, - request: CaptureRequest, - result: TotalCaptureResult) { - unlockFocus() + private fun observeCameraState(cameraInfo: CameraInfo) { + cameraInfo.cameraState.observe(viewLifecycleOwner) { cameraState -> + cameraState.error?.let { error -> + val text: String? = when (error.code) { + // Open errors + CameraState.ERROR_STREAM_CONFIG -> { + // Make sure to setup the use cases properly + "Stream config error" + } + // Opening errors + CameraState.ERROR_CAMERA_IN_USE -> { + // Close the camera or ask user to close another camera app that's using the + // camera + "Camera in use" + } + CameraState.ERROR_MAX_CAMERAS_IN_USE -> { + // Close another open camera in the app, or ask the user to close another + // camera app that's using the camera + "Max cameras in use" + } + // Closing errors + CameraState.ERROR_CAMERA_DISABLED -> { + // Ask the user to enable the device's cameras + "Camera disabled" + } + CameraState.ERROR_CAMERA_FATAL_ERROR -> { + // Ask the user to reboot the device to restore camera function + "Fatal error" + } + // Closed errors + CameraState.ERROR_DO_NOT_DISTURB_MODE_ENABLED -> { + // Ask the user to disable the "Do Not Disturb" mode, then reopen the camera + "Do not disturb mode enabled" + } + else -> null } + cameraActivityListener.onError(AcuantError(ErrorCodes.ERROR_UnexpectedError, ErrorDescriptions.ERROR_DESC_UnexpectedError, text)) } - captureSession?.apply { - stopRepeating() - capture(captureBuilder.build(), captureCallback, null) - } - } - catch (e: CameraAccessException) { - Log.e(TAG, e.toString()) - } - catch(e:Exception){ - e.printStackTrace() - } - } - - /** - * Unlock the focus. This method should be called when still image capture sequence is - * finished. - */ - internal fun unlockFocus() { - try { - // Reset the auto-focus trigger - previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, - CameraMetadata.CONTROL_AF_TRIGGER_CANCEL) - - captureSession?.capture(previewRequestBuilder.build(), captureCallback, - backgroundHandler) - } - catch (e: CameraAccessException) { - Log.e(TAG, e.toString()) - } - catch(e:Exception){ - e.printStackTrace() } } companion object { + private const val TAG = "Acuant Camera" + internal const val INTERNAL_OPTIONS = "options_internal" - /** - * Conversion from screen rotation to JPEG orientation. - */ - private val ORIENTATIONS = SparseIntArray() - private const val FRAGMENT_DIALOG = "dialog" - - init { - ORIENTATIONS.append(Surface.ROTATION_0, 90) - ORIENTATIONS.append(Surface.ROTATION_90, 0) - - ORIENTATIONS.append(Surface.ROTATION_180, 270) - ORIENTATIONS.append(Surface.ROTATION_270, 180) - } - - /** - * Tag for the [Log]. - */ - private const val TAG = "AcuantBaseCameraFrag" + private fun addExif(file: File, captureType: String, rotation: Int) { + val exif = ExifInterface(file.absolutePath) + val json = JSONObject() - /** - * Camera state: Showing camera preview. - */ - private const val STATE_PREVIEW = 0 - /** - * Camera state: Waiting for the focus to be locked. - */ - private const val STATE_WAITING_LOCK = 1 + json.put(AcuantImagePreparation.CAPTURE_TYPE_TAG, captureType) + json.put(AcuantImagePreparation.ROTATION_TAG, rotation) - /** - * Camera state: Waiting for the exposure to be precapture state. - */ - private const val STATE_WAITING_PRECAPTURE = 2 - - /** - * Camera state: Waiting for the exposure state to be something other than precapture. - */ - private const val STATE_WAITING_NON_PRECAPTURE = 3 - - /** - * Camera state: Picture was taken. - */ - private const val STATE_PICTURE_TAKEN = 4 - - private const val RATIO_TOLERANCE = 0.1f - - @Suppress("unused") - @JvmStatic private fun chooseBestCaptureSize(sizes: List, screenWidth: Int, screenHeight: Int) : Size { - val targetSmallSide = min(screenHeight, screenWidth) - val targetLargeSide = max(screenHeight, screenWidth) - - val sortedSizes = sizes.sortedWith(CompareSizesByArea()) - - val minSize = sortedSizes[0].width * sortedSizes[0].height * 0.75f - - for (option in sortedSizes) { - - val currentSmallSide = min(option.height, option.width) - val currentLargeSide = max(option.height, option.width) - - if (abs(currentLargeSide.toFloat()/currentSmallSide - targetLargeSide.toFloat()/targetSmallSide) < RATIO_TOLERANCE && - currentLargeSide * currentSmallSide > minSize) { - return option - } + when (rotation) { + 270 -> exif.setAttribute(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_ROTATE_270.toString()) + else -> exif.setAttribute(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_ROTATE_90.toString()) } - return sortedSizes[0] + exif.setAttribute(ExifInterface.TAG_IMAGE_DESCRIPTION, json.toString()) + exif.saveAttributes() } - /** - * Given `choices` of `Size`s supported by a camera, choose the smallest one that - * is at least as large as the respective texture view size, and that is at most as large as - * the respective max size, and whose aspect ratio matches with the specified value. If such - * size doesn't exist, choose the largest one that is at most as large as the respective max - * size, and whose aspect ratio matches with the specified value. - * - * @param choices The list of sizes that the camera supports for the intended - * output class - * @param textureViewWidth The width of the texture view relative to sensor coordinate - * @param textureViewHeight The height of the texture view relative to sensor coordinate - * @param maxWidth The maximum width that can be chosen - * @param maxHeight The maximum height that can be chosen - * @return The optimal `Size`, or an arbitrary one if none were big enough - */ - @JvmStatic private fun chooseOptimalSize( - choices: Array, - textureViewWidth: Int, - textureViewHeight: Int, - maxWidth: Int, - maxHeight: Int, - captureSize: Size - ): Size { - val targetRatio = captureSize.width.toFloat()/captureSize.height - // Collect the supported resolutions that are at least as big as the preview Surface - val bigEnoughGood = ArrayList() - val bigEnoughBad = ArrayList() - // Collect the supported resolutions that are smaller than the preview Surface - val notBigEnoughGood = ArrayList() - val notBigEnoughBad = ArrayList() - for (option in choices) { - if (option.width <= maxWidth && option.height <= maxHeight ) { - if (option.width >= textureViewWidth && option.height >= textureViewHeight) { - if (abs(option.width.toFloat()/option.height - targetRatio) < RATIO_TOLERANCE) { - bigEnoughGood.add(option) - } else { - bigEnoughBad.add(option) - } - } else { - if (abs(option.width.toFloat()/option.height - targetRatio) < RATIO_TOLERANCE) { - notBigEnoughGood.add(option) - } else { - notBigEnoughBad.add(option) - } - } - } - } - - // Pick the smallest of those big enough. If there is no one big enough, pick the - // largest of those not big enough. - return when { - bigEnoughGood.size > 0 -> Collections.min(bigEnoughGood, CompareSizesByArea()) - notBigEnoughGood.size > 0 -> Collections.max(notBigEnoughGood, CompareSizesByArea()) - bigEnoughBad.size > 0 -> Collections.min(bigEnoughBad, CompareSizesByArea()) - notBigEnoughBad.size > 0 -> Collections.max(notBigEnoughBad, CompareSizesByArea()) - else -> { - Log.e(TAG, "Couldn't find any suitable preview size") - choices[0] - } - } + //we currently want all doc cameras to be in 4:3 to use as much of the available camera space as possible + private fun aspectRatio(): Int { + return AspectRatio.RATIO_4_3 } } -} - - +} \ No newline at end of file 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 104cf4b..c4622cc 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantCameraActivity.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantCameraActivity.kt @@ -1,252 +1,113 @@ package com.acuant.acuantcamera.camera -import android.content.Context import android.content.Intent -import android.hardware.camera2.CameraCharacteristics -import android.hardware.camera2.CameraManager -import android.os.Build import android.os.Bundle -import android.support.v4.app.Fragment -import android.support.v7.app.AppCompatActivity -import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment import com.acuant.acuantcamera.R import com.acuant.acuantcamera.camera.barcode.AcuantBarcodeCameraFragment -import com.acuant.acuantcamera.camera.document.cameraone.DocumentCaptureActivity import com.acuant.acuantcamera.camera.document.AcuantDocCameraFragment import com.acuant.acuantcamera.camera.mrz.AcuantMrzCameraFragment -import com.acuant.acuantcamera.camera.AcuantCameraOptions.CameraMode +import com.acuant.acuantcamera.interfaces.ICameraActivityFinish import com.acuant.acuantcamera.constant.* +import com.acuant.acuantcamera.databinding.ActivityCameraBinding import com.acuant.acuantcamera.helper.MrzResult +import com.acuant.acuantcommon.model.AcuantError import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability +class AcuantCameraActivity: AppCompatActivity(), ICameraActivityFinish { -interface ICameraActivityFinish{ - fun onActivityFinish(imageUrl: String, barCodeString: String?) - fun onActivityFinish(mrzResult: MrzResult) - fun onActivityFinish(barCodeString: String?) -} - -class AcuantCameraActivity : AppCompatActivity(), ICameraActivityFinish { - - companion object { - const val RESULT_SUCCESS_CODE = 1 - const val BARCODE_REQUEST = 3 - const val MRZ_REQUEST = 2 - const val DOC_REQUEST = 1 - } - - override fun onActivityFinish(imageUrl: String, barCodeString: String?) { - val intent = Intent() - intent.putExtra(ACUANT_EXTRA_IMAGE_URL, imageUrl) - intent.putExtra(ACUANT_EXTRA_PDF417_BARCODE, barCodeString) - this@AcuantCameraActivity.setResult(RESULT_SUCCESS_CODE, intent) - this@AcuantCameraActivity.finish() - } - - override fun onActivityFinish(mrzResult: MrzResult) { - val intent = Intent() - intent.putExtra(ACUANT_EXTRA_MRZ_RESULT, mrzResult) - this@AcuantCameraActivity.setResult(RESULT_SUCCESS_CODE, intent) - this@AcuantCameraActivity.finish() - } - - override fun onActivityFinish(barCodeString: String?) { - val intent = Intent() - intent.putExtra(ACUANT_EXTRA_PDF417_BARCODE, barCodeString) - this@AcuantCameraActivity.setResult(RESULT_SUCCESS_CODE, intent) - this@AcuantCameraActivity.finish() - } - - private fun hideTopMenu() { - @Suppress("DEPRECATION") //this needs android x to use the not deprecated code - window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN - actionBar?.hide() - supportActionBar?.hide() - } - - override fun onResume() { - super.onResume() - hideTopMenu() - } + private lateinit var binding: ActivityCameraBinding + //Camera Launch override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + binding = ActivityCameraBinding.inflate(layoutInflater) + setContentView(binding.root) + hideTopMenu() val unserializedOptions = intent.getSerializableExtra(ACUANT_EXTRA_CAMERA_OPTIONS) - val isAutoCapture = intent.getBooleanExtra(ACUANT_EXTRA_IS_AUTO_CAPTURE, true) - val isBorderAllowed = intent.getBooleanExtra(ACUANT_EXTRA_BORDER_ENABLED, true) val options: AcuantCameraOptions = if (unserializedOptions == null) { - AcuantCameraOptions.DocumentCameraOptionsBuilder().setAutoCapture(isAutoCapture).setAllowBox(isBorderAllowed).build() + AcuantCameraOptions.DocumentCameraOptionsBuilder().build() } else { unserializedOptions as AcuantCameraOptions } + //if the user wants to use google's barcode reader check if google apis are available if (options.useGMS) { - val resultCode = try { - val googleApi = GoogleApiAvailability.getInstance() - googleApi.isGooglePlayServicesAvailable(this) - } catch (e: java.lang.Exception) { - e.printStackTrace() - ConnectionResult.SERVICE_INVALID - } - - if (resultCode != ConnectionResult.SUCCESS) { - options.useGMS = false - } + options.useGMS = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS } - if (supportCamera2(this)) { - setContentView(R.layout.activity_acu_camera) - hideTopMenu() - - if (null == savedInstanceState) { - val cameraFragment: AcuantBaseCameraFragment = when (options.cameraMode) { - CameraMode.BarcodeOnly -> { - AcuantBarcodeCameraFragment.newInstance() - } - CameraMode.Mrz -> { - AcuantMrzCameraFragment.newInstance() - } - else -> { //Document - AcuantDocCameraFragment.newInstance() - } - } - cameraFragment.arguments = Bundle().apply { - putBoolean(ACUANT_EXTRA_IS_AUTO_CAPTURE, isAutoCapture) - putBoolean(ACUANT_EXTRA_BORDER_ENABLED, isBorderAllowed) - putSerializable(ACUANT_EXTRA_CAMERA_OPTIONS, options) - } - - - if (!options.useGMS) { - onActivityFinish(null) - } else { - supportFragmentManager.beginTransaction() - .replace(R.id.container, cameraFragment as Fragment) - .commit() + //start the camera if this si the first time the activity is created (camera already exists otherwise) + if (savedInstanceState == null) { + val cameraFragment: AcuantBaseCameraFragment = when (options.cameraMode) { + AcuantCameraOptions.CameraMode.BarcodeOnly -> { + AcuantBarcodeCameraFragment.newInstance(options) } - } - } else { - when (options.cameraMode) { - CameraMode.BarcodeOnly -> { - if (options.useGMS) { - val cameraIntent = Intent( - this@AcuantCameraActivity, - com.acuant.acuantcamera.camera.barcode.cameraone.BarcodeCaptureActivity::class.java - ) - - cameraIntent.putExtra(ACUANT_EXTRA_CAMERA_OPTIONS, options) - - startActivityForResult(cameraIntent, BARCODE_REQUEST) - } else { - onActivityFinish(null) - } - } - CameraMode.Mrz -> { - val cameraIntent = Intent( - this@AcuantCameraActivity, - com.acuant.acuantcamera.camera.mrz.cameraone.MrzCaptureActivity::class.java - ) - - cameraIntent.putExtra(ACUANT_EXTRA_CAMERA_OPTIONS, options) - - startActivityForResult(cameraIntent, MRZ_REQUEST) + AcuantCameraOptions.CameraMode.Mrz -> { + AcuantMrzCameraFragment.newInstance(options) } else -> { //Document - val cameraIntent = Intent( - this@AcuantCameraActivity, - DocumentCaptureActivity::class.java - ) - cameraIntent.putExtra(ACUANT_EXTRA_IS_AUTO_CAPTURE, options.autoCapture) - cameraIntent.putExtra(ACUANT_EXTRA_CAMERA_OPTIONS, options) - - startActivityForResult(cameraIntent, DOC_REQUEST) + AcuantDocCameraFragment.newInstance(options) } } + + supportFragmentManager.beginTransaction() + .replace(R.id.container, cameraFragment as Fragment) + .commit() } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - when(requestCode){ - DOC_REQUEST -> { - if(resultCode == RESULT_SUCCESS_CODE && data != null){ - onActivityFinish(data.getStringExtra(ACUANT_EXTRA_IMAGE_URL) ?: "-1", data.getStringExtra(ACUANT_EXTRA_PDF417_BARCODE)) - } - else{ - this@AcuantCameraActivity.finish() - } - } - MRZ_REQUEST -> { - if(resultCode == RESULT_SUCCESS_CODE && data != null){ - onActivityFinish(data.getSerializableExtra(ACUANT_EXTRA_MRZ_RESULT) as MrzResult) - } - else{ - this@AcuantCameraActivity.finish() - } - } - BARCODE_REQUEST -> { - if(resultCode == RESULT_SUCCESS_CODE && data != null){ - onActivityFinish(data.getStringExtra(ACUANT_EXTRA_PDF417_BARCODE)) - } - else{ - this@AcuantCameraActivity.finish() - } - } - } + //Camera Responses + override fun onCameraDone(imageUrl: String, barCodeString: String?) { + val intent = Intent() + intent.putExtra(ACUANT_EXTRA_IMAGE_URL, imageUrl) + intent.putExtra(ACUANT_EXTRA_PDF417_BARCODE, barCodeString) + this@AcuantCameraActivity.setResult(RESULT_OK, intent) + this@AcuantCameraActivity.finish() } - override fun onBackPressed() { + override fun onCameraDone(mrzResult: MrzResult) { + val intent = Intent() + intent.putExtra(ACUANT_EXTRA_MRZ_RESULT, mrzResult) + this@AcuantCameraActivity.setResult(RESULT_OK, intent) this@AcuantCameraActivity.finish() } - private fun supportCamera2(activity: AppCompatActivity): Boolean { - // Check if we're running on Android 5.0 or higher - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (Build.MODEL == "Nexus 9") { - false - } else hasSuitableCamera2Camera(activity) - } else false + override fun onCameraDone(barCodeString: String) { + val intent = Intent() + intent.putExtra(ACUANT_EXTRA_PDF417_BARCODE, barCodeString) + this@AcuantCameraActivity.setResult(RESULT_OK, intent) + this@AcuantCameraActivity.finish() } - private fun hasSuitableCamera2Camera(activity: AppCompatActivity): Boolean { - var foundCamera = false - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - val manager = activity.applicationContext.getSystemService(Context.CAMERA_SERVICE) as CameraManager - try { - // Find first back-facing camera that has necessary capability. - val cameraIds = manager.cameraIdList - for (id in cameraIds) { - val info = manager.getCameraCharacteristics(id) - val facing = info.get(CameraCharacteristics.LENS_FACING)!! - - val level = info.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)!! - val hasFullLevel = level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL - - val syncLatency = info.get(CameraCharacteristics.SYNC_MAX_LATENCY)!! - val hasEnoughCapability = syncLatency == CameraCharacteristics.SYNC_MAX_LATENCY_PER_FRAME_CONTROL - - // All these are guaranteed by - // CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, but checking - // for only the things we care about expands range of devices we can run on. - // We want: - // - Back-facing camera - // - Per-frame synchronization (so that exposure can be changed every frame) - if (facing == CameraCharacteristics.LENS_FACING_BACK && (hasFullLevel || hasEnoughCapability)) { - // Found suitable camera - get info, open, and set up outputs - foundCamera = true - break - } - } + override fun onCancel() { + val intent = Intent() + this@AcuantCameraActivity.setResult(RESULT_CANCELED, intent) + this@AcuantCameraActivity.finish() + } - } catch (e: Exception) { - e.printStackTrace() - } + override fun onError(error: AcuantError) { + val intent = Intent() + intent.putExtra(ACUANT_EXTRA_ERROR, error) + this@AcuantCameraActivity.setResult(RESULT_ERROR, intent) + this@AcuantCameraActivity.finish() + } - } - return foundCamera + //misc/housekeeping + override fun onBackPressed() { + onCancel() + } + + private fun hideTopMenu() { + actionBar?.hide() + supportActionBar?.hide() + } + override fun onResume() { + super.onResume() + hideTopMenu() } -} +} \ 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 acebcae..0cedd3e 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantCameraOptions.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/AcuantCameraOptions.kt @@ -10,27 +10,26 @@ import java.io.Serializable * ACUANT_EXTRA_IS_AUTO_CAPTURE with the autoCapture variable * ACUANT_EXTRA_BORDER_ENABLED with the allowBox variable */ + //todo this should be refactored into a base class and extensions when allowed to break backwards compatibility -open class AcuantCameraOptions -@Deprecated("Use DocumentCameraOptionsBuilder or MrzCameraOptionsBuilder, constructor will be private in the future.") -constructor( - val timeInMsPerDigit: Int = 900, - val digitsToShow: Int = 2, - val allowBox : Boolean = true, - val autoCapture : Boolean = true, - val bracketLengthInHorizontal : Int = 155, - val bracketLengthInVertical : Int = 255, - val defaultBracketMarginWidth : Int = 160, - val defaultBracketMarginHeight : Int = 160, - val colorHold : Int = Color.YELLOW, - val colorCapturing : Int = Color.GREEN, - val colorBracketAlign : Int = Color.BLACK, - val colorBracketCloser : Int = Color.RED, - val colorBracketHold : Int = Color.YELLOW, - val colorBracketCapturing : Int = Color.GREEN, - var useGMS: Boolean = true, - @Deprecated("This variable is not exposed in any of the OptionsBuilders, and is not intended to be modified externally. When the constructor goes private you will not be able to modify it.") - val cardRatio : Float = 0.65f, +open class AcuantCameraOptions +internal constructor( + internal val timeInMsPerDigit: Int = 900, + internal val digitsToShow: Int = 2, + internal val allowBox : Boolean = true, + internal val autoCapture : Boolean = true, + internal val bracketLengthInHorizontal : Int = 155, + internal val bracketLengthInVertical : Int = 255, + internal val defaultBracketMarginWidth : Int = 160, + internal val defaultBracketMarginHeight : Int = 160, + internal val colorHold : Int = Color.YELLOW, + internal val colorCapturing : Int = Color.GREEN, + internal val colorBracketAlign : Int = Color.BLACK, + internal val colorBracketCloser : Int = Color.RED, + internal val colorBracketHold : Int = Color.YELLOW, + internal val colorBracketCapturing : Int = Color.GREEN, + internal var useGMS: Boolean = true, + internal val cardRatio : Float = 0.65f, internal val cameraMode: CameraMode = CameraMode.Document ) : Serializable { @@ -59,7 +58,6 @@ constructor( private var colorBracketCapturing : Int = Color.GREEN private var useGms: Boolean = true private val cardRatio : Float = 0.65f - private var isMrzMode: Boolean = false fun setTimeInMsPerDigit(value: Int) : DocumentCameraOptionsBuilder { timeInMsPerDigit = value @@ -206,7 +204,6 @@ constructor( private var colorBracketHold : Int = Color.YELLOW private var colorBracketCapturing : Int = Color.GREEN private var useGMS: Boolean = true - //private val cardRatio : Float = 0.15f private val cardRatio : Float = 0.65f private var isMrzMode: Boolean = true diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/AcuantBarcodeCameraFragment.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/AcuantBarcodeCameraFragment.kt index 6f5fc04..bc6b39c 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/AcuantBarcodeCameraFragment.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/AcuantBarcodeCameraFragment.kt @@ -1,75 +1,138 @@ package com.acuant.acuantcamera.camera.barcode -import android.graphics.* -import android.os.* -import android.support.v4.app.ActivityCompat -import android.support.v4.content.res.ResourcesCompat -import android.util.Log -import android.view.* +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.os.CountDownTimer +import android.util.Size +import android.view.LayoutInflater +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import androidx.appcompat.content.res.AppCompatResources +import androidx.camera.core.ImageAnalysis +import androidx.core.content.res.ResourcesCompat import com.acuant.acuantcamera.R import com.acuant.acuantcamera.camera.AcuantBaseCameraFragment import com.acuant.acuantcamera.camera.AcuantCameraOptions -import com.acuant.acuantcamera.camera.ICameraActivityFinish -import com.acuant.acuantcamera.constant.* -import com.acuant.acuantcamera.detector.barcode.AcuantBarcodeDetector -import com.acuant.acuantcamera.detector.barcode.AcuantBarcodeDetectorHandler -import com.acuant.acuantcamera.overlay.DocRectangleView +import com.acuant.acuantcamera.databinding.BarcodeFragmentUiBinding +import com.acuant.acuantcamera.detector.DocumentFrameAnalyzer -class AcuantBarcodeCameraFragment : AcuantBaseCameraFragment(), - ActivityCompat.OnRequestPermissionsResultCallback, AcuantBarcodeDetectorHandler { +enum class BarcodeCameraState { Align, Capturing } - private var done = false - private lateinit var autoCancel: CountDownTimer +class AcuantBarcodeCameraFragment: AcuantBaseCameraFragment() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - options = arguments?.getSerializable(ACUANT_EXTRA_CAMERA_OPTIONS) as AcuantCameraOptions? ?: AcuantCameraOptions.BarcodeCameraOptionsBuilder().build() + private var cameraUiContainerBinding: BarcodeFragmentUiBinding? = null + private var textView: TextView? = null + private var imageView: ImageView? = null + private var autoCancelCountdown: CountDownTimer? = null + private var captureCountdown: CountDownTimer? = null + private var barcodeString: String? = null + private var defaultTextDrawable: Drawable? = null + private var timeToCancel: Long = 20000 - detectors = listOf(AcuantBarcodeDetector(this.activity!!.applicationContext, this)) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + cameraUiContainerBinding?.root?.let { + fragmentCameraBinding!!.root.removeView(it) + } + + cameraUiContainerBinding = BarcodeFragmentUiBinding.inflate( + LayoutInflater.from(requireContext()), + fragmentCameraBinding!!.root, + true + ) + + textView = cameraUiContainerBinding?.barcodeText + imageView = cameraUiContainerBinding?.barcodeImage + + timeToCancel = acuantOptions.digitsToShow.toLong() + + defaultTextDrawable = AppCompatResources.getDrawable(requireContext(), R.drawable.camera_text_config_default) - defaultTextDrawable = activity!!.getDrawable(R.drawable.camera_text_config_default) - capturingTextDrawable = activity!!.getDrawable(R.drawable.camera_text_config_capturing) } - override fun setTextFromState(state: CameraState) { + override fun onPause() { + autoCancelCountdown?.cancel() + captureCountdown?.cancel() + autoCancelCountdown = null + captureCountdown = null + super.onPause() + } - textView.visibility = View.VISIBLE - textView.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT + override fun onResume() { + if (autoCancelCountdown == null) { + setTextFromState(BarcodeCameraState.Align) + autoCancelCountdown = object : CountDownTimer(timeToCancel, INTERVAL) { + override fun onFinish() { + cameraActivityListener.onCancel() + } + + override fun onTick(millisUntilFinished: Long) { + timeToCancel -= INTERVAL + } + }.start() + } + super.onResume() + } - when(state) { - CameraState.Capturing -> { - imageView.visibility = View.GONE - 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 = resources.getString(R.string.acuant_camera_capturing_barcode) - textView.setTextColor(options?.colorCapturing ?: Color.GREEN) - } - else -> {//align - 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_align_barcode) - textView.setTextColor(options?.colorHold ?: Color.WHITE) - imageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.barcode, null)) - imageView.rotation = 90f - imageView.alpha = 0.4f - imageView.visibility = View.VISIBLE - textView.bringToFront() + private fun setTextFromState(state: BarcodeCameraState) { + if (!isAdded) + return + val imageView = this.imageView + val textView = this.textView + + if (imageView != null && textView != null) { + + textView.visibility = View.VISIBLE + + when (state) { + BarcodeCameraState.Capturing -> { + imageView.visibility = View.GONE + 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 = resources.getString(R.string.acuant_camera_capturing_barcode) + textView.setTextColor(acuantOptions.colorCapturing) + } + else -> {//align + 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_align_barcode) + textView.setTextColor(acuantOptions.colorHold) + imageView.setImageDrawable( + ResourcesCompat.getDrawable( + resources, + R.drawable.barcode, + null + ) + ) + imageView.rotation = 90f + imageView.alpha = 0.4f + imageView.visibility = View.VISIBLE + textView.bringToFront() + } } } } - override fun onBarcodeDetected(barcode: String) { - this.barCodeString = barcode - if (!done) { - done = true - activity?.runOnUiThread { - setTextFromState(CameraState.Capturing) - autoCancel.cancel() - object : CountDownTimer(timeInMsPerDigit.toLong(), 100) { + override fun rotateUi(rotation: Int) { + textView?.rotation = rotation.toFloat() + } + + private fun onBarcodeDetection(barcode: String?) { + if (barcode != null) { + barcodeString = barcode + if (captureCountdown == null) { + setTextFromState(BarcodeCameraState.Capturing) + captureCountdown = object : CountDownTimer(acuantOptions.timeInMsPerDigit.toLong(), 100) { override fun onFinish() { - finish() + cameraActivityListener.onCameraDone(barcodeString ?: barcode) } override fun onTick(millisUntilFinished: Long) { @@ -80,46 +143,30 @@ class AcuantBarcodeCameraFragment : AcuantBaseCameraFragment(), } } - fun finish() { - if (activity is ICameraActivityFinish) { - (activity as ICameraActivityFinish).onActivityFinish(barCodeString) + override fun buildImageAnalyzer(screenAspectRatio: Int, rotation: Int) { + val frameAnalyzer = DocumentFrameAnalyzer { _, _, barcode, _ -> + onBarcodeDetection(barcode) } - } - - override fun onCreateView(inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? = inflater.inflate(R.layout.fragment_camera2_basic, container, false) - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - textureView = view.findViewById(R.id.texture) - rectangleView = view.findViewById(R.id.acu_doc_rectangle) as DocRectangleView - rectangleView.visibility = View.GONE - barcodeOnly = true - - super.onViewCreated(view, savedInstanceState) - - - autoCancel = object : CountDownTimer(digitsToShow.toLong(), 1000) { - override fun onFinish() { - finish() + frameAnalyzer.disableDocumentDetection() + imageAnalyzer = ImageAnalysis.Builder() +// .setTargetAspectRatio(screenAspectRatio) + .setTargetResolution(Size(1280, 960)) + .setTargetRotation(rotation) + .build() + .also { + it.setAnalyzer(cameraExecutor, frameAnalyzer) } - - override fun onTick(millisUntilFinished: Long) { - Log.d("BarcodeCamera","Giving Up In: $millisUntilFinished") - } - } - autoCancel.start() - - setTextFromState(CameraState.Align) - } - - override fun setTapToCapture() { - //n/a } companion object { - - @JvmStatic fun newInstance(): AcuantBarcodeCameraFragment = AcuantBarcodeCameraFragment() + const val INTERVAL = 1000.toLong() + + @JvmStatic fun newInstance(acuantOptions: AcuantCameraOptions): AcuantBarcodeCameraFragment { + val frag = AcuantBarcodeCameraFragment() + val args = Bundle() + args.putSerializable(INTERNAL_OPTIONS, acuantOptions) + frag.arguments = args + return frag + } } } \ No newline at end of file diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/AcuantBarcodeFeedback.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/AcuantBarcodeFeedback.kt deleted file mode 100644 index c984406..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/AcuantBarcodeFeedback.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.acuant.acuantcamera.camera.barcode.cameraone - -import android.graphics.Point -import android.util.Size - -data class AcuantBarcodeFeedback(val feedback: BarcodeFeedback, 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 - - other as AcuantBarcodeFeedback - - if (feedback != other.feedback) return false - if (point != null) { - if (other.point == null) return false - if (!point.contentEquals(other.point)) return false - } else if (other.point != null) return false - if (frameSize != other.frameSize) return false - if (barcode != other.barcode) return false - - return true - } - - override fun hashCode(): Int { - var result = feedback.hashCode() - result = 31 * result + (point?.contentHashCode() ?: 0) - result = 31 * result + (frameSize?.hashCode() ?: 0) - result = 31 * result + (barcode?.hashCode() ?: 0) - return result - } -} \ No newline at end of file diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeCameraSource.java b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeCameraSource.java deleted file mode 100644 index 39c782d..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeCameraSource.java +++ /dev/null @@ -1,1270 +0,0 @@ -package com.acuant.acuantcamera.camera.barcode.cameraone; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.ImageFormat; -import android.graphics.SurfaceTexture; -import android.hardware.Camera; -import android.hardware.Camera.CameraInfo; -import android.os.Build; -import android.os.SystemClock; -import android.support.annotation.Nullable; -import android.support.annotation.RequiresPermission; -import android.support.annotation.StringDef; -import android.util.Log; -import android.view.Surface; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.WindowManager; - -import com.acuant.acuantcamera.camera.document.cameraone.DocumentCameraSourcePreview; -import com.google.android.gms.common.images.Size; -import com.google.android.gms.vision.Detector; -import com.google.android.gms.vision.Frame; - -import java.io.IOException; -import java.lang.Thread.State; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -// Note: This requires Google Play Services 8.1 or higher, due to using indirect byte buffers for -// storing images. - -/** - * Manages the camera in conjunction with an underlying - * {@link Detector}. This receives preview frames from the camera at - * a specified rate, sending those frames to the detector as fast as it is able to process those - * frames. - *

- * This camera source makes a best effort to manage processing on preview frames as fast as - * possible, while at the same time minimizing lag. As such, frames may be dropped if the detector - * is unable to keep up with the rate of frames generated by the camera. You should use - * {@link Builder#setRequestedFps(float)} to specify a frame rate that works well with - * the capabilities of the camera hardware and the detector options that you have selected. If CPU - * utilization is higher than you'd like, then you may want to consider reducing FPS. If the camera - * preview or detector results are too "jerky", then you may want to consider increasing FPS. - *

- * The following Android permission is required to use the camera: - *

    - *
  • android.permissions.CAMERA
  • - *
- */ -@SuppressWarnings("deprecation") -public class BarcodeCameraSource { - @SuppressLint("InlinedApi") - public static final int CAMERA_FACING_BACK = CameraInfo.CAMERA_FACING_BACK; - @SuppressLint("InlinedApi") - public static final int CAMERA_FACING_FRONT = CameraInfo.CAMERA_FACING_FRONT; - - private static final String TAG = "OpenCameraSource"; - - /** - * The dummy surface texture must be assigned a chosen name. Since we never use an OpenGL - * context, we can choose any ID we want here. - */ - private static final int DUMMY_TEXTURE_NAME = 100; - - /** - * If the absolute difference between a preview size aspect ratio and a picture size aspect - * ratio is less than this tolerance, they are considered to be the same aspect ratio. - */ - private static final float ASPECT_RATIO_TOLERANCE = 0.1f; - - @StringDef({ - Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE, - Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, - Camera.Parameters.FOCUS_MODE_AUTO, - Camera.Parameters.FOCUS_MODE_EDOF, - Camera.Parameters.FOCUS_MODE_FIXED, - Camera.Parameters.FOCUS_MODE_INFINITY, - Camera.Parameters.FOCUS_MODE_MACRO - }) - @Retention(RetentionPolicy.SOURCE) - private @interface FocusMode {} - - @StringDef({ - Camera.Parameters.FLASH_MODE_ON, - Camera.Parameters.FLASH_MODE_OFF, - Camera.Parameters.FLASH_MODE_AUTO, - Camera.Parameters.FLASH_MODE_RED_EYE, - Camera.Parameters.FLASH_MODE_TORCH - }) - @Retention(RetentionPolicy.SOURCE) - private @interface FlashMode {} - - private Context mContext; - - private final Object mCameraLock = new Object(); - - // Guarded by mCameraLock - private Camera mCamera; - - private int mFacing = CAMERA_FACING_BACK; - - - /** - * Rotation of the device, and thus the associated preview images captured from the device. - * See {@link Frame.Metadata#getRotation()}. - */ - private int mRotation; - - private Size mPreviewSize; - - // These values may be requested by the caller. Due to hardware limitations, we may need to - // select close, but not exactly the same values for these. - private float mRequestedFps = 30.0f; - private int mRequestedPreviewWidth = 1600; - private int mRequestedPreviewHeight = 1200; - - - private String mFocusMode = null; - private String mFlashMode = null; - - // These instances need to be held onto to avoid GC of their underlying resources. Even though - // these aren't used outside of the method that creates them, they still must have hard - // references maintained to them. - private SurfaceView mDummySurfaceView; - private SurfaceTexture mDummySurfaceTexture; - - /** - * Dedicated thread and associated runnable for calling into the detector with frames, as the - * frames become available from the camera. - */ - private Thread mProcessingThread; - private FrameProcessingRunnable mFrameProcessor; - - /** - * Map to convert between a byte array, received from the camera, and its associated byte - * buffer. We use byte buffers internally because this is a more efficient way to call into - * native code later (avoids a potential copy). - */ - private Map mBytesToByteBuffer = new HashMap<>(); - - //============================================================================================== - // Builder - //============================================================================================== - - /** - * Builder for configuring and creating an associated camera source. - */ - public static class Builder { - private final Detector mDetector; - private BarcodeCameraSource barcodeCameraSource = new BarcodeCameraSource(); - - /** - * Creates a camera source builder with the supplied context and detector. Camera preview - * images will be streamed to the associated detector upon starting the camera source. - */ - public Builder(Context context, Detector detector) { - if (context == null) { - throw new IllegalArgumentException("No context supplied."); - } - if (detector == null) { - throw new IllegalArgumentException("No detector supplied."); - } - - mDetector = detector; - barcodeCameraSource.mContext = context; - } - - /** - * Sets the requested frame rate in frames per second. If the exact requested value is not - * not available, the best matching available value is selected. Default: 30. - */ - public Builder setRequestedFps(float fps) { - if (fps <= 0) { - throw new IllegalArgumentException("Invalid fps: " + fps); - } - barcodeCameraSource.mRequestedFps = fps; - return this; - } - - public Builder setFocusMode(@FocusMode String mode) { - barcodeCameraSource.mFocusMode = mode; - return this; - } - - - - public Builder setFlashMode(@FlashMode String mode) { - barcodeCameraSource.mFlashMode = mode; - return this; - } - - /** - * Sets the desired width and height of the camera frames in pixels. If the exact desired - * values are not available options, the best matching available options are selected. - * Also, we try to select a preview size which corresponds to the aspect ratio of an - * associated full picture size, if applicable. Default: 1024x768. - */ - public Builder setRequestedPreviewSize(int width, int height) { - // Restrict the requested range to something within the realm of possibility. The - // choice of 1000000 is a bit arbitrary -- intended to be well beyond resolutions that - // devices can support. We bound this to avoid int overflow in the code later. - final int MAX = 1000000; - if ((width <= 0) || (width > MAX) || (height <= 0) || (height > MAX)) { - throw new IllegalArgumentException("Invalid preview size: " + width + "x" + height); - } - barcodeCameraSource.mRequestedPreviewWidth = width; - barcodeCameraSource.mRequestedPreviewHeight = height; - return this; - } - - /** - * Sets the camera to use (either {@link #CAMERA_FACING_BACK} or - * {@link #CAMERA_FACING_FRONT}). Default: back facing. - */ - public Builder setFacing(int facing) { - if ((facing != CAMERA_FACING_BACK) && (facing != CAMERA_FACING_FRONT)) { - throw new IllegalArgumentException("Invalid camera: " + facing); - } - barcodeCameraSource.mFacing = facing; - return this; - } - - /** - * Creates an instance of the camera source. - */ - public BarcodeCameraSource build() { - barcodeCameraSource.mFrameProcessor = barcodeCameraSource.new FrameProcessingRunnable(mDetector); - return barcodeCameraSource; - } - } - - //============================================================================================== - // Bridge Functionality for the Camera1 API - //============================================================================================== - - /** - * Callback interface used to signal the moment of actual image capture. - */ - public interface ShutterCallback { - /** - * Called as near as possible to the moment when a photo is captured from the sensor. This - * is a good opportunity to play a shutter sound or give other feedback of camera operation. - * This may be some time after the photo was triggered, but some time before the actual data - * is available. - */ - void onShutter(); - } - - /** - * Callback interface used to supply image data from a photo capture. - */ - public interface PictureCallback { - /** - * Called when image data is available after a picture is taken. The format of the data - * is a jpeg binary. - */ - void onPictureTaken(byte[] data); - } - - /** - * Callback interface used to notify on completion of camera auto focus. - */ - public interface AutoFocusCallback { - /** - * Called when the camera auto focus completes. If the camera - * does not support auto-focus and autoFocus is called, - * onAutoFocus will be called immediately with a fake value of - * success set to true. - *

- * The auto-focus routine does not lock auto-exposure and auto-white - * balance after it completes. - * - * @param success true if focus was successful, false if otherwise - */ - void onAutoFocus(boolean success); - } - - /** - * Callback interface used to notify on auto focus start and stop. - *

- *

This is only supported in continuous autofocus modes -- {@link - * Camera.Parameters#FOCUS_MODE_CONTINUOUS_VIDEO} and {@link - * Camera.Parameters#FOCUS_MODE_CONTINUOUS_PICTURE}. Applications can show - * autofocus animation based on this.

- */ - public interface AutoFocusMoveCallback { - /** - * Called when the camera auto focus starts or stops. - * - * @param start true if focus starts to move, false if focus stops to move - */ - void onAutoFocusMoving(boolean start); - } - - //============================================================================================== - // Public - //============================================================================================== - - /** - * Stops the camera and releases the resources of the camera and underlying detector. - */ - public void release() { - synchronized (mCameraLock) { - stop(); - mFrameProcessor.release(); - - } - } - - /** - * Opens the camera and starts sending preview frames to the underlying detector. The preview - * frames are not displayed. - * - * @throws IOException if the camera's preview texture or display could not be initialized - */ - @RequiresPermission(Manifest.permission.CAMERA) - public BarcodeCameraSource start() throws IOException { - synchronized (mCameraLock) { - if (mCamera != null) { - return this; - } - - mCamera = createCamera(); - - mDummySurfaceTexture = new SurfaceTexture(DUMMY_TEXTURE_NAME); - mCamera.setPreviewTexture(mDummySurfaceTexture); - mCamera.startPreview(); - - mProcessingThread = new Thread(mFrameProcessor); - mFrameProcessor.setActive(true); - mProcessingThread.start(); - } - return this; - } - - @RequiresPermission(Manifest.permission.CAMERA) - public BarcodeCameraSource start(SurfaceHolder surfaceHolder, BarcodeCameraSourcePreview preview) throws IOException { - synchronized (mCameraLock) { - if (mCamera != null) { - return this; - } - - try { - mCamera = createCamera(); - } catch (RuntimeException e) { - e.printStackTrace(); - throw new IOException(); - } - mCamera.setPreviewDisplay(surfaceHolder); - mCamera.startPreview(); - - if (preview != null) { - preview.requestLayout(); - } - - mProcessingThread = new Thread(mFrameProcessor); - mFrameProcessor.setActive(true); - mProcessingThread.start(); - - - } - return this; - } - - /** - * Closes the camera and stops sending frames to the underlying frame detector. - *

- * This camera source may be restarted again by calling {@link #start()} or - * {@link #start(SurfaceHolder, BarcodeCameraSourcePreview)}. - *

- * Call {@link #release()} instead to completely shut down this camera source and release the - * resources of the underlying detector. - */ - public void stop() { - synchronized (mCameraLock) { - mFrameProcessor.setActive(false); - if (mProcessingThread != null) { - try { - // Wait for the thread to complete to ensure that we can't have multiple threads - // executing at the same time (i.e., which would happen if we called start too - // quickly after stop). - mProcessingThread.join(); - } catch (InterruptedException e) { - Log.d(TAG, "Frame processing thread interrupted on release."); - } - mProcessingThread = null; - } - - // clear the buffer to prevent oom exceptions - mBytesToByteBuffer.clear(); - - if (mCamera != null) { - mCamera.stopPreview(); - mCamera.setPreviewCallbackWithBuffer(null); - try { - mCamera.setPreviewTexture(null); - } catch (Exception e) { - Log.e(TAG, "Failed to clear camera preview: " + e); - } - mCamera.release(); - mCamera = null; - } - } - } - - /** - * Returns the preview size that is currently in use by the underlying camera. - */ - public Size getPreviewSize() { - return mPreviewSize; - } - - /** - * Returns the selected camera; one of {@link #CAMERA_FACING_BACK} or - * {@link #CAMERA_FACING_FRONT}. - */ - public int getCameraFacing() { - return mFacing; - } - - public int doZoom(float scale) { - synchronized (mCameraLock) { - if (mCamera == null) { - return 0; - } - int currentZoom = 0; - int maxZoom; - Camera.Parameters parameters = mCamera.getParameters(); - if (!parameters.isZoomSupported()) { - Log.w(TAG, "Zoom is not supported on this device"); - return currentZoom; - } - maxZoom = parameters.getMaxZoom(); - - currentZoom = parameters.getZoom() + 1; - float newZoom; - if (scale > 1) { - newZoom = currentZoom + scale * (maxZoom / 10); - } else { - newZoom = currentZoom * scale; - } - currentZoom = Math.round(newZoom) - 1; - if (currentZoom < 0) { - currentZoom = 0; - } else if (currentZoom > maxZoom) { - currentZoom = maxZoom; - } - parameters.setZoom(currentZoom); - mCamera.setParameters(parameters); - return currentZoom; - } - } - - /** - * Initiates taking a picture, which happens asynchronously. The camera source should have been - * activated previously with {@link #start()} or {@link #start(SurfaceHolder, BarcodeCameraSourcePreview)}. The camera - * preview is suspended while the picture is being taken, but will resume once picture taking is - * done. - * - * @param shutter the callback for image capture moment, or null - * @param jpeg the callback for JPEG image data, or null - */ - public void takePicture(ShutterCallback shutter, PictureCallback jpeg) { - synchronized (mCameraLock) { - if (mCamera != null) { - PictureStartCallback startCallback = new PictureStartCallback(); - startCallback.mDelegate = shutter; - PictureDoneCallback doneCallback = new PictureDoneCallback(); - doneCallback.mDelegate = jpeg; - mCamera.takePicture(startCallback, null, null, doneCallback); - } - } - } - - /** - * Gets the current focus mode setting. - * - * @return current focus mode. This value is null if the camera is not yet created. Applications should call {@link - * #autoFocus(AutoFocusCallback)} to start the focus if focus - * mode is FOCUS_MODE_AUTO or FOCUS_MODE_MACRO. - * @see Camera.Parameters#FOCUS_MODE_AUTO - * @see Camera.Parameters#FOCUS_MODE_INFINITY - * @see Camera.Parameters#FOCUS_MODE_MACRO - * @see Camera.Parameters#FOCUS_MODE_FIXED - * @see Camera.Parameters#FOCUS_MODE_EDOF - * @see Camera.Parameters#FOCUS_MODE_CONTINUOUS_VIDEO - * @see Camera.Parameters#FOCUS_MODE_CONTINUOUS_PICTURE - */ - @Nullable - @FocusMode - public String getFocusMode() { - return mFocusMode; - } - - /** - * Sets the focus mode. - * - * @param mode the focus mode - * @return {@code true} if the focus mode is set, {@code false} otherwise - * @see #getFocusMode() - */ - public boolean setFocusMode(@FocusMode String mode) { - synchronized (mCameraLock) { - if (mCamera != null && mode != null) { - Camera.Parameters parameters = mCamera.getParameters(); - if (parameters.getSupportedFocusModes().contains(mode)) { - parameters.setFocusMode(mode); - mCamera.setParameters(parameters); - mFocusMode = mode; - return true; - } - } - - return false; - } - } - - /** - * Gets the current flash mode setting. - * - * @return current flash mode. null if flash mode setting is not - * supported or the camera is not yet created. - * @see Camera.Parameters#FLASH_MODE_OFF - * @see Camera.Parameters#FLASH_MODE_AUTO - * @see Camera.Parameters#FLASH_MODE_ON - * @see Camera.Parameters#FLASH_MODE_RED_EYE - * @see Camera.Parameters#FLASH_MODE_TORCH - */ - @Nullable - @FlashMode - public String getFlashMode() { - return mFlashMode; - } - - /** - * Sets the flash mode. - * - * @param mode flash mode. - * @return {@code true} if the flash mode is set, {@code false} otherwise - * @see #getFlashMode() - */ - public boolean setFlashMode(@FlashMode String mode) { - synchronized (mCameraLock) { - if (mCamera != null && mode != null) { - Camera.Parameters parameters = mCamera.getParameters(); - if (parameters.getSupportedFlashModes().contains(mode)) { - parameters.setFlashMode(mode); - mCamera.setParameters(parameters); - mFlashMode = mode; - return true; - } - } - - return false; - } - } - - /** - * Starts camera auto-focus and registers a callback function to run when - * the camera is focused. This method is only valid when preview is active - * (between {@link #start()} or {@link #start(SurfaceHolder, BarcodeCameraSourcePreview)} and before {@link #stop()} or {@link #release()}). - *

- *

Callers should check - * {@link #getFocusMode()} to determine if - * this method should be called. If the camera does not support auto-focus, - * it is a no-op and {@link AutoFocusCallback#onAutoFocus(boolean)} - * callback will be called immediately. - *

- *

If the current flash mode is not - * {@link Camera.Parameters#FLASH_MODE_OFF}, flash may be - * fired during auto-focus, depending on the driver and camera hardware.

- * - * @param cb the callback to run - * @see #cancelAutoFocus() - */ - public void autoFocus(@Nullable AutoFocusCallback cb) { - synchronized (mCameraLock) { - if (mCamera != null) { - CameraAutoFocusCallback autoFocusCallback = null; - if (cb != null) { - autoFocusCallback = new CameraAutoFocusCallback(); - autoFocusCallback.mDelegate = cb; - } - mCamera.autoFocus(autoFocusCallback); - } - } - } - - /** - * Cancels any auto-focus function in progress. - * Whether or not auto-focus is currently in progress, - * this function will return the focus position to the default. - * If the camera does not support auto-focus, this is a no-op. - * - * @see #autoFocus(AutoFocusCallback) - */ - public void cancelAutoFocus() { - synchronized (mCameraLock) { - if (mCamera != null) { - mCamera.cancelAutoFocus(); - } - } - } - - /** - * Sets camera auto-focus move callback. - * - * @param cb the callback to run - * @return {@code true} if the operation is supported (i.e. from Jelly Bean), {@code false} otherwise - */ - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - public boolean setAutoFocusMoveCallback(@Nullable AutoFocusMoveCallback cb) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { - return false; - } - - synchronized (mCameraLock) { - if (mCamera != null) { - CameraAutoFocusMoveCallback autoFocusMoveCallback = null; - if (cb != null) { - autoFocusMoveCallback = new CameraAutoFocusMoveCallback(); - autoFocusMoveCallback.mDelegate = cb; - } - mCamera.setAutoFocusMoveCallback(autoFocusMoveCallback); - } - } - - return true; - } - - //============================================================================================== - // Private - //============================================================================================== - - /** - * Only allow creation via the builder class. - */ - private BarcodeCameraSource() { - } - - /** - * Wraps the camera1 shutter callback so that the deprecated API isn't exposed. - */ - private class PictureStartCallback implements Camera.ShutterCallback { - private ShutterCallback mDelegate; - - @Override - public void onShutter() { - if (mDelegate != null) { - mDelegate.onShutter(); - } - } - } - - /** - * Wraps the final callback in the camera sequence, so that we can automatically turn the camera - * preview back on after the picture has been taken. - */ - private class PictureDoneCallback implements Camera.PictureCallback { - private PictureCallback mDelegate; - - @Override - public void onPictureTaken(byte[] data, Camera camera) { - if (mDelegate != null) { - mDelegate.onPictureTaken(data); - } - synchronized (mCameraLock) { - if (mCamera != null) { - mCamera.startPreview(); - } - } - } - } - - /** - * Wraps the camera1 auto focus callback so that the deprecated API isn't exposed. - */ - private class CameraAutoFocusCallback implements Camera.AutoFocusCallback { - private AutoFocusCallback mDelegate; - - @Override - public void onAutoFocus(boolean success, Camera camera) { - if (mDelegate != null) { - mDelegate.onAutoFocus(success); - } - } - } - - /** - * Wraps the camera1 auto focus move callback so that the deprecated API isn't exposed. - */ - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - private class CameraAutoFocusMoveCallback implements Camera.AutoFocusMoveCallback { - private AutoFocusMoveCallback mDelegate; - - @Override - public void onAutoFocusMoving(boolean start, Camera camera) { - if (mDelegate != null) { - mDelegate.onAutoFocusMoving(start); - } - } - } - - /** - * Opens the camera and applies the user settings. - * - * @throws RuntimeException if the method fails - */ - @SuppressLint("InlinedApi") - private Camera createCamera() { - int requestedCameraId = getIdForRequestedCamera(mFacing); - if (requestedCameraId == -1) { - throw new RuntimeException("Could not find requested camera."); - } - Camera camera = Camera.open(requestedCameraId); - - SizePair sizePair = selectSizePair(camera, mRequestedPreviewWidth, mRequestedPreviewHeight); - if (sizePair == null) { - throw new RuntimeException("Could not find suitable preview size."); - } - Size pictureSize = sizePair.pictureSize(); - mPreviewSize = sizePair.previewSize(); - - int[] previewFpsRange = selectPreviewFpsRange(camera, mRequestedFps); - if (previewFpsRange == null) { - throw new RuntimeException("Could not find suitable preview frames per second range."); - } - - Camera.Parameters parameters = camera.getParameters(); - - if (pictureSize != null) { - parameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight()); - } - - parameters.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); - parameters.setPreviewFpsRange( - previewFpsRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX], - previewFpsRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]); - parameters.setPreviewFormat(ImageFormat.NV21); - - setRotation(camera, parameters, requestedCameraId); - - if (mFocusMode != null) { - if (parameters.getSupportedFocusModes().contains( - mFocusMode)) { - parameters.setFocusMode(mFocusMode); - } else { - Log.i(TAG, "Camera focus mode: " + mFocusMode + " is not supported on this device."); - } - } - - // setting mFocusMode to the one set in the params - mFocusMode = parameters.getFocusMode(); - - if (mFlashMode != null) { - if (parameters.getSupportedFlashModes() != null) { - if (parameters.getSupportedFlashModes().contains( - mFlashMode)) { - parameters.setFlashMode(mFlashMode); - } else { - Log.i(TAG, "Camera flash mode: " + mFlashMode + " is not supported on this device."); - } - } - } - - // setting mFlashMode to the one set in the params - mFlashMode = parameters.getFlashMode(); - - camera.setParameters(parameters); - - - - // Four frame buffers are needed for working with the camera: - // - // one for the frame that is currently being executed upon in doing detection - // one for the next pending frame to process immediately upon completing detection - // two for the frames that the camera uses to populate future preview images - camera.setPreviewCallbackWithBuffer(new CameraPreviewCallback()); - camera.addCallbackBuffer(createPreviewBuffer(mPreviewSize)); - camera.addCallbackBuffer(createPreviewBuffer(mPreviewSize)); - camera.addCallbackBuffer(createPreviewBuffer(mPreviewSize)); - camera.addCallbackBuffer(createPreviewBuffer(mPreviewSize)); - - return camera; - } - - /** - * Gets the id for the camera specified by the direction it is facing. Returns -1 if no such - * camera was found. - * - * @param facing the desired camera (front-facing or rear-facing) - */ - private static int getIdForRequestedCamera(int facing) { - CameraInfo cameraInfo = new CameraInfo(); - for (int i = 0; i < Camera.getNumberOfCameras(); ++i) { - Camera.getCameraInfo(i, cameraInfo); - if (cameraInfo.facing == facing) { - return i; - } - } - return -1; - } - -// private static SizePair selectSizePair(Camera camera, int desiredWidth, int desiredHeight) { -// List validPreviewSizes = generateValidPreviewSizeList(camera); -// // The method for selecting the best size is to minimize the sum of the differences between -// // the desired values and the actual values for width and height. This is certainly not the -// // only way to select the best size, but it provides a decent tradeoff between using the -// // closest aspect ratio vs. using the closest pixel area. -// SizePair selectedPair = null; -// int maxArea = Integer.MIN_VALUE; -// int maxPict = Integer.MIN_VALUE; -// int area = Integer.MIN_VALUE; -// for (SizePair sizePair : validPreviewSizes) { -// Size size_pict = sizePair.pictureSize(); -// int newArea = size_pict.getWidth()*size_pict.getHeight(); -// if (newArea > maxPict) { -// maxPict = newArea; -// maxArea = Integer.MIN_VALUE; -// Size size = sizePair.previewSize(); -// area = size.getWidth()*size.getHeight(); -// if (maxArea < area) { -// selectedPair = sizePair; -// maxArea = area; -// } -// } -// else if (newArea == maxPict) { -// Size size = sizePair.previewSize(); -// area = size.getWidth()*size.getHeight(); -// if (maxArea < area) { -// selectedPair = sizePair; -// maxArea = area; -// } -// } -// } -// return selectedPair; -// } - - /** - * Selects the most suitable preview and picture size, given the desired width and height. - *

- * Even though we may only need the preview size, it's necessary to find both the preview - * size and the picture size of the camera together, because these need to have the same aspect - * ratio. On some hardware, if you would only set the preview size, you will get a distorted - * image. - * - * @param camera the camera to select a preview size from - * @param desiredWidth the desired width of the camera preview frames - * @param desiredHeight the desired height of the camera preview frames - * @return the selected preview and picture size pair - */ - private static SizePair selectSizePair(Camera camera, int desiredWidth, int desiredHeight) { - List validPreviewSizes = generateValidPreviewSizeList(camera); - long expectedSize = desiredWidth*desiredHeight; - int desiredShortSide = Math.min(desiredHeight, desiredWidth); - int desiredLongSide = Math.max(desiredHeight, desiredWidth); - // The method for selecting the best size is to minimize the sum of the differences between - // the desired values and the actual values for width and height. This is certainly not the - // only way to select the best size, but it provides a decent tradeoff between using the - // closest aspect ratio vs. using the closest pixel area. - SizePair selectedPair = null; - int minDiff = Integer.MAX_VALUE; - for (SizePair sizePair : validPreviewSizes) { - Size size = sizePair.previewSize(); - - - int currentShortSide = Math.min(size.getWidth(), size.getHeight()); - int currentLongSide = Math.max(size.getWidth(), size.getHeight()); - float change = Math.min(Math.min((float) desiredShortSide / currentShortSide, (float) desiredLongSide / currentLongSide), 2f); - currentLongSide *= change; - currentShortSide *= change; - - - int diff = Math.abs(currentShortSide - desiredShortSide) + - Math.abs(currentLongSide - desiredLongSide); - if ((!(currentLongSide > desiredLongSide) && !(currentShortSide > desiredShortSide)) && diff < minDiff && (sizePair.pictureSize().getWidth()*sizePair.pictureSize().getHeight() >= expectedSize * 0.6f)) { - selectedPair = sizePair; - minDiff = diff; - } - } - - if (selectedPair != null) { - return selectedPair; - } else if (validPreviewSizes.size() > 0){ - return validPreviewSizes.get(0); - } else { - return null; - } - } - -// private static SizePair selectSizePair(Camera camera, int desiredWidth, int desiredHeight) { -// List validPreviewSizes = generateValidPreviewSizeList(camera); -// -// int desiredSize = desiredHeight * desiredWidth; -// float sizeRatio = 0.6f; -// -// SizePair selectedPair = null; -// -// do { -// float desiredRatio = (float) Math.max(desiredWidth, desiredHeight) / Math.min(desiredWidth, desiredHeight); -// float minDiff = Integer.MAX_VALUE; -// for (SizePair sizePair : validPreviewSizes) { -// Size size = sizePair.previewSize(); -// float diff = Math.abs((float) Math.max(size.getWidth(), size.getHeight()) / Math.min(size.getWidth(), size.getHeight()) - desiredRatio); -// if (diff < minDiff && size.getWidth()*size.getHeight() > desiredSize*sizeRatio) { -// selectedPair = sizePair; -// minDiff = diff; -// } -// } -// sizeRatio -= 0.1f; -// } while (selectedPair == null); -// -// return selectedPair; -// } - - /** - * Stores a preview size and a corresponding same-aspect-ratio picture size. To avoid distorted - * preview images on some devices, the picture size must be set to a size that is the same - * aspect ratio as the preview size or the preview may end up being distorted. If the picture - * size is null, then there is no picture size with the same aspect ratio as the preview size. - */ - private static class SizePair { - private Size mPreview; - private Size mPicture; - - public SizePair(Camera.Size previewSize, - Camera.Size pictureSize) { - mPreview = new Size(previewSize.width, previewSize.height); - if (pictureSize != null) { - mPicture = new Size(pictureSize.width, pictureSize.height); - } - } - - public Size previewSize() { - return mPreview; - } - - @SuppressWarnings("unused") - public Size pictureSize() { - return mPicture; - } - } - - /** - * Generates a list of acceptable preview sizes. Preview sizes are not acceptable if there is - * not a corresponding picture size of the same aspect ratio. If there is a corresponding - * picture size of the same aspect ratio, the picture size is paired up with the preview size. - *

- * This is necessary because even if we don't use still pictures, the still picture size must be - * set to a size that is the same aspect ratio as the preview size we choose. Otherwise, the - * preview images may be distorted on some devices. - */ - private static List generateValidPreviewSizeList(Camera camera) { - Camera.Parameters parameters = camera.getParameters(); - List supportedPreviewSizes = - parameters.getSupportedPreviewSizes(); - List supportedPictureSizes = - parameters.getSupportedPictureSizes(); - List validPreviewSizes = new ArrayList<>(); - for (Camera.Size previewSize : supportedPreviewSizes) { - float previewAspectRatio = (float) previewSize.width / (float) previewSize.height; - - // By looping through the picture sizes in order, we favor the higher resolutions. - // We choose the highest resolution in order to support taking the full resolution - // picture later. - for (Camera.Size pictureSize : supportedPictureSizes) { - float pictureAspectRatio = (float) pictureSize.width / (float) pictureSize.height; - if (Math.abs(previewAspectRatio - pictureAspectRatio) < ASPECT_RATIO_TOLERANCE) { - validPreviewSizes.add(new SizePair(previewSize, pictureSize)); - break; - } - } - } - - // If there are no picture sizes with the same aspect ratio as any preview sizes, allow all - // of the preview sizes and hope that the camera can handle it. Probably unlikely, but we - // still account for it. - if (validPreviewSizes.size() == 0) { - Log.w(TAG, "No preview sizes have a corresponding same-aspect-ratio picture size"); - for (Camera.Size previewSize : supportedPreviewSizes) { - // The null picture size will let us know that we shouldn't set a picture size. - validPreviewSizes.add(new SizePair(previewSize, null)); - } - } - - return validPreviewSizes; - } - - /** - * Selects the most suitable preview frames per second range, given the desired frames per - * second. - * - * @param camera the camera to select a frames per second range from - * @param desiredPreviewFps the desired frames per second for the camera preview frames - * @return the selected preview frames per second range - */ - private int[] selectPreviewFpsRange(Camera camera, float desiredPreviewFps) { - // The camera API uses integers scaled by a factor of 1000 instead of floating-point frame - // rates. - int desiredPreviewFpsScaled = (int) (desiredPreviewFps * 1000.0f); - - // The method for selecting the best range is to minimize the sum of the differences between - // the desired value and the upper and lower bounds of the range. This may select a range - // that the desired value is outside of, but this is often preferred. For example, if the - // desired frame rate is 29.97, the range (30, 30) is probably more desirable than the - // range (15, 30). - int[] selectedFpsRange = null; - int minDiff = Integer.MAX_VALUE; - List previewFpsRangeList = camera.getParameters().getSupportedPreviewFpsRange(); - for (int[] range : previewFpsRangeList) { - int deltaMin = desiredPreviewFpsScaled - range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX]; - int deltaMax = desiredPreviewFpsScaled - range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]; - int diff = Math.abs(deltaMin) + Math.abs(deltaMax); - if (diff < minDiff) { - selectedFpsRange = range; - minDiff = diff; - } - } - return selectedFpsRange; - } - - /** - * Calculates the correct rotation for the given camera id and sets the rotation in the - * parameters. It also sets the camera's display orientation and rotation. - * - * @param parameters the camera parameters for which to set the rotation - * @param cameraId the camera id to set rotation based on - */ - private void setRotation(Camera camera, Camera.Parameters parameters, int cameraId) { - WindowManager windowManager = - (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - int degrees = 0; - int rotation = windowManager.getDefaultDisplay().getRotation(); - switch (rotation) { - case Surface.ROTATION_0: - degrees = 0; - break; - case Surface.ROTATION_90: - degrees = 90; - break; - case Surface.ROTATION_180: - degrees = 180; - break; - case Surface.ROTATION_270: - degrees = 270; - break; - default: - Log.e(TAG, "Bad rotation value: " + rotation); - } - - CameraInfo cameraInfo = new CameraInfo(); - Camera.getCameraInfo(cameraId, cameraInfo); - - int angle; - int displayAngle; - if (cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT) { - angle = (cameraInfo.orientation + degrees) % 360; - displayAngle = (360 - angle) % 360; // compensate for it being mirrored - } else { // back-facing - angle = (cameraInfo.orientation - degrees + 360) % 360; - displayAngle = angle; - } - - // This corresponds to the rotation constants in {@link Frame}. - mRotation = angle / 90; - - camera.setDisplayOrientation(displayAngle); - parameters.setRotation(angle); - } - - /** - * Creates one buffer for the camera preview callback. The size of the buffer is based off of - * the camera preview size and the format of the camera image. - * - * @return a new preview buffer of the appropriate size for the current camera settings - */ - private byte[] createPreviewBuffer(Size previewSize) { - int bitsPerPixel = ImageFormat.getBitsPerPixel(ImageFormat.NV21); - long sizeInBits = previewSize.getHeight() * previewSize.getWidth() * bitsPerPixel; - int bufferSize = (int) Math.ceil(sizeInBits / 8.0d) + 1; - - // - // NOTICE: This code only works when using play services v. 8.1 or higher. - // - - // Creating the byte array this way and wrapping it, as opposed to using .allocate(), - // should guarantee that there will be an array to work with. - byte[] byteArray = new byte[bufferSize]; - ByteBuffer buffer = ByteBuffer.wrap(byteArray); - if (!buffer.hasArray() || (buffer.array() != byteArray)) { - // I don't think that this will ever happen. But if it does, then we wouldn't be - // passing the preview content to the underlying detector later. - throw new IllegalStateException("Failed to create valid buffer for camera source."); - } - - mBytesToByteBuffer.put(byteArray, buffer); - return byteArray; - } - - //============================================================================================== - // Frame processing - //============================================================================================== - - /** - * Called when the camera has a new preview frame. - */ - private class CameraPreviewCallback implements Camera.PreviewCallback { - @Override - public void onPreviewFrame(byte[] data, Camera camera) { - mFrameProcessor.setNextFrame(data, camera); - } - } - - /** - * This runnable controls access to the underlying receiver, calling it to process frames when - * available from the camera. This is designed to run detection on frames as fast as possible - * (i.e., without unnecessary context switching or waiting on the next frame). - *

- * While detection is running on a frame, new frames may be received from the camera. As these - * frames come in, the most recent frame is held onto as pending. As soon as detection and its - * associated processing are done for the previous frame, detection on the mostly recently - * received frame will immediately start on the same thread. - */ - private class FrameProcessingRunnable implements Runnable { - private Detector mDetector; - private long mStartTimeMillis = SystemClock.elapsedRealtime(); - - // This lock guards all of the member variables below. - private final Object mLock = new Object(); - private boolean mActive = true; - - // These pending variables hold the state associated with the new frame awaiting processing. - private long mPendingTimeMillis; - private int mPendingFrameId = 0; - private ByteBuffer mPendingFrameData; - - FrameProcessingRunnable(Detector detector) { - mDetector = detector; - } - - /** - * Releases the underlying receiver. This is only safe to do after the associated thread - * has completed, which is managed in camera source's release method above. - */ - @SuppressLint("Assert") - void release() { - assert mProcessingThread == null || mProcessingThread.getState() == State.TERMINATED; - mDetector.release(); - mDetector = null; - } - - /** - * Marks the runnable as active/not active. Signals any blocked threads to continue. - */ - void setActive(boolean active) { - synchronized (mLock) { - mActive = active; - mLock.notifyAll(); - } - } - - /** - * Sets the frame data received from the camera. This adds the previous unused frame buffer - * (if present) back to the camera, and keeps a pending reference to the frame data for - * future use. - */ - void setNextFrame(byte[] data, Camera camera) { - synchronized (mLock) { - if (mPendingFrameData != null) { - camera.addCallbackBuffer(mPendingFrameData.array()); - mPendingFrameData = null; - } - - if (!mBytesToByteBuffer.containsKey(data)) { - Log.d(TAG, - "Skipping frame. Could not find ByteBuffer associated with the image " + - "data from the camera."); - return; - } - - // Timestamp and frame ID are maintained here, which will give downstream code some - // idea of the timing of frames received and when frames were dropped along the way. - mPendingTimeMillis = SystemClock.elapsedRealtime() - mStartTimeMillis; - mPendingFrameId++; - mPendingFrameData = mBytesToByteBuffer.get(data); - - // Notify the processor thread if it is waiting on the next frame (see below). - mLock.notifyAll(); - } - } - - /** - * As long as the processing thread is active, this executes detection on frames - * continuously. The next pending frame is either immediately available or hasn't been - * received yet. Once it is available, we transfer the frame info to local variables and - * run detection on that frame. It immediately loops back for the next frame without - * pausing. - *

- * If detection takes longer than the time in between new frames from the camera, this will - * mean that this loop will run without ever waiting on a frame, avoiding any context - * switching or frame acquisition time latency. - *

- * If you find that this is using more CPU than you'd like, you should probably decrease the - * FPS setting above to allow for some idle time in between frames. - */ - @Override - public void run() { - Frame outputFrame; - ByteBuffer data; - - while (true) { - synchronized (mLock) { - while (mActive && (mPendingFrameData == null)) { - try { - // Wait for the next frame to be received from the camera, since we - // don't have it yet. - mLock.wait(); - } catch (InterruptedException e) { - Log.d(TAG, "Frame processing loop terminated.", e); - return; - } - } - - if (!mActive) { - // Exit the loop once this camera source is stopped or released. We check - // this here, immediately after the wait() above, to handle the case where - // setActive(false) had been called, triggering the termination of this - // loop. - return; - } - - outputFrame = new Frame.Builder() - .setImageData(mPendingFrameData, mPreviewSize.getWidth(), - mPreviewSize.getHeight(), ImageFormat.NV21) - .setId(mPendingFrameId) - .setTimestampMillis(mPendingTimeMillis) - .setRotation(mRotation) - .build(); - - // Hold onto the frame data locally, so that we can use this for detection - // below. We need to clear mPendingFrameData to ensure that this buffer isn't - // recycled back to the camera before we are done using that data. - data = mPendingFrameData; - mPendingFrameData = null; - } - - // The code below needs to run outside of synchronization, because this will allow - // the camera to add pending frame(s) while we are running detection on the current - // frame. - - try { - mDetector.receiveFrame(outputFrame); - } catch (Throwable t) { - Log.e(TAG, "Exception thrown from receiver.", t); - } finally { - mCamera.addCallbackBuffer(data.array()); - } - } - } - } -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeCameraSourcePreview.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeCameraSourcePreview.kt deleted file mode 100644 index e819c5d..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeCameraSourcePreview.kt +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.acuant.acuantcamera.camera.barcode.cameraone - - -import android.Manifest -import android.content.Context -import android.content.res.Configuration -import android.support.annotation.RequiresPermission -import android.support.v7.app.AlertDialog -import android.util.AttributeSet -import android.util.Log -import android.view.SurfaceHolder -import android.view.SurfaceView -import android.view.ViewGroup -import android.widget.RelativeLayout -import com.acuant.acuantcamera.R -import java.io.IOException - -class BarcodeCameraSourcePreview(private val mContext: Context, attrs: AttributeSet?) : ViewGroup(mContext, attrs) { - var mSurfaceView: SurfaceView - var pointXOffset: Int = 0 - var pointYOffset: Int = 0 - private var mStartRequested: Boolean = false - private var mSurfaceAvailable: Boolean = false - private var barcodeCameraSource: BarcodeCameraSource? = null - - private val isPortraitMode: Boolean - get() { - val orientation = mContext.resources.configuration.orientation - if (orientation == Configuration.ORIENTATION_LANDSCAPE) { - return false - } - if (orientation == Configuration.ORIENTATION_PORTRAIT) { - return true - } - - Log.d(TAG, "isPortraitMode returning false by default") - return false - } - - init { - mStartRequested = false - mSurfaceAvailable = false - - val previewParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, - RelativeLayout.LayoutParams.MATCH_PARENT) - previewParams.addRule(RelativeLayout.CENTER_IN_PARENT) - mSurfaceView = SurfaceView(mContext) - mSurfaceView.layoutParams = previewParams - mSurfaceView.holder.addCallback(SurfaceCallback()) - addView(mSurfaceView) - } - - @RequiresPermission(Manifest.permission.CAMERA) - @Throws(IOException::class, SecurityException::class) - fun start(barcodeCameraSource: BarcodeCameraSource?) { - if (barcodeCameraSource == null) { - stop() - } - - this.barcodeCameraSource = barcodeCameraSource - - if (this.barcodeCameraSource != null) { - mStartRequested = true - startIfReady() - } - } - - fun stop() { - if (barcodeCameraSource != null) { - barcodeCameraSource!!.stop() - } - } - - fun release() { - if (barcodeCameraSource != null) { - barcodeCameraSource!!.release() - barcodeCameraSource = null - } - } - - @RequiresPermission(Manifest.permission.CAMERA) - @Throws(IOException::class, SecurityException::class) - private fun startIfReady() { - if (mStartRequested && mSurfaceAvailable) { - barcodeCameraSource!!.start(mSurfaceView.holder, this) - mStartRequested = false - } - } - - private inner class SurfaceCallback : SurfaceHolder.Callback { - - override fun surfaceCreated(surface: SurfaceHolder) { - mSurfaceAvailable = true - try { - startIfReady() - } catch (se: SecurityException) { - Log.e(TAG, "Do not have permission to start the camera", se) - } catch (e: IOException) { - Log.e(TAG, "Could not start camera source.", e) - val dialogBuilder = AlertDialog.Builder(context) - dialogBuilder.setMessage(R.string.error_starting_cam) - .setPositiveButton(R.string.ok - ) { dialog, _ -> - dialog.dismiss() - - } - dialogBuilder.create().show() - } - - } - - override fun surfaceDestroyed(surface: SurfaceHolder) { - mSurfaceAvailable = false - } - - override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {} - - } - - override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - - val viewWidth = right - left - val viewHeight = bottom - top - - var previewWidth = viewWidth - var previewHeight = viewHeight - if (barcodeCameraSource != null) { - val size = barcodeCameraSource?.previewSize - if (size != null) { - previewWidth = size.width - previewHeight = size.height - } - } - - // Swap width and height sizes when in portrait, since it will be rotated 90 degrees - if (isPortraitMode) { - val tmp = previewWidth - previewWidth = previewHeight - previewHeight = tmp - } - val childWidth: Int - val childHeight: Int - val childXOffset: Int - val childYOffset: Int - val widthRatio = viewWidth.toFloat() / previewWidth.toFloat() - val heightRatio = viewHeight.toFloat() / previewHeight.toFloat() - - if (widthRatio < heightRatio) { - childWidth = viewWidth - childHeight = (previewHeight.toFloat() * widthRatio).toInt() - } else { - childWidth = (previewWidth.toFloat() * heightRatio).toInt() - childHeight = viewHeight - } - - childXOffset = (childWidth - viewWidth) / 2 - childYOffset = (childHeight - viewHeight) / 2 - - pointXOffset = childXOffset - pointYOffset = childYOffset - - for (i in 0 until childCount) { - getChildAt(i).layout( - -1 * childXOffset, -1 * childYOffset, - childWidth - childXOffset, childHeight - childYOffset) - getChildAt(i).requestLayout() - } - try { - startIfReady() - } catch (e: IOException) { - e.printStackTrace() - } catch (se: SecurityException) { - se.printStackTrace() - } - } - - companion object { - private const val TAG = "CameraSourcePreview" - } -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeCaptureActivity.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeCaptureActivity.kt deleted file mode 100644 index d4bbb6a..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeCaptureActivity.kt +++ /dev/null @@ -1,444 +0,0 @@ -@file:Suppress("DEPRECATION") - -package com.acuant.acuantcamera.camera.barcode.cameraone - -import android.Manifest -import android.annotation.SuppressLint -import android.content.DialogInterface -import android.content.Intent -import android.content.pm.ActivityInfo -import android.content.pm.PackageManager -import android.content.res.Configuration.ORIENTATION_LANDSCAPE -import android.content.res.Configuration.ORIENTATION_PORTRAIT -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Point -import android.graphics.drawable.Drawable -import android.hardware.Camera -import android.os.Bundle -import android.os.CountDownTimer -import android.support.v4.app.ActivityCompat -import android.support.v7.app.AlertDialog -import android.support.v7.app.AppCompatActivity -import android.util.DisplayMetrics -import android.util.Log -import android.view.* -import android.widget.ImageView -import android.widget.RelativeLayout -import android.widget.TextView -import com.acuant.acuantcamera.R -import com.acuant.acuantcamera.camera.AcuantBaseCameraFragment.CameraState -import com.acuant.acuantcamera.camera.AcuantCameraActivity -import com.acuant.acuantcamera.camera.AcuantCameraOptions -import com.acuant.acuantcamera.constant.ACUANT_EXTRA_CAMERA_OPTIONS -import com.acuant.acuantcamera.constant.ACUANT_EXTRA_IMAGE_URL -import com.acuant.acuantcamera.constant.ACUANT_EXTRA_PDF417_BARCODE -import com.acuant.acuantcamera.detector.ImageSaver -import java.io.ByteArrayOutputStream -import java.io.File -import java.io.IOException -import java.util.* - -/** - * Activity for the multi-tracker app. This app detects barcodes and displays the value with the - * rear facing camera. During detection overlay graphics are drawn to indicate the position, - * size, and ID of each barcode. - */ -class BarcodeCaptureActivity : AppCompatActivity(), BarcodeCameraSource.PictureCallback, BarcodeCameraSource.ShutterCallback { - private var barcodeCameraSource: BarcodeCameraSource? = null - private var mPreview: BarcodeCameraSourcePreview? = null - private var capturing = false - private lateinit var barcodeProcessor: LiveBarcodeProcessor - private var permissionNotGranted = false - private var barcodeDetector: BarcodeDetector? = null - - private lateinit var instructionView: TextView - private lateinit var imageView: ImageView - - private var capturedbarcodeString: String? = null - - private var defaultTextDrawable: Drawable? = null - private var timeInMsPerDigit: Int = 800 - private var digitsToShow: Int = 2 - private lateinit var options: AcuantCameraOptions - private lateinit var displaySize: Point - private var lastOrientation = ORIENTATION_LANDSCAPE - private lateinit var parent: RelativeLayout - private lateinit var autoCancel: CountDownTimer - - /** - * Initializes the UI and creates the detector pipeline. - */ - public override fun onCreate(icicle: Bundle?) { - super.onCreate(icicle) - requestWindowFeature(Window.FEATURE_NO_TITLE) - requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY) - window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN) - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - supportActionBar?.title = "" - supportActionBar?.hide() - - defaultTextDrawable = this.getDrawable(R.drawable.camera_text_config_default) - - setContentView(R.layout.activity_acu_barcode_camera) - - parent = findViewById(R.id.cam1_barcode_parent) - mPreview = findViewById(R.id.cam1_barcode_preview) - instructionView = findViewById(R.id.cam1_barcode_text) - imageView = findViewById(R.id.cam1_barcode_image) - - - // Check for the camera permission before accessing the camera. If the - // permission is not granted yet, request permission. - val rc = ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) - if (rc == PackageManager.PERMISSION_GRANTED) { - createCameraSource(useFlash = false) - } else { - requestCameraPermission() - permissionNotGranted = true - } - - options = intent.getSerializableExtra(ACUANT_EXTRA_CAMERA_OPTIONS) as AcuantCameraOptions? ?: AcuantCameraOptions.BarcodeCameraOptionsBuilder().build() - - setOptions(options) - - setTextFromState(CameraState.Align) - - displaySize = Point() - this.windowManager.defaultDisplay.getSize(displaySize) - - autoCancel = object : CountDownTimer(digitsToShow.toLong(), 500) { - override fun onFinish() { - doFinish() - } - - override fun onTick(millisUntilFinished: Long) { - //do nothing - } - } - autoCancel.start() - - mOrientationEventListener = object : OrientationEventListener(this.applicationContext) { - override fun onOrientationChanged(orientation: Int) { - if (orientation < 0) { - return // Flip screen, Not take account - } - val curOrientation: Int = when { - orientation <= 45 -> { - ORIENTATION_PORTRAIT - } - orientation <= 135 -> { - ORIENTATION_LANDSCAPE_REVERSE - } - orientation <= 225 -> { - ORIENTATION_PORTRAIT_REVERSE - } - orientation <= 315 -> { - ORIENTATION_LANDSCAPE - } - else -> { - ORIENTATION_PORTRAIT - } - } - if (curOrientation != lastOrientation) { - onChanged(lastOrientation, curOrientation) - lastOrientation = curOrientation - } - } - } - } - - fun doFinish() { - this@BarcodeCaptureActivity.runOnUiThread { - val result = Intent() - result.putExtra(ACUANT_EXTRA_PDF417_BARCODE, this.capturedbarcodeString) - setResult(AcuantCameraActivity.RESULT_SUCCESS_CODE, result) - finish() - } - } - - /** - * Handles the requesting of the camera permission. This includes - * showing a "Snackbar" message of why the permission is needed then - * sending the request. - */ - private fun requestCameraPermission() { - Log.w(TAG, "Camera permission is not granted. Requesting permission") - - val permissions = arrayOf(Manifest.permission.CAMERA) - - val builder = AlertDialog.Builder(this) - builder.setMessage(R.string.cam_perm_request_text) - .setOnCancelListener { - if (!ActivityCompat.shouldShowRequestPermissionRationale(this, - Manifest.permission.CAMERA)) { - ActivityCompat.requestPermissions(this, permissions, RC_HANDLE_CAMERA_PERM) - } else { - finish() - } - } - .setPositiveButton(R.string.ok - ) { dialog, _ -> - dialog.dismiss() - if (!ActivityCompat.shouldShowRequestPermissionRationale(this, - Manifest.permission.CAMERA)) { - ActivityCompat.requestPermissions(this, permissions, RC_HANDLE_CAMERA_PERM) - } else { - finish() - } - } - builder.create().show() - } - - /** - * Creates and starts the camera. Note that this uses a higher resolution in comparison - * to other detection examples to enable the barcode detector to detect small barcodes - * at long distances. - * - * - * Suppressing InlinedApi since there is a check that the minimum version is met before using - * the constant. - */ - @Suppress("SameParameterValue") - @SuppressLint("InlinedApi") - private fun createCameraSource(useFlash: Boolean) { - // Creates and starts the camera. Note that this uses a higher resolution in comparison - // to other detection examples to enable the barcode detector to detect small barcodes - // at long distances. - val displayMetrics = DisplayMetrics() - windowManager.defaultDisplay.getMetrics(displayMetrics) - val height: Int = displayMetrics.heightPixels - val width: Int = displayMetrics.widthPixels - - barcodeDetector = createDocumentDetector() - var builder: BarcodeCameraSource.Builder = BarcodeCameraSource.Builder(applicationContext, barcodeDetector) - .setFacing(BarcodeCameraSource.CAMERA_FACING_BACK) - .setRequestedPreviewSize(width, height) - .setRequestedFps(60.0f) - - builder = builder.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE) - - barcodeCameraSource = builder - .setFlashMode(if (useFlash) Camera.Parameters.FLASH_MODE_TORCH else Camera.Parameters.FLASH_MODE_OFF) - .build() - } - - - private fun createDocumentDetector(): BarcodeDetector { - barcodeProcessor = LiveBarcodeProcessor() - return barcodeProcessor.getBarcodeDetector(applicationContext) { - if (it.feedback == BarcodeFeedback.Barcode) { - this.capturedbarcodeString = it.barcode - if (!capturing) { - runOnUiThread { - setTextFromState(CameraState.Capturing) - capturing = true - autoCancel.cancel() - object : CountDownTimer(timeInMsPerDigit.toLong(), 100) { - override fun onFinish() { - - doFinish() - } - - override fun onTick(millisUntilFinished: Long) { - //do nothing - } - }.start() - } - } - } - } - } - - private fun setOptions(options : AcuantCameraOptions) { - this.timeInMsPerDigit = options.timeInMsPerDigit - this.digitsToShow = options.digitsToShow - } - - private lateinit var mOrientationEventListener: OrientationEventListener - /** - * Restarts the camera. - */ - override fun onResume() { - super.onResume() - - if (mOrientationEventListener.canDetectOrientation()) { - mOrientationEventListener.enable() - } - startCameraSource() - } - - /** - * Stops the camera. - */ - override fun onPause() { - super.onPause() - if (mPreview != null) { - mPreview!!.stop() - } - mOrientationEventListener.disable() - } - - /** - * Releases the resources associated with the camera source, the associated detectors, and the - * rest of the processing pipeline. - */ - override fun onDestroy() { - super.onDestroy() - if (!permissionNotGranted) { - barcodeProcessor.stop() - } - if (mPreview != null) { - mPreview!!.release() - } - } - - /** - * Callback for the result from requesting permissions. This method - * is invoked for every call on [.requestPermissions]. - * - * - * **Note:** It is possible that the permissions request interaction - * with the user is interrupted. In this case you will receive empty permissions - * and results arrays which should be treated as a cancellation. - * - * - * @param requestCode The request code passed in [.requestPermissions]. - * @param permissions The requested permissions. Never null. - * @param grantResults The grant results for the corresponding permissions - * which is either [PackageManager.PERMISSION_GRANTED] - * or [PackageManager.PERMISSION_DENIED]. Never null. - * @see .requestPermissions - */ - override fun onRequestPermissionsResult(requestCode: Int, - permissions: Array, - grantResults: IntArray) { - if (requestCode != RC_HANDLE_CAMERA_PERM) { - Log.d(TAG, "Got unexpected permission result: $requestCode") - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - return - } - - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - Log.d(TAG, "Camera permission granted - initialize the camera source") - permissionNotGranted = false - createCameraSource(useFlash = false) - return - } - - Log.e(TAG, "Permission not granted: results len = " + grantResults.size + - " Result code = " + if (grantResults.isNotEmpty()) grantResults[0] else "(empty)") - - val listener = DialogInterface.OnClickListener { _, _ -> finish() } - - val builder = AlertDialog.Builder(this) - builder.setTitle(R.string.camera_load_error) - .setMessage(R.string.no_camera_permission) - .setPositiveButton(R.string.ok, listener) - .show() - } - - /** - * Starts or restarts the camera source, if it exists. If the camera source doesn't exist yet - * (e.g., because onResume was called before the camera source was created), this will be called - * again when the camera source is created. - */ - @Throws(SecurityException::class) - private fun startCameraSource() { - if (barcodeCameraSource != null && mPreview != null) { - try { - mPreview!!.start(barcodeCameraSource) - } catch (e: IOException) { - Log.e(TAG, "Unable to start camera source.", e) - barcodeCameraSource!!.release() - barcodeCameraSource = null - } - - } - } - - override fun onBackPressed() { - this@BarcodeCaptureActivity.finish() - } - - private fun setTextFromState(state: CameraState) { - when(state) { - CameraState.Capturing -> { - imageView.visibility = View.GONE - instructionView.background = defaultTextDrawable - instructionView.text = getString(R.string.acuant_camera_capturing_barcode) - instructionView.setTextColor(options.colorCapturing) - instructionView.textSize = 24f - } - else -> {//align - imageView.visibility = View.VISIBLE - instructionView.background = defaultTextDrawable - instructionView.text = getString(R.string.acuant_camera_align_barcode) - instructionView.setTextColor(options.colorHold) - instructionView.textSize = 24f - } - } - } - - private fun onChanged(@Suppress("UNUSED_PARAMETER") lastOrientation: Int, curOrientation: Int) { - - runOnUiThread { - if (curOrientation == ORIENTATION_LANDSCAPE_REVERSE) { - rotateView(instructionView, 0f, 270f) - - - } else if (curOrientation == ORIENTATION_LANDSCAPE) { - rotateView(instructionView, 360f, 90f) - - - } - } - - } - - private fun rotateView(view: View?, startDeg: Float, endDeg: Float) { - if (view != null) { - view.rotation = startDeg - view.animate().rotation(endDeg).start() - } - } - - - override fun onPictureTaken(data: ByteArray) { - val image = BitmapFactory.decodeByteArray(data, 0, data.size) - - if((image.width > image.height && this.lastOrientation == ORIENTATION_LANDSCAPE_REVERSE) || - (image.width < image.height && this.lastOrientation == ORIENTATION_LANDSCAPE)){ - val rotated = ImageSaver.rotateImage(image, 180f) - val stream = ByteArrayOutputStream() - rotated.compress(Bitmap.CompressFormat.JPEG, 100, stream) - rotated.recycle() - - } - - val file = File(this.cacheDir, "${UUID.randomUUID()}.jpg") - - this@BarcodeCaptureActivity.runOnUiThread { - val result = Intent() - result.putExtra(ACUANT_EXTRA_IMAGE_URL, file.absolutePath) - result.putExtra(ACUANT_EXTRA_PDF417_BARCODE, this.capturedbarcodeString) - setResult(AcuantCameraActivity.RESULT_SUCCESS_CODE, result) - finish() - } - } - - override fun onShutter() { - - Log.d("onShutter", "onShutter") - - } - - companion object { - private const val TAG = "Barcode-reader" - - // permission request codes need to be < 256 - private const val RC_HANDLE_CAMERA_PERM = 2 - private const val ORIENTATION_PORTRAIT_REVERSE = 4 - private const val ORIENTATION_LANDSCAPE_REVERSE = 3 - } -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeDetector.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeDetector.kt deleted file mode 100644 index f4439ed..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeDetector.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.acuant.acuantcamera.camera.barcode.cameraone - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.ImageFormat -import android.graphics.Matrix -import android.graphics.Rect -import android.graphics.YuvImage -import android.util.SparseArray - -import com.google.android.gms.vision.Detector -import com.google.android.gms.vision.Frame -import com.google.android.gms.vision.barcode.Barcode - -import java.io.ByteArrayOutputStream - -class BarcodeDetector(private val mDelegate: Detector) : Detector() { - var frame: Bitmap? = null - private set - - override fun detect(frame: Frame): SparseArray { - val yuvImage = YuvImage(frame.grayscaleImageData.array(), ImageFormat.NV21, frame.metadata.width, frame.metadata.height, null) - val byteArrayOutputStream = ByteArrayOutputStream() - yuvImage.compressToJpeg(Rect(0, 0, frame.metadata.width, frame.metadata.height), 100, byteArrayOutputStream) - val jpegArray = byteArrayOutputStream.toByteArray() - this.frame = BitmapFactory.decodeByteArray(jpegArray, 0, jpegArray.size) - return mDelegate.detect(frame) - } - - override fun isOperational(): Boolean { - return mDelegate.isOperational - } - - override fun setFocus(id: Int): Boolean { - return mDelegate.setFocus(id) - } -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeFeedback.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeFeedback.kt deleted file mode 100644 index de0e672..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeFeedback.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.acuant.acuantcamera.camera.barcode.cameraone - -enum class BarcodeFeedback { - NoDocument, - Barcode -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeGraphicTracker.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeGraphicTracker.kt deleted file mode 100644 index 674adbb..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeGraphicTracker.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.acuant.acuantcamera.camera.barcode.cameraone - -import android.support.annotation.UiThread -import com.google.android.gms.vision.Detector -import com.google.android.gms.vision.Tracker -import com.google.android.gms.vision.barcode.Barcode - -/** - * Generic tracker which is used for tracking or reading a barcode (and can really be used for - * any type of item). This is used to receive newly detected items, add a graphical representation - * to an overlay, update the graphics as the item changes, and remove the graphics when the item - * goes away. - */ -internal class BarcodeGraphicTracker(private val mBarcodeUpdateListener: BarcodeUpdateListener) : Tracker() { - - /** - * Consume the item instance detected from an Activity or Fragment level by implementing the - * BarcodeUpdateListener interface method onBarcodeDetected. - */ - interface BarcodeUpdateListener { - @UiThread - fun onBarcodeDetected(barcode: Barcode?) - } - - /** - * Start tracking the detected item instance within the item overlay. - */ - override fun onNewItem(id: Int, item: Barcode?) { - //mBarcodeUpdateListener.onBarcodeDetected(item); - } - - /** - * Update the position/characteristics of the item within the overlay. - */ - override fun onUpdate(detectionResults: Detector.Detections?, item: Barcode?) { - mBarcodeUpdateListener.onBarcodeDetected(item) - } - - /** - * Hide the graphic when the corresponding object was not detected. This can happen for - * intermediate frames temporarily, for example if the object was momentarily blocked from - * view. - */ - override fun onMissing(detectionResults: Detector.Detections?) { - - } - - /** - * Called when the item is assumed to be gone for good. Remove the graphic annotation from - * the overlay. - */ - override fun onDone() { - - } -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeTrackerFactory.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeTrackerFactory.kt deleted file mode 100644 index 9ccee24..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/BarcodeTrackerFactory.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.acuant.acuantcamera.camera.barcode.cameraone - -/* - * Copyright (C) The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import com.google.android.gms.vision.MultiProcessor -import com.google.android.gms.vision.Tracker -import com.google.android.gms.vision.barcode.Barcode - -/** - * Factory for creating a tracker and associated graphic to be associated with a new barcode. The - * multi-processor uses this factory to create barcode trackers as needed -- one for each barcode. - */ -internal class BarcodeTrackerFactory(private val listener: BarcodeGraphicTracker.BarcodeUpdateListener) : MultiProcessor.Factory { - - override fun create(barcode: Barcode): Tracker { - return BarcodeGraphicTracker(listener) - } - -} - diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/LiveBarcodeProcessor.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/LiveBarcodeProcessor.kt deleted file mode 100644 index 0569dd6..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/barcode/cameraone/LiveBarcodeProcessor.kt +++ /dev/null @@ -1,103 +0,0 @@ -package com.acuant.acuantcamera.camera.barcode.cameraone - -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.graphics.Bitmap -import android.util.Log -import android.util.Size -import com.google.android.gms.vision.MultiProcessor -import com.google.android.gms.vision.barcode.Barcode -import com.google.android.gms.vision.barcode.BarcodeDetector - - -class LiveBarcodeProcessor : BarcodeGraphicTracker.BarcodeUpdateListener { - private var feedbackListener: ((AcuantBarcodeFeedback) -> Unit)? = null - private var finishedCapturing = false - private var barcodeDetector: com.acuant.acuantcamera.camera.barcode.cameraone.BarcodeDetector? = null - - fun getBarcodeDetector(context: Context, listener: (AcuantBarcodeFeedback) -> Unit): com.acuant.acuantcamera.camera.barcode.cameraone.BarcodeDetector { - this.feedbackListener = listener - val barcodeDetectorDelagte = BarcodeDetector.Builder(context).setBarcodeFormats(Barcode.PDF417).build() - val barcodeFactory = BarcodeTrackerFactory(this) - val processor = MultiProcessor.Builder(barcodeFactory).build() - barcodeDetector = BarcodeDetector(barcodeDetectorDelagte) - barcodeDetector!!.setProcessor(processor) - - if (!barcodeDetector!!.isOperational) { - // Note: The first time that an app using the barcode or face API is installed on a - // device, GMS will download a native libraries to the device in order to do detection. - // Usually this completes before the app is run for the first time. But if that - // download has not yet completed, then the above call will not detect any barcodes - // and/or faces. - // - // isOperational() can be used to check if the required native libraries are currently - // available. The detectors will automatically become operational once the library - // downloads complete on device. - - // Check for low storage. If there is low storage, the native library will not be - // downloaded, so detection will not become operational. - val lowstorageFilter = IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW) - val hasLowStorage = context.registerReceiver(null, lowstorageFilter) != null - - if (hasLowStorage) { - Log.d("Live Doc Processor", "Received low storage warning") - } - } - provideFeedback() - - return barcodeDetector as com.acuant.acuantcamera.camera.barcode.cameraone.BarcodeDetector - } - - fun stop(){ - finishedCapturing = true - feedbackListener = null - thread?.join() - barcodeDetector?.release() - thread = null - barcodeDetector = null - // thread = null - } - - private var thread : Thread? = null - - private fun provideFeedback() { - thread = Thread(object : Runnable { - private var flag = true - private var processing = false - private var frame: Bitmap? = null - override fun run() { - while (flag) { - if (!processing) { - processing = true - frame = barcodeDetector?.frame - if (frame != null) { - val frameSize = Size(0, 0) - try { - var feedback: AcuantBarcodeFeedback? - - feedback = AcuantBarcodeFeedback(BarcodeFeedback.NoDocument, null, frameSize, null, 0) - - feedbackListener?.let { it(feedback) } - } catch (e: Exception) { - e.printStackTrace() - } - - } - processing = false - - } - flag = !finishedCapturing - } - } - }) - - thread!!.start() - } - - override fun onBarcodeDetected(barcode: Barcode?) { - if(barcode?.rawValue != null){ - feedbackListener?.let { it(AcuantBarcodeFeedback(BarcodeFeedback.Barcode, null, null, barcode.rawValue)) } - } - } -} 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 790c1a0..d33b845 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 @@ -1,324 +1,356 @@ package com.acuant.acuantcamera.camera.document -import android.graphics.* +import android.graphics.Color +import android.graphics.Point +import android.graphics.Rect import android.graphics.drawable.Drawable -import android.os.* -import android.support.v4.app.ActivityCompat -import android.util.Log -import android.util.Size -import android.util.SparseIntArray -import android.view.* +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import androidx.appcompat.content.res.AppCompatResources +import androidx.camera.core.ImageAnalysis import com.acuant.acuantcamera.R import com.acuant.acuantcamera.camera.AcuantBaseCameraFragment import com.acuant.acuantcamera.camera.AcuantCameraOptions -import com.acuant.acuantcamera.constant.* -import com.acuant.acuantcamera.detector.barcode.AcuantBarcodeDetector -import com.acuant.acuantcamera.detector.barcode.AcuantBarcodeDetectorHandler -import com.acuant.acuantcamera.detector.document.AcuantDocumentDetectorHandler -import com.acuant.acuantcamera.detector.document.AcuantDocumentDetector +import com.acuant.acuantcamera.interfaces.IAcuantSavedImage +import com.acuant.acuantcamera.databinding.DocumentFragmentUiBinding +import com.acuant.acuantcamera.detector.DocumentFrameAnalyzer +import com.acuant.acuantcamera.detector.DocumentState +import com.acuant.acuantcamera.helper.PointsUtils import com.acuant.acuantcamera.overlay.DocRectangleView -import kotlin.math.* +import com.acuant.acuantcommon.model.AcuantError +import kotlin.math.pow +import kotlin.math.roundToInt +import kotlin.math.sqrt -class AcuantDocCameraFragment : AcuantBaseCameraFragment(), - ActivityCompat.OnRequestPermissionsResultCallback, AcuantDocumentDetectorHandler, AcuantBarcodeDetectorHandler { +enum class DocumentCameraState { Align, MoveCloser, MoveBack, HoldSteady, CountingDown, Capturing } - private var holdTextDrawable: Drawable? = null +class AcuantDocCameraFragment: AcuantBaseCameraFragment() { - //private variables - private var currentDigit: Int = digitsToShow + private var rectangleView: DocRectangleView? = null + private var textView: TextView? = null + private var cameraUiContainerBinding: DocumentFragmentUiBinding? = null + private var latestBarcode: String? = null + private var capturingTextDrawable: Drawable? = null + private var defaultTextDrawable: Drawable? = null + private var holdTextDrawable: Drawable? = null + private var tapToCapture = false + private var currentDigit: Int = 2 private var lastTime: Long = System.currentTimeMillis() private var greenTransparent: Int = 0 private var firstThreeTimings: Array = arrayOf(-1, -1, -1) private var hasFinishedTest = false + private var oldPoints: Array? = null + private lateinit var frameAnalyzer: DocumentFrameAnalyzer - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - options = arguments?.getSerializable(ACUANT_EXTRA_CAMERA_OPTIONS) as AcuantCameraOptions? ?: AcuantCameraOptions.DocumentCameraOptionsBuilder().setAutoCapture(isAutoCapture).setAllowBox(isBorderEnabled).build() - if(options != null) { - isAutoCapture = options!!.autoCapture - isBorderEnabled = options!!.allowBox - } - - detectors = if (options?.useGMS == false) { - listOf(AcuantDocumentDetector(this)) - } else { - listOf(AcuantDocumentDetector(this), AcuantBarcodeDetector(this.activity!!.applicationContext, this)) - } - - capturingTextDrawable = activity!!.getDrawable(R.drawable.camera_text_config_capturing) - defaultTextDrawable = activity!!.getDrawable(R.drawable.camera_text_config_default) - holdTextDrawable = activity!!.getDrawable(R.drawable.camera_text_config_hold) - } - - private fun scalePoints(points: Array) : Array { - val scaledPoints = points.copyOf() - val scaledPointY = textureView.height.toFloat() / previewSize.width.toFloat() - val scaledPointX = textureView.width.toFloat() / previewSize.height.toFloat() - rectangleView.setWidth(textureView.width.toFloat()) - - points.apply { - this.forEach { - it.x = (it.x * scaledPointY).toInt() - it.y = (it.y * scaledPointX).toInt() - it.y -= pointYOffset - it.x += pointXOffset + private fun drawBorder(points: Array?) { + activity?.runOnUiThread { + if (points != null) { + rectangleView?.setAndDrawPoints(points) + } else { + rectangleView?.setAndDrawPoints(null) } } - - return scaledPoints } - private fun drawBorder(points: Array?){ - if(points != null) { - rectangleView.setAndDrawPoints(points) - } - else{ - rectangleView.setAndDrawPoints(null) - } - } + private fun onDocumentDetection(points: Array?, docState: DocumentState, cropDuration: Long) { + if (!capturing && !tapToCapture) { + activity?.runOnUiThread { - /** - * Callback from AcuantDocumentDetector's Detect() - */ - override fun onDetected(croppedImage: com.acuant.acuantcommon.model.Image?, cropDuration: Long) { - activity?.runOnUiThread { + if (!hasFinishedTest) { + rectangleView?.setViewFromState(DocumentCameraState.Align) + setTextFromState(DocumentCameraState.Align) + resetTimer() - if (!hasFinishedTest) { - for (i in firstThreeTimings.indices) { - if (firstThreeTimings[i] == (-1).toLong()) { - firstThreeTimings[i] = cropDuration - break - } - } - if (!firstThreeTimings.contains((-1).toLong())) { - hasFinishedTest = true - if (firstThreeTimings.min() ?: (TOO_SLOW_FOR_AUTO_THRESHOLD + 10) > TOO_SLOW_FOR_AUTO_THRESHOLD) { - isAutoCapture = false - setTapToCapture() + for (i in firstThreeTimings.indices) { + if (firstThreeTimings[i] == (-1).toLong()) { + firstThreeTimings[i] = cropDuration + break + } } - } - } - if (!hasFinishedTest) { - rectangleView.setViewFromState(CameraState.Align) - setTextFromState(CameraState.Align) - detectors.forEach { - if (it is AcuantDocumentDetector) { - it.isProcessing = false + if (!firstThreeTimings.contains((-1).toLong())) { + hasFinishedTest = true + if (firstThreeTimings.minOrNull() ?: (TOO_SLOW_FOR_AUTO_THRESHOLD + 10) > TOO_SLOW_FOR_AUTO_THRESHOLD) { + setTapToCapture() + } } } - } - if (hasFinishedTest && isAutoCapture) { - var detectedPoints = croppedImage?.points + if (hasFinishedTest && !tapToCapture) { + var detectedPoints = points + + val camContainer = fragmentCameraBinding?.root + val analyzerSize = imageAnalyzer?.resolutionInfo?.resolution + val previewSize = fragmentCameraBinding?.viewFinder + + val state = if (detectedPoints != null && detectedPoints.size == 4) { + detectedPoints = PointsUtils.fixPoints(PointsUtils.scalePoints(detectedPoints, camContainer, analyzerSize, previewSize, rectangleView)) + 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()) + var isContained = true + detectedPoints.forEach { + if (!view.contains(it.y, it.x)) { + isContained = false + } + } + if (isContained) { + docState + } else { + DocumentState.NoDocument + } + } else { + docState + } + } else { + docState + } - 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() - } - !isAcceptableDistance(detectedPoints, Size(textureView.width, textureView.height)) -> { - 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() - } + when (state) { + DocumentState.NoDocument -> { + rectangleView?.setViewFromState(DocumentCameraState.Align) + setTextFromState(DocumentCameraState.Align) + resetTimer() } - - 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) + DocumentState.TooClose -> { + rectangleView?.setViewFromState(DocumentCameraState.MoveBack) + setTextFromState(DocumentCameraState.MoveBack) + resetTimer() + } + DocumentState.TooFar -> { + rectangleView?.setViewFromState(DocumentCameraState.MoveCloser) + setTextFromState(DocumentCameraState.MoveCloser) + resetTimer() + } + else -> { // good document + if (System.currentTimeMillis() - lastTime > (acuantOptions.digitsToShow - currentDigit + 2) * acuantOptions.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(DocumentCameraState.HoldSteady) + setTextFromState(DocumentCameraState.HoldSteady) + resetTimer() + } + System.currentTimeMillis() - lastTime < acuantOptions.digitsToShow * acuantOptions.timeInMsPerDigit -> { + rectangleView?.setViewFromState(DocumentCameraState.CountingDown) + setTextFromState(DocumentCameraState.CountingDown) + } + else -> { + captureImage(object : IAcuantSavedImage { + override fun onSaved(uri: String) { + cameraActivityListener.onCameraDone(uri, latestBarcode) + } + + override fun onError(error: AcuantError) { + cameraActivityListener.onError(error) + } + + }, "AUTO") + rectangleView?.setViewFromState(DocumentCameraState.Capturing) + setTextFromState(DocumentCameraState.Capturing) + } } } } - } - oldPoints = detectedPoints - drawBorder(detectedPoints) - detectors.forEach { - if (it is AcuantDocumentDetector) { - it.isProcessing = false - } + oldPoints = detectedPoints + drawBorder(detectedPoints) } } } } - override fun setTextFromState(state: CameraState) { - - textView.visibility = View.VISIBLE - textView.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT - - when(state) { - CameraState.MoveCloser -> { - 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_move_closer) - textView.setTextColor(Color.WHITE) - } - CameraState.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_not_in_frame) - textView.setTextColor(Color.WHITE) - } - CameraState.Hold -> { - textView.background = holdTextDrawable - textView.layoutParams.width = context?.resources?.getDimension(R.dimen.cam_info_width)?.toInt() ?: 300 - textView.textSize = context?.resources?.getDimension(R.dimen.cam_doc_font_big) ?: 48f - textView.text = resources.getQuantityString(R.plurals.acuant_camera_timer, currentDigit, currentDigit) - textView.setTextColor(Color.RED) - } - CameraState.Steady -> { - 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_hold_steady) - textView.setTextColor(Color.WHITE) - } - CameraState.Capturing -> { - textView.background = capturingTextDrawable - textView.layoutParams.width = context?.resources?.getDimension(R.dimen.cam_info_width)?.toInt() ?: 300 - textView.textSize = context?.resources?.getDimension(R.dimen.cam_doc_font_big) ?: 48f - textView.text = resources.getQuantityString(R.plurals.acuant_camera_timer, currentDigit, currentDigit) - textView.setTextColor(Color.RED) - } - else -> {//align - 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_align) - textView.setTextColor(Color.WHITE) + private fun setTextFromState(state: DocumentCameraState) { + textView?.visibility = View.VISIBLE + if (!isAdded) + return + + val textView = this.textView + + if (textView != null) { + when (state) { + DocumentCameraState.MoveCloser -> { + 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_move_closer) + textView.setTextColor(Color.WHITE) + } + DocumentCameraState.MoveBack -> { + 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_not_in_frame) + textView.setTextColor(Color.WHITE) + } + DocumentCameraState.CountingDown -> { + textView.background = holdTextDrawable + textView.layoutParams?.width = + context?.resources?.getDimension(R.dimen.cam_info_width)?.toInt() ?: 300 + textView.textSize = + context?.resources?.getDimension(R.dimen.cam_doc_font_big) ?: 48f + textView.text = resources.getQuantityString( + R.plurals.acuant_camera_timer, + currentDigit, + currentDigit + ) + textView.setTextColor(Color.RED) + } + DocumentCameraState.HoldSteady -> { + 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_hold_steady) + textView.setTextColor(Color.WHITE) + } + DocumentCameraState.Capturing -> { + textView.background = capturingTextDrawable + textView.layoutParams?.width = + context?.resources?.getDimension(R.dimen.cam_info_width)?.toInt() ?: 300 + textView.textSize = + context?.resources?.getDimension(R.dimen.cam_doc_font_big) ?: 48f + textView.text = resources.getQuantityString( + R.plurals.acuant_camera_timer, + currentDigit, + currentDigit + ) + textView.setTextColor(Color.RED) + } + else -> {//align + 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_align) + textView.setTextColor(Color.WHITE) + } } } } - private fun resetTimer() { - lastTime = System.currentTimeMillis() - currentDigit = digitsToShow + override fun rotateUi(rotation: Int) { + textView?.rotation = rotation.toFloat() } - override fun onBarcodeDetected(barcode: String) { - this.barCodeString = barcode + private fun resetTimer() { + lastTime = System.currentTimeMillis() + currentDigit = acuantOptions.digitsToShow } - override fun onCreateView(inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? = inflater.inflate(R.layout.fragment_camera2_basic, container, false) - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - textureView = view.findViewById(R.id.texture) - rectangleView = view.findViewById(R.id.acu_doc_rectangle) as DocRectangleView - rectangleView.visibility = View.VISIBLE + super.onViewCreated(view, savedInstanceState) greenTransparent = getColorWithAlpha(Color.GREEN, .50f) - super.onViewCreated(view, savedInstanceState) - } + cameraUiContainerBinding?.root?.let { + fragmentCameraBinding!!.root.removeView(it) + } - @Suppress("SameParameterValue") - private fun getColorWithAlpha(color: Int, ratio: Float): Int { - return Color.argb((Color.alpha(color) * ratio).roundToInt(), Color.red(color), Color.green(color), Color.blue(color)) - } + cameraUiContainerBinding = DocumentFragmentUiBinding.inflate( + LayoutInflater.from(requireContext()), + fragmentCameraBinding!!.root, + true + ) - override fun setTapToCapture(){ - if(!isAutoCapture){ - setTextFromState(CameraState.Align) - textView.text = getString(R.string.acuant_camera_align_and_tap) - textView.layoutParams.width = context?.resources?.getDimension(R.dimen.cam_info_width)?.toInt() ?: 300 - textureView.setOnClickListener{ - activity?.runOnUiThread{ - this.isCapturing = true - textView.setBackgroundColor(greenTransparent) - textView.text = getString(R.string.acuant_camera_capturing) - lockFocus() - } - } - } - } + rectangleView = cameraUiContainerBinding?.documentRectangle + textView = cameraUiContainerBinding?.documentText - companion object { + setOptions(rectangleView) + currentDigit = acuantOptions.digitsToShow - internal const val TOO_SLOW_FOR_AUTO_THRESHOLD: Long = 130 + capturingTextDrawable = AppCompatResources.getDrawable(requireContext(), R.drawable.camera_text_config_capturing) + defaultTextDrawable = AppCompatResources.getDrawable(requireContext(), R.drawable.camera_text_config_default) + holdTextDrawable = AppCompatResources.getDrawable(requireContext(), R.drawable.camera_text_config_hold) - fun isAcceptableDistance(points: Array?, screenSize: Size): Boolean { - if (points != null) { - val shortSide = min(distance(points[0], points[1]), distance(points[0], points[3])) - val largeSide = max(distance(points[0], points[1]), distance(points[0], points[3])) - val screenShortSide = min(screenSize.width, screenSize.height).toFloat() - val screenLargeSide = max(screenSize.width, screenSize.height).toFloat() + } - if (shortSide > 0.75 * screenShortSide || largeSide > 0.75 * screenLargeSide) { - return true - } + private fun setTapToCapture() { + frameAnalyzer.disableDocumentDetection() + tapToCapture = true + setTextFromState(DocumentCameraState.Align) + textView?.text = getString(R.string.acuant_camera_align_and_tap) + textView?.layoutParams?.width = context?.resources?.getDimension(R.dimen.cam_info_width)?.toInt() ?: 300 + fragmentCameraBinding?.root?.setOnClickListener{ + activity?.runOnUiThread{ + 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 onError(error: AcuantError) { + cameraActivityListener.onError(error) + } + }, "TAP") } - return false } + } - private fun distance(pointA: Point, pointB: Point): Float { - return sqrt( (pointA.x - pointB.x).toFloat().pow(2) + (pointA.y - pointB.y).toFloat().pow(2)) + private fun onBarcodeDetection(barcode: String?) { + if (barcode != null) { + latestBarcode = barcode } + } - /** - * Conversion from screen rotation to JPEG orientation. - */ - private val ORIENTATIONS = SparseIntArray() - - init { - ORIENTATIONS.append(Surface.ROTATION_0, 90) - ORIENTATIONS.append(Surface.ROTATION_90, 0) - - ORIENTATIONS.append(Surface.ROTATION_180, 270) - ORIENTATIONS.append(Surface.ROTATION_270, 180) + override fun buildImageAnalyzer(screenAspectRatio: Int, rotation: Int) { + frameAnalyzer = DocumentFrameAnalyzer { points, state, barcode, detectTime -> + onBarcodeDetection(barcode) + onDocumentDetection(points, state, detectTime) + } + if (!acuantOptions.autoCapture) { + setTapToCapture() } + imageAnalyzer = ImageAnalysis.Builder() + .setTargetAspectRatio(screenAspectRatio) + .setTargetRotation(rotation) + .build() + .also { + it.setAnalyzer(cameraExecutor, frameAnalyzer) + } + } + + companion object { + internal const val TOO_SLOW_FOR_AUTO_THRESHOLD: Long = 200 /** * How much total x/y movement between frames is too much */ internal const val TOO_MUCH_MOVEMENT = 350 - @JvmStatic fun newInstance(): AcuantDocCameraFragment = AcuantDocCameraFragment() + @Suppress("SameParameterValue") + private fun getColorWithAlpha(color: Int, ratio: Float): Int { + return Color.argb((Color.alpha(color) * ratio).roundToInt(), Color.red(color), Color.green(color), Color.blue(color)) + } + + @JvmStatic fun newInstance(acuantOptions: AcuantCameraOptions): AcuantDocCameraFragment { + val frag = AcuantDocCameraFragment() + val args = Bundle() + args.putSerializable(INTERNAL_OPTIONS, acuantOptions) + frag.arguments = args + return frag + } } } \ No newline at end of file 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 deleted file mode 100644 index 9f61806..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/AcuantDocumentFeedback.kt +++ /dev/null @@ -1,31 +0,0 @@ -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, val detectTime: Long = -1) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as AcuantDocumentFeedback - - if (feedback != other.feedback) return false - if (point != null) { - if (other.point == null) return false - if (!point.contentEquals(other.point)) return false - } else if (other.point != null) return false - if (frameSize != other.frameSize) return false - if (barcode != other.barcode) return false - - return true - } - - override fun hashCode(): Int { - var result = feedback.hashCode() - result = 31 * result + (point?.contentHashCode() ?: 0) - result = 31 * result + (frameSize?.hashCode() ?: 0) - result = 31 * result + (barcode?.hashCode() ?: 0) - return result - } -} \ No newline at end of file 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 deleted file mode 100644 index 14e4786..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCameraSource.java +++ /dev/null @@ -1,1268 +0,0 @@ -package com.acuant.acuantcamera.camera.document.cameraone; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.ImageFormat; -import android.graphics.SurfaceTexture; -import android.hardware.Camera; -import android.hardware.Camera.CameraInfo; -import android.os.Build; -import android.os.SystemClock; -import android.support.annotation.Nullable; -import android.support.annotation.RequiresPermission; -import android.support.annotation.StringDef; -import android.util.Log; -import android.view.Surface; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.WindowManager; -import com.google.android.gms.common.images.Size; -import com.google.android.gms.vision.Detector; -import com.google.android.gms.vision.Frame; - -import java.io.IOException; -import java.lang.Thread.State; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -// Note: This requires Google Play Services 8.1 or higher, due to using indirect byte buffers for -// storing images. - -/** - * Manages the camera in conjunction with an underlying - * {@link Detector}. This receives preview frames from the camera at - * a specified rate, sending those frames to the detector as fast as it is able to process those - * frames. - *

- * This camera source makes a best effort to manage processing on preview frames as fast as - * possible, while at the same time minimizing lag. As such, frames may be dropped if the detector - * is unable to keep up with the rate of frames generated by the camera. You should use - * {@link Builder#setRequestedFps(float)} to specify a frame rate that works well with - * the capabilities of the camera hardware and the detector options that you have selected. If CPU - * utilization is higher than you'd like, then you may want to consider reducing FPS. If the camera - * preview or detector results are too "jerky", then you may want to consider increasing FPS. - *

- * The following Android permission is required to use the camera: - *

    - *
  • android.permissions.CAMERA
  • - *
- */ -@SuppressWarnings("deprecation") -public class DocumentCameraSource { - @SuppressLint("InlinedApi") - public static final int CAMERA_FACING_BACK = CameraInfo.CAMERA_FACING_BACK; - @SuppressLint("InlinedApi") - public static final int CAMERA_FACING_FRONT = CameraInfo.CAMERA_FACING_FRONT; - - private static final String TAG = "OpenCameraSource"; - - /** - * The dummy surface texture must be assigned a chosen name. Since we never use an OpenGL - * context, we can choose any ID we want here. - */ - private static final int DUMMY_TEXTURE_NAME = 100; - - /** - * If the absolute difference between a preview size aspect ratio and a picture size aspect - * ratio is less than this tolerance, they are considered to be the same aspect ratio. - */ - private static final float ASPECT_RATIO_TOLERANCE = 0.1f; - - @StringDef({ - Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE, - Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, - Camera.Parameters.FOCUS_MODE_AUTO, - Camera.Parameters.FOCUS_MODE_EDOF, - Camera.Parameters.FOCUS_MODE_FIXED, - Camera.Parameters.FOCUS_MODE_INFINITY, - Camera.Parameters.FOCUS_MODE_MACRO - }) - @Retention(RetentionPolicy.SOURCE) - private @interface FocusMode {} - - @StringDef({ - Camera.Parameters.FLASH_MODE_ON, - Camera.Parameters.FLASH_MODE_OFF, - Camera.Parameters.FLASH_MODE_AUTO, - Camera.Parameters.FLASH_MODE_RED_EYE, - Camera.Parameters.FLASH_MODE_TORCH - }) - @Retention(RetentionPolicy.SOURCE) - private @interface FlashMode {} - - private Context mContext; - - private final Object mCameraLock = new Object(); - - // Guarded by mCameraLock - private Camera mCamera; - - private int mFacing = CAMERA_FACING_BACK; - - - /** - * Rotation of the device, and thus the associated preview images captured from the device. - * See {@link Frame.Metadata#getRotation()}. - */ - private int mRotation; - - private Size mPreviewSize; - - // These values may be requested by the caller. Due to hardware limitations, we may need to - // select close, but not exactly the same values for these. - private float mRequestedFps = 30.0f; - private int mRequestedPreviewWidth = 1600; - private int mRequestedPreviewHeight = 1200; - - - private String mFocusMode = null; - private String mFlashMode = null; - - // These instances need to be held onto to avoid GC of their underlying resources. Even though - // these aren't used outside of the method that creates them, they still must have hard - // references maintained to them. - private SurfaceView mDummySurfaceView; - private SurfaceTexture mDummySurfaceTexture; - - /** - * Dedicated thread and associated runnable for calling into the detector with frames, as the - * frames become available from the camera. - */ - private Thread mProcessingThread; - private FrameProcessingRunnable mFrameProcessor; - - /** - * Map to convert between a byte array, received from the camera, and its associated byte - * buffer. We use byte buffers internally because this is a more efficient way to call into - * native code later (avoids a potential copy). - */ - private Map mBytesToByteBuffer = new HashMap<>(); - - //============================================================================================== - // Builder - //============================================================================================== - - /** - * Builder for configuring and creating an associated camera source. - */ - public static class Builder { - private final Detector mDetector; - private DocumentCameraSource documentCameraSource = new DocumentCameraSource(); - - /** - * Creates a camera source builder with the supplied context and detector. Camera preview - * images will be streamed to the associated detector upon starting the camera source. - */ - public Builder(Context context, Detector detector) { - if (context == null) { - throw new IllegalArgumentException("No context supplied."); - } - if (detector == null) { - throw new IllegalArgumentException("No detector supplied."); - } - - mDetector = detector; - documentCameraSource.mContext = context; - } - - /** - * Sets the requested frame rate in frames per second. If the exact requested value is not - * not available, the best matching available value is selected. Default: 30. - */ - public Builder setRequestedFps(float fps) { - if (fps <= 0) { - throw new IllegalArgumentException("Invalid fps: " + fps); - } - documentCameraSource.mRequestedFps = fps; - return this; - } - - public Builder setFocusMode(@FocusMode String mode) { - documentCameraSource.mFocusMode = mode; - return this; - } - - - - public Builder setFlashMode(@FlashMode String mode) { - documentCameraSource.mFlashMode = mode; - return this; - } - - /** - * Sets the desired width and height of the camera frames in pixels. If the exact desired - * values are not available options, the best matching available options are selected. - * Also, we try to select a preview size which corresponds to the aspect ratio of an - * associated full picture size, if applicable. Default: 1024x768. - */ - public Builder setRequestedPreviewSize(int width, int height) { - // Restrict the requested range to something within the realm of possibility. The - // choice of 1000000 is a bit arbitrary -- intended to be well beyond resolutions that - // devices can support. We bound this to avoid int overflow in the code later. - final int MAX = 1000000; - if ((width <= 0) || (width > MAX) || (height <= 0) || (height > MAX)) { - throw new IllegalArgumentException("Invalid preview size: " + width + "x" + height); - } - documentCameraSource.mRequestedPreviewWidth = width; - documentCameraSource.mRequestedPreviewHeight = height; - return this; - } - - /** - * Sets the camera to use (either {@link #CAMERA_FACING_BACK} or - * {@link #CAMERA_FACING_FRONT}). Default: back facing. - */ - public Builder setFacing(int facing) { - if ((facing != CAMERA_FACING_BACK) && (facing != CAMERA_FACING_FRONT)) { - throw new IllegalArgumentException("Invalid camera: " + facing); - } - documentCameraSource.mFacing = facing; - return this; - } - - /** - * Creates an instance of the camera source. - */ - public DocumentCameraSource build() { - documentCameraSource.mFrameProcessor = documentCameraSource.new FrameProcessingRunnable(mDetector); - return documentCameraSource; - } - } - - //============================================================================================== - // Bridge Functionality for the Camera1 API - //============================================================================================== - - /** - * Callback interface used to signal the moment of actual image capture. - */ - public interface ShutterCallback { - /** - * Called as near as possible to the moment when a photo is captured from the sensor. This - * is a good opportunity to play a shutter sound or give other feedback of camera operation. - * This may be some time after the photo was triggered, but some time before the actual data - * is available. - */ - void onShutter(); - } - - /** - * Callback interface used to supply image data from a photo capture. - */ - public interface PictureCallback { - /** - * Called when image data is available after a picture is taken. The format of the data - * is a jpeg binary. - */ - void onPictureTaken(byte[] data); - } - - /** - * Callback interface used to notify on completion of camera auto focus. - */ - public interface AutoFocusCallback { - /** - * Called when the camera auto focus completes. If the camera - * does not support auto-focus and autoFocus is called, - * onAutoFocus will be called immediately with a fake value of - * success set to true. - *

- * The auto-focus routine does not lock auto-exposure and auto-white - * balance after it completes. - * - * @param success true if focus was successful, false if otherwise - */ - void onAutoFocus(boolean success); - } - - /** - * Callback interface used to notify on auto focus start and stop. - *

- *

This is only supported in continuous autofocus modes -- {@link - * Camera.Parameters#FOCUS_MODE_CONTINUOUS_VIDEO} and {@link - * Camera.Parameters#FOCUS_MODE_CONTINUOUS_PICTURE}. Applications can show - * autofocus animation based on this.

- */ - public interface AutoFocusMoveCallback { - /** - * Called when the camera auto focus starts or stops. - * - * @param start true if focus starts to move, false if focus stops to move - */ - void onAutoFocusMoving(boolean start); - } - - //============================================================================================== - // Public - //============================================================================================== - - /** - * Stops the camera and releases the resources of the camera and underlying detector. - */ - public void release() { - synchronized (mCameraLock) { - stop(); - mFrameProcessor.release(); - - } - } - - /** - * Opens the camera and starts sending preview frames to the underlying detector. The preview - * frames are not displayed. - * - * @throws IOException if the camera's preview texture or display could not be initialized - */ - @RequiresPermission(Manifest.permission.CAMERA) - public DocumentCameraSource start() throws IOException { - synchronized (mCameraLock) { - if (mCamera != null) { - return this; - } - - mCamera = createCamera(); - - mDummySurfaceTexture = new SurfaceTexture(DUMMY_TEXTURE_NAME); - mCamera.setPreviewTexture(mDummySurfaceTexture); - mCamera.startPreview(); - - mProcessingThread = new Thread(mFrameProcessor); - mFrameProcessor.setActive(true); - mProcessingThread.start(); - } - return this; - } - - @RequiresPermission(Manifest.permission.CAMERA) - public DocumentCameraSource start(SurfaceHolder surfaceHolder, DocumentCameraSourcePreview preview) throws IOException { - synchronized (mCameraLock) { - if (mCamera != null) { - return this; - } - - try { - mCamera = createCamera(); - } catch (RuntimeException e) { - e.printStackTrace(); - throw new IOException(); - } - mCamera.setPreviewDisplay(surfaceHolder); - mCamera.startPreview(); - - if (preview != null) { - preview.requestLayout(); - } - - mProcessingThread = new Thread(mFrameProcessor); - mFrameProcessor.setActive(true); - mProcessingThread.start(); - - - } - return this; - } - - /** - * Closes the camera and stops sending frames to the underlying frame detector. - *

- * This camera source may be restarted again by calling {@link #start()} or - * {@link #start(SurfaceHolder, DocumentCameraSourcePreview)}. - *

- * Call {@link #release()} instead to completely shut down this camera source and release the - * resources of the underlying detector. - */ - public void stop() { - synchronized (mCameraLock) { - mFrameProcessor.setActive(false); - if (mProcessingThread != null) { - try { - // Wait for the thread to complete to ensure that we can't have multiple threads - // executing at the same time (i.e., which would happen if we called start too - // quickly after stop). - mProcessingThread.join(); - } catch (InterruptedException e) { - Log.d(TAG, "Frame processing thread interrupted on release."); - } - mProcessingThread = null; - } - - // clear the buffer to prevent oom exceptions - mBytesToByteBuffer.clear(); - - if (mCamera != null) { - mCamera.stopPreview(); - mCamera.setPreviewCallbackWithBuffer(null); - try { - mCamera.setPreviewTexture(null); - } catch (Exception e) { - Log.e(TAG, "Failed to clear camera preview: " + e); - } - mCamera.release(); - mCamera = null; - } - } - } - - /** - * Returns the preview size that is currently in use by the underlying camera. - */ - public Size getPreviewSize() { - return mPreviewSize; - } - - /** - * Returns the selected camera; one of {@link #CAMERA_FACING_BACK} or - * {@link #CAMERA_FACING_FRONT}. - */ - public int getCameraFacing() { - return mFacing; - } - - public int doZoom(float scale) { - synchronized (mCameraLock) { - if (mCamera == null) { - return 0; - } - int currentZoom = 0; - int maxZoom; - Camera.Parameters parameters = mCamera.getParameters(); - if (!parameters.isZoomSupported()) { - Log.w(TAG, "Zoom is not supported on this device"); - return currentZoom; - } - maxZoom = parameters.getMaxZoom(); - - currentZoom = parameters.getZoom() + 1; - float newZoom; - if (scale > 1) { - newZoom = currentZoom + scale * (maxZoom / 10); - } else { - newZoom = currentZoom * scale; - } - currentZoom = Math.round(newZoom) - 1; - if (currentZoom < 0) { - currentZoom = 0; - } else if (currentZoom > maxZoom) { - currentZoom = maxZoom; - } - parameters.setZoom(currentZoom); - mCamera.setParameters(parameters); - return currentZoom; - } - } - - /** - * Initiates taking a picture, which happens asynchronously. The camera source should have been - * activated previously with {@link #start()} or {@link #start(SurfaceHolder, DocumentCameraSourcePreview)}. The camera - * preview is suspended while the picture is being taken, but will resume once picture taking is - * done. - * - * @param shutter the callback for image capture moment, or null - * @param jpeg the callback for JPEG image data, or null - */ - public void takePicture(ShutterCallback shutter, PictureCallback jpeg) { - synchronized (mCameraLock) { - if (mCamera != null) { - PictureStartCallback startCallback = new PictureStartCallback(); - startCallback.mDelegate = shutter; - PictureDoneCallback doneCallback = new PictureDoneCallback(); - doneCallback.mDelegate = jpeg; - mCamera.takePicture(startCallback, null, null, doneCallback); - } - } - } - - /** - * Gets the current focus mode setting. - * - * @return current focus mode. This value is null if the camera is not yet created. Applications should call {@link - * #autoFocus(AutoFocusCallback)} to start the focus if focus - * mode is FOCUS_MODE_AUTO or FOCUS_MODE_MACRO. - * @see Camera.Parameters#FOCUS_MODE_AUTO - * @see Camera.Parameters#FOCUS_MODE_INFINITY - * @see Camera.Parameters#FOCUS_MODE_MACRO - * @see Camera.Parameters#FOCUS_MODE_FIXED - * @see Camera.Parameters#FOCUS_MODE_EDOF - * @see Camera.Parameters#FOCUS_MODE_CONTINUOUS_VIDEO - * @see Camera.Parameters#FOCUS_MODE_CONTINUOUS_PICTURE - */ - @Nullable - @FocusMode - public String getFocusMode() { - return mFocusMode; - } - - /** - * Sets the focus mode. - * - * @param mode the focus mode - * @return {@code true} if the focus mode is set, {@code false} otherwise - * @see #getFocusMode() - */ - public boolean setFocusMode(@FocusMode String mode) { - synchronized (mCameraLock) { - if (mCamera != null && mode != null) { - Camera.Parameters parameters = mCamera.getParameters(); - if (parameters.getSupportedFocusModes().contains(mode)) { - parameters.setFocusMode(mode); - mCamera.setParameters(parameters); - mFocusMode = mode; - return true; - } - } - - return false; - } - } - - /** - * Gets the current flash mode setting. - * - * @return current flash mode. null if flash mode setting is not - * supported or the camera is not yet created. - * @see Camera.Parameters#FLASH_MODE_OFF - * @see Camera.Parameters#FLASH_MODE_AUTO - * @see Camera.Parameters#FLASH_MODE_ON - * @see Camera.Parameters#FLASH_MODE_RED_EYE - * @see Camera.Parameters#FLASH_MODE_TORCH - */ - @Nullable - @FlashMode - public String getFlashMode() { - return mFlashMode; - } - - /** - * Sets the flash mode. - * - * @param mode flash mode. - * @return {@code true} if the flash mode is set, {@code false} otherwise - * @see #getFlashMode() - */ - public boolean setFlashMode(@FlashMode String mode) { - synchronized (mCameraLock) { - if (mCamera != null && mode != null) { - Camera.Parameters parameters = mCamera.getParameters(); - if (parameters.getSupportedFlashModes().contains(mode)) { - parameters.setFlashMode(mode); - mCamera.setParameters(parameters); - mFlashMode = mode; - return true; - } - } - - return false; - } - } - - /** - * Starts camera auto-focus and registers a callback function to run when - * the camera is focused. This method is only valid when preview is active - * (between {@link #start()} or {@link #start(SurfaceHolder, DocumentCameraSourcePreview)} and before {@link #stop()} or {@link #release()}). - *

- *

Callers should check - * {@link #getFocusMode()} to determine if - * this method should be called. If the camera does not support auto-focus, - * it is a no-op and {@link AutoFocusCallback#onAutoFocus(boolean)} - * callback will be called immediately. - *

- *

If the current flash mode is not - * {@link Camera.Parameters#FLASH_MODE_OFF}, flash may be - * fired during auto-focus, depending on the driver and camera hardware.

- * - * @param cb the callback to run - * @see #cancelAutoFocus() - */ - public void autoFocus(@Nullable AutoFocusCallback cb) { - synchronized (mCameraLock) { - if (mCamera != null) { - CameraAutoFocusCallback autoFocusCallback = null; - if (cb != null) { - autoFocusCallback = new CameraAutoFocusCallback(); - autoFocusCallback.mDelegate = cb; - } - mCamera.autoFocus(autoFocusCallback); - } - } - } - - /** - * Cancels any auto-focus function in progress. - * Whether or not auto-focus is currently in progress, - * this function will return the focus position to the default. - * If the camera does not support auto-focus, this is a no-op. - * - * @see #autoFocus(AutoFocusCallback) - */ - public void cancelAutoFocus() { - synchronized (mCameraLock) { - if (mCamera != null) { - mCamera.cancelAutoFocus(); - } - } - } - - /** - * Sets camera auto-focus move callback. - * - * @param cb the callback to run - * @return {@code true} if the operation is supported (i.e. from Jelly Bean), {@code false} otherwise - */ - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - public boolean setAutoFocusMoveCallback(@Nullable AutoFocusMoveCallback cb) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { - return false; - } - - synchronized (mCameraLock) { - if (mCamera != null) { - CameraAutoFocusMoveCallback autoFocusMoveCallback = null; - if (cb != null) { - autoFocusMoveCallback = new CameraAutoFocusMoveCallback(); - autoFocusMoveCallback.mDelegate = cb; - } - mCamera.setAutoFocusMoveCallback(autoFocusMoveCallback); - } - } - - return true; - } - - //============================================================================================== - // Private - //============================================================================================== - - /** - * Only allow creation via the builder class. - */ - private DocumentCameraSource() { - } - - /** - * Wraps the camera1 shutter callback so that the deprecated API isn't exposed. - */ - private class PictureStartCallback implements Camera.ShutterCallback { - private ShutterCallback mDelegate; - - @Override - public void onShutter() { - if (mDelegate != null) { - mDelegate.onShutter(); - } - } - } - - /** - * Wraps the final callback in the camera sequence, so that we can automatically turn the camera - * preview back on after the picture has been taken. - */ - private class PictureDoneCallback implements Camera.PictureCallback { - private PictureCallback mDelegate; - - @Override - public void onPictureTaken(byte[] data, Camera camera) { - if (mDelegate != null) { - mDelegate.onPictureTaken(data); - } - synchronized (mCameraLock) { - if (mCamera != null) { - mCamera.startPreview(); - } - } - } - } - - /** - * Wraps the camera1 auto focus callback so that the deprecated API isn't exposed. - */ - private class CameraAutoFocusCallback implements Camera.AutoFocusCallback { - private AutoFocusCallback mDelegate; - - @Override - public void onAutoFocus(boolean success, Camera camera) { - if (mDelegate != null) { - mDelegate.onAutoFocus(success); - } - } - } - - /** - * Wraps the camera1 auto focus move callback so that the deprecated API isn't exposed. - */ - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - private class CameraAutoFocusMoveCallback implements Camera.AutoFocusMoveCallback { - private AutoFocusMoveCallback mDelegate; - - @Override - public void onAutoFocusMoving(boolean start, Camera camera) { - if (mDelegate != null) { - mDelegate.onAutoFocusMoving(start); - } - } - } - - /** - * Opens the camera and applies the user settings. - * - * @throws RuntimeException if the method fails - */ - @SuppressLint("InlinedApi") - private Camera createCamera() { - int requestedCameraId = getIdForRequestedCamera(mFacing); - if (requestedCameraId == -1) { - throw new RuntimeException("Could not find requested camera."); - } - Camera camera = Camera.open(requestedCameraId); - - SizePair sizePair = selectSizePair(camera, mRequestedPreviewWidth, mRequestedPreviewHeight); - if (sizePair == null) { - throw new RuntimeException("Could not find suitable preview size."); - } - Size pictureSize = sizePair.pictureSize(); - mPreviewSize = sizePair.previewSize(); - - int[] previewFpsRange = selectPreviewFpsRange(camera, mRequestedFps); - if (previewFpsRange == null) { - throw new RuntimeException("Could not find suitable preview frames per second range."); - } - - Camera.Parameters parameters = camera.getParameters(); - - if (pictureSize != null) { - parameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight()); - } - - parameters.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); - parameters.setPreviewFpsRange( - previewFpsRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX], - previewFpsRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]); - parameters.setPreviewFormat(ImageFormat.NV21); - - setRotation(camera, parameters, requestedCameraId); - - if (mFocusMode != null) { - if (parameters.getSupportedFocusModes().contains( - mFocusMode)) { - parameters.setFocusMode(mFocusMode); - } else { - Log.i(TAG, "Camera focus mode: " + mFocusMode + " is not supported on this device."); - } - } - - // setting mFocusMode to the one set in the params - mFocusMode = parameters.getFocusMode(); - - if (mFlashMode != null) { - if (parameters.getSupportedFlashModes() != null) { - if (parameters.getSupportedFlashModes().contains( - mFlashMode)) { - parameters.setFlashMode(mFlashMode); - } else { - Log.i(TAG, "Camera flash mode: " + mFlashMode + " is not supported on this device."); - } - } - } - - // setting mFlashMode to the one set in the params - mFlashMode = parameters.getFlashMode(); - - camera.setParameters(parameters); - - - - // Four frame buffers are needed for working with the camera: - // - // one for the frame that is currently being executed upon in doing detection - // one for the next pending frame to process immediately upon completing detection - // two for the frames that the camera uses to populate future preview images - camera.setPreviewCallbackWithBuffer(new CameraPreviewCallback()); - camera.addCallbackBuffer(createPreviewBuffer(mPreviewSize)); - camera.addCallbackBuffer(createPreviewBuffer(mPreviewSize)); - camera.addCallbackBuffer(createPreviewBuffer(mPreviewSize)); - camera.addCallbackBuffer(createPreviewBuffer(mPreviewSize)); - - return camera; - } - - /** - * Gets the id for the camera specified by the direction it is facing. Returns -1 if no such - * camera was found. - * - * @param facing the desired camera (front-facing or rear-facing) - */ - private static int getIdForRequestedCamera(int facing) { - CameraInfo cameraInfo = new CameraInfo(); - for (int i = 0; i < Camera.getNumberOfCameras(); ++i) { - Camera.getCameraInfo(i, cameraInfo); - if (cameraInfo.facing == facing) { - return i; - } - } - return -1; - } - -// private static SizePair selectSizePair(Camera camera, int desiredWidth, int desiredHeight) { -// List validPreviewSizes = generateValidPreviewSizeList(camera); -// // The method for selecting the best size is to minimize the sum of the differences between -// // the desired values and the actual values for width and height. This is certainly not the -// // only way to select the best size, but it provides a decent tradeoff between using the -// // closest aspect ratio vs. using the closest pixel area. -// SizePair selectedPair = null; -// int maxArea = Integer.MIN_VALUE; -// int maxPict = Integer.MIN_VALUE; -// int area = Integer.MIN_VALUE; -// for (SizePair sizePair : validPreviewSizes) { -// Size size_pict = sizePair.pictureSize(); -// int newArea = size_pict.getWidth()*size_pict.getHeight(); -// if (newArea > maxPict) { -// maxPict = newArea; -// maxArea = Integer.MIN_VALUE; -// Size size = sizePair.previewSize(); -// area = size.getWidth()*size.getHeight(); -// if (maxArea < area) { -// selectedPair = sizePair; -// maxArea = area; -// } -// } -// else if (newArea == maxPict) { -// Size size = sizePair.previewSize(); -// area = size.getWidth()*size.getHeight(); -// if (maxArea < area) { -// selectedPair = sizePair; -// maxArea = area; -// } -// } -// } -// return selectedPair; -// } - - /** - * Selects the most suitable preview and picture size, given the desired width and height. - *

- * Even though we may only need the preview size, it's necessary to find both the preview - * size and the picture size of the camera together, because these need to have the same aspect - * ratio. On some hardware, if you would only set the preview size, you will get a distorted - * image. - * - * @param camera the camera to select a preview size from - * @param desiredWidth the desired width of the camera preview frames - * @param desiredHeight the desired height of the camera preview frames - * @return the selected preview and picture size pair - */ - private static SizePair selectSizePair(Camera camera, int desiredWidth, int desiredHeight) { - List validPreviewSizes = generateValidPreviewSizeList(camera); - long expectedSize = desiredWidth*desiredHeight; - int desiredShortSide = Math.min(desiredHeight, desiredWidth); - int desiredLongSide = Math.max(desiredHeight, desiredWidth); - // The method for selecting the best size is to minimize the sum of the differences between - // the desired values and the actual values for width and height. This is certainly not the - // only way to select the best size, but it provides a decent tradeoff between using the - // closest aspect ratio vs. using the closest pixel area. - SizePair selectedPair = null; - int minDiff = Integer.MAX_VALUE; - for (SizePair sizePair : validPreviewSizes) { - Size size = sizePair.previewSize(); - - - int currentShortSide = Math.min(size.getWidth(), size.getHeight()); - int currentLongSide = Math.max(size.getWidth(), size.getHeight()); - float change = Math.min(Math.min((float) desiredShortSide / currentShortSide, (float) desiredLongSide / currentLongSide), 2f); - currentLongSide *= change; - currentShortSide *= change; - - - int diff = Math.abs(currentShortSide - desiredShortSide) + - Math.abs(currentLongSide - desiredLongSide); - if ((!(currentLongSide > desiredLongSide) && !(currentShortSide > desiredShortSide)) && diff < minDiff && (sizePair.pictureSize().getWidth()*sizePair.pictureSize().getHeight() >= expectedSize * 0.6f)) { - selectedPair = sizePair; - minDiff = diff; - } - } - - if (selectedPair != null) { - return selectedPair; - } else if (validPreviewSizes.size() > 0){ - return validPreviewSizes.get(0); - } else { - return null; - } - } - -// private static SizePair selectSizePair(Camera camera, int desiredWidth, int desiredHeight) { -// List validPreviewSizes = generateValidPreviewSizeList(camera); -// -// int desiredSize = desiredHeight * desiredWidth; -// float sizeRatio = 0.6f; -// -// SizePair selectedPair = null; -// -// do { -// float desiredRatio = (float) Math.max(desiredWidth, desiredHeight) / Math.min(desiredWidth, desiredHeight); -// float minDiff = Integer.MAX_VALUE; -// for (SizePair sizePair : validPreviewSizes) { -// Size size = sizePair.previewSize(); -// float diff = Math.abs((float) Math.max(size.getWidth(), size.getHeight()) / Math.min(size.getWidth(), size.getHeight()) - desiredRatio); -// if (diff < minDiff && size.getWidth()*size.getHeight() > desiredSize*sizeRatio) { -// selectedPair = sizePair; -// minDiff = diff; -// } -// } -// sizeRatio -= 0.1f; -// } while (selectedPair == null); -// -// return selectedPair; -// } - - /** - * Stores a preview size and a corresponding same-aspect-ratio picture size. To avoid distorted - * preview images on some devices, the picture size must be set to a size that is the same - * aspect ratio as the preview size or the preview may end up being distorted. If the picture - * size is null, then there is no picture size with the same aspect ratio as the preview size. - */ - private static class SizePair { - private Size mPreview; - private Size mPicture; - - public SizePair(Camera.Size previewSize, - Camera.Size pictureSize) { - mPreview = new Size(previewSize.width, previewSize.height); - if (pictureSize != null) { - mPicture = new Size(pictureSize.width, pictureSize.height); - } - } - - public Size previewSize() { - return mPreview; - } - - @SuppressWarnings("unused") - public Size pictureSize() { - return mPicture; - } - } - - /** - * Generates a list of acceptable preview sizes. Preview sizes are not acceptable if there is - * not a corresponding picture size of the same aspect ratio. If there is a corresponding - * picture size of the same aspect ratio, the picture size is paired up with the preview size. - *

- * This is necessary because even if we don't use still pictures, the still picture size must be - * set to a size that is the same aspect ratio as the preview size we choose. Otherwise, the - * preview images may be distorted on some devices. - */ - private static List generateValidPreviewSizeList(Camera camera) { - Camera.Parameters parameters = camera.getParameters(); - List supportedPreviewSizes = - parameters.getSupportedPreviewSizes(); - List supportedPictureSizes = - parameters.getSupportedPictureSizes(); - List validPreviewSizes = new ArrayList<>(); - for (Camera.Size previewSize : supportedPreviewSizes) { - float previewAspectRatio = (float) previewSize.width / (float) previewSize.height; - - // By looping through the picture sizes in order, we favor the higher resolutions. - // We choose the highest resolution in order to support taking the full resolution - // picture later. - for (Camera.Size pictureSize : supportedPictureSizes) { - float pictureAspectRatio = (float) pictureSize.width / (float) pictureSize.height; - if (Math.abs(previewAspectRatio - pictureAspectRatio) < ASPECT_RATIO_TOLERANCE) { - validPreviewSizes.add(new SizePair(previewSize, pictureSize)); - break; - } - } - } - - // If there are no picture sizes with the same aspect ratio as any preview sizes, allow all - // of the preview sizes and hope that the camera can handle it. Probably unlikely, but we - // still account for it. - if (validPreviewSizes.size() == 0) { - Log.w(TAG, "No preview sizes have a corresponding same-aspect-ratio picture size"); - for (Camera.Size previewSize : supportedPreviewSizes) { - // The null picture size will let us know that we shouldn't set a picture size. - validPreviewSizes.add(new SizePair(previewSize, null)); - } - } - - return validPreviewSizes; - } - - /** - * Selects the most suitable preview frames per second range, given the desired frames per - * second. - * - * @param camera the camera to select a frames per second range from - * @param desiredPreviewFps the desired frames per second for the camera preview frames - * @return the selected preview frames per second range - */ - private int[] selectPreviewFpsRange(Camera camera, float desiredPreviewFps) { - // The camera API uses integers scaled by a factor of 1000 instead of floating-point frame - // rates. - int desiredPreviewFpsScaled = (int) (desiredPreviewFps * 1000.0f); - - // The method for selecting the best range is to minimize the sum of the differences between - // the desired value and the upper and lower bounds of the range. This may select a range - // that the desired value is outside of, but this is often preferred. For example, if the - // desired frame rate is 29.97, the range (30, 30) is probably more desirable than the - // range (15, 30). - int[] selectedFpsRange = null; - int minDiff = Integer.MAX_VALUE; - List previewFpsRangeList = camera.getParameters().getSupportedPreviewFpsRange(); - for (int[] range : previewFpsRangeList) { - int deltaMin = desiredPreviewFpsScaled - range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX]; - int deltaMax = desiredPreviewFpsScaled - range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]; - int diff = Math.abs(deltaMin) + Math.abs(deltaMax); - if (diff < minDiff) { - selectedFpsRange = range; - minDiff = diff; - } - } - return selectedFpsRange; - } - - /** - * Calculates the correct rotation for the given camera id and sets the rotation in the - * parameters. It also sets the camera's display orientation and rotation. - * - * @param parameters the camera parameters for which to set the rotation - * @param cameraId the camera id to set rotation based on - */ - private void setRotation(Camera camera, Camera.Parameters parameters, int cameraId) { - WindowManager windowManager = - (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - int degrees = 0; - int rotation = windowManager.getDefaultDisplay().getRotation(); - switch (rotation) { - case Surface.ROTATION_0: - degrees = 0; - break; - case Surface.ROTATION_90: - degrees = 90; - break; - case Surface.ROTATION_180: - degrees = 180; - break; - case Surface.ROTATION_270: - degrees = 270; - break; - default: - Log.e(TAG, "Bad rotation value: " + rotation); - } - - CameraInfo cameraInfo = new CameraInfo(); - Camera.getCameraInfo(cameraId, cameraInfo); - - int angle; - int displayAngle; - if (cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT) { - angle = (cameraInfo.orientation + degrees) % 360; - displayAngle = (360 - angle) % 360; // compensate for it being mirrored - } else { // back-facing - angle = (cameraInfo.orientation - degrees + 360) % 360; - displayAngle = angle; - } - - // This corresponds to the rotation constants in {@link Frame}. - mRotation = angle / 90; - - camera.setDisplayOrientation(displayAngle); - parameters.setRotation(angle); - } - - /** - * Creates one buffer for the camera preview callback. The size of the buffer is based off of - * the camera preview size and the format of the camera image. - * - * @return a new preview buffer of the appropriate size for the current camera settings - */ - private byte[] createPreviewBuffer(Size previewSize) { - int bitsPerPixel = ImageFormat.getBitsPerPixel(ImageFormat.NV21); - long sizeInBits = previewSize.getHeight() * previewSize.getWidth() * bitsPerPixel; - int bufferSize = (int) Math.ceil(sizeInBits / 8.0d) + 1; - - // - // NOTICE: This code only works when using play services v. 8.1 or higher. - // - - // Creating the byte array this way and wrapping it, as opposed to using .allocate(), - // should guarantee that there will be an array to work with. - byte[] byteArray = new byte[bufferSize]; - ByteBuffer buffer = ByteBuffer.wrap(byteArray); - if (!buffer.hasArray() || (buffer.array() != byteArray)) { - // I don't think that this will ever happen. But if it does, then we wouldn't be - // passing the preview content to the underlying detector later. - throw new IllegalStateException("Failed to create valid buffer for camera source."); - } - - mBytesToByteBuffer.put(byteArray, buffer); - return byteArray; - } - - //============================================================================================== - // Frame processing - //============================================================================================== - - /** - * Called when the camera has a new preview frame. - */ - private class CameraPreviewCallback implements Camera.PreviewCallback { - @Override - public void onPreviewFrame(byte[] data, Camera camera) { - mFrameProcessor.setNextFrame(data, camera); - } - } - - /** - * This runnable controls access to the underlying receiver, calling it to process frames when - * available from the camera. This is designed to run detection on frames as fast as possible - * (i.e., without unnecessary context switching or waiting on the next frame). - *

- * While detection is running on a frame, new frames may be received from the camera. As these - * frames come in, the most recent frame is held onto as pending. As soon as detection and its - * associated processing are done for the previous frame, detection on the mostly recently - * received frame will immediately start on the same thread. - */ - private class FrameProcessingRunnable implements Runnable { - private Detector mDetector; - private long mStartTimeMillis = SystemClock.elapsedRealtime(); - - // This lock guards all of the member variables below. - private final Object mLock = new Object(); - private boolean mActive = true; - - // These pending variables hold the state associated with the new frame awaiting processing. - private long mPendingTimeMillis; - private int mPendingFrameId = 0; - private ByteBuffer mPendingFrameData; - - FrameProcessingRunnable(Detector detector) { - mDetector = detector; - } - - /** - * Releases the underlying receiver. This is only safe to do after the associated thread - * has completed, which is managed in camera source's release method above. - */ - @SuppressLint("Assert") - void release() { - assert mProcessingThread == null || mProcessingThread.getState() == State.TERMINATED; - mDetector.release(); - mDetector = null; - } - - /** - * Marks the runnable as active/not active. Signals any blocked threads to continue. - */ - void setActive(boolean active) { - synchronized (mLock) { - mActive = active; - mLock.notifyAll(); - } - } - - /** - * Sets the frame data received from the camera. This adds the previous unused frame buffer - * (if present) back to the camera, and keeps a pending reference to the frame data for - * future use. - */ - void setNextFrame(byte[] data, Camera camera) { - synchronized (mLock) { - if (mPendingFrameData != null) { - camera.addCallbackBuffer(mPendingFrameData.array()); - mPendingFrameData = null; - } - - if (!mBytesToByteBuffer.containsKey(data)) { - Log.d(TAG, - "Skipping frame. Could not find ByteBuffer associated with the image " + - "data from the camera."); - return; - } - - // Timestamp and frame ID are maintained here, which will give downstream code some - // idea of the timing of frames received and when frames were dropped along the way. - mPendingTimeMillis = SystemClock.elapsedRealtime() - mStartTimeMillis; - mPendingFrameId++; - mPendingFrameData = mBytesToByteBuffer.get(data); - - // Notify the processor thread if it is waiting on the next frame (see below). - mLock.notifyAll(); - } - } - - /** - * As long as the processing thread is active, this executes detection on frames - * continuously. The next pending frame is either immediately available or hasn't been - * received yet. Once it is available, we transfer the frame info to local variables and - * run detection on that frame. It immediately loops back for the next frame without - * pausing. - *

- * If detection takes longer than the time in between new frames from the camera, this will - * mean that this loop will run without ever waiting on a frame, avoiding any context - * switching or frame acquisition time latency. - *

- * If you find that this is using more CPU than you'd like, you should probably decrease the - * FPS setting above to allow for some idle time in between frames. - */ - @Override - public void run() { - Frame outputFrame; - ByteBuffer data; - - while (true) { - synchronized (mLock) { - while (mActive && (mPendingFrameData == null)) { - try { - // Wait for the next frame to be received from the camera, since we - // don't have it yet. - mLock.wait(); - } catch (InterruptedException e) { - Log.d(TAG, "Frame processing loop terminated.", e); - return; - } - } - - if (!mActive) { - // Exit the loop once this camera source is stopped or released. We check - // this here, immediately after the wait() above, to handle the case where - // setActive(false) had been called, triggering the termination of this - // loop. - return; - } - - outputFrame = new Frame.Builder() - .setImageData(mPendingFrameData, mPreviewSize.getWidth(), - mPreviewSize.getHeight(), ImageFormat.NV21) - .setId(mPendingFrameId) - .setTimestampMillis(mPendingTimeMillis) - .setRotation(mRotation) - .build(); - - // Hold onto the frame data locally, so that we can use this for detection - // below. We need to clear mPendingFrameData to ensure that this buffer isn't - // recycled back to the camera before we are done using that data. - data = mPendingFrameData; - mPendingFrameData = null; - } - - // The code below needs to run outside of synchronization, because this will allow - // the camera to add pending frame(s) while we are running detection on the current - // frame. - - try { - mDetector.receiveFrame(outputFrame); - } catch (Throwable t) { - Log.e(TAG, "Exception thrown from receiver.", t); - } finally { - mCamera.addCallbackBuffer(data.array()); - } - } - } - } -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCameraSourcePreview.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCameraSourcePreview.kt deleted file mode 100644 index 493c97a..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCameraSourcePreview.kt +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.acuant.acuantcamera.camera.document.cameraone - - -import android.Manifest -import android.content.Context -import android.content.res.Configuration -import android.support.annotation.RequiresPermission -import android.support.v7.app.AlertDialog -import android.util.AttributeSet -import android.util.Log -import android.view.SurfaceHolder -import android.view.SurfaceView -import android.view.ViewGroup -import android.widget.RelativeLayout -import com.acuant.acuantcamera.R -import java.io.IOException - -class DocumentCameraSourcePreview(private val mContext: Context, attrs: AttributeSet?) : ViewGroup(mContext, attrs) { - var mSurfaceView: SurfaceView - var pointXOffset: Int = 0 - var pointYOffset: Int = 0 - private var mStartRequested: Boolean = false - private var mSurfaceAvailable: Boolean = false - private var documentCameraSource: DocumentCameraSource? = null - - private val isPortraitMode: Boolean - get() { - val orientation = mContext.resources.configuration.orientation - if (orientation == Configuration.ORIENTATION_LANDSCAPE) { - return false - } - if (orientation == Configuration.ORIENTATION_PORTRAIT) { - return true - } - - Log.d(TAG, "isPortraitMode returning false by default") - return false - } - - init { - mStartRequested = false - mSurfaceAvailable = false - - val previewParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, - RelativeLayout.LayoutParams.MATCH_PARENT) - previewParams.addRule(RelativeLayout.CENTER_IN_PARENT) - mSurfaceView = SurfaceView(mContext) - mSurfaceView.layoutParams = previewParams - mSurfaceView.holder.addCallback(SurfaceCallback()) - addView(mSurfaceView) - } - - @RequiresPermission(Manifest.permission.CAMERA) - @Throws(IOException::class, SecurityException::class) - fun start(documentCameraSource: DocumentCameraSource?) { - if (documentCameraSource == null) { - stop() - } - - this.documentCameraSource = documentCameraSource - - if (this.documentCameraSource != null) { - mStartRequested = true - startIfReady() - } - } - - fun stop() { - if (documentCameraSource != null) { - documentCameraSource!!.stop() - } - } - - fun release() { - if (documentCameraSource != null) { - documentCameraSource!!.release() - documentCameraSource = null - } - } - - @RequiresPermission(Manifest.permission.CAMERA) - @Throws(IOException::class, SecurityException::class) - private fun startIfReady() { - if (mStartRequested && mSurfaceAvailable) { - documentCameraSource!!.start(mSurfaceView.holder, this) - mStartRequested = false - } - } - - private inner class SurfaceCallback : SurfaceHolder.Callback { - - override fun surfaceCreated(surface: SurfaceHolder) { - mSurfaceAvailable = true - try { - startIfReady() - } catch (se: SecurityException) { - Log.e(TAG, "Do not have permission to start the camera", se) - } catch (e: IOException) { - Log.e(TAG, "Could not start camera source.", e) - val dialogBuilder = AlertDialog.Builder(context) - dialogBuilder.setMessage(R.string.error_starting_cam) - .setPositiveButton(R.string.ok - ) { dialog, _ -> - dialog.dismiss() - - } - dialogBuilder.create().show() - } - - } - - override fun surfaceDestroyed(surface: SurfaceHolder) { - mSurfaceAvailable = false - } - - override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {} - - } - - override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - - val viewWidth = right - left - val viewHeight = bottom - top - - var previewWidth = viewWidth - var previewHeight = viewHeight - if (documentCameraSource != null) { - val size = documentCameraSource?.previewSize - if (size != null) { - previewWidth = size.width - previewHeight = size.height - } - } - - // Swap width and height sizes when in portrait, since it will be rotated 90 degrees - if (isPortraitMode) { - val tmp = previewWidth - previewWidth = previewHeight - previewHeight = tmp - } - val childWidth: Int - val childHeight: Int - val childXOffset: Int - val childYOffset: Int - val widthRatio = viewWidth.toFloat() / previewWidth.toFloat() - val heightRatio = viewHeight.toFloat() / previewHeight.toFloat() - - if (widthRatio < heightRatio) { - childWidth = viewWidth - childHeight = (previewHeight.toFloat() * widthRatio).toInt() - } else { - childWidth = (previewWidth.toFloat() * heightRatio).toInt() - childHeight = viewHeight - } - - childXOffset = (childWidth - viewWidth) / 2 - childYOffset = (childHeight - viewHeight) / 2 - - pointXOffset = childXOffset - pointYOffset = childYOffset - - for (i in 0 until childCount) { - getChildAt(i).layout( - -1 * childXOffset, -1 * childYOffset, - childWidth - childXOffset, childHeight - childYOffset) - getChildAt(i).requestLayout() - } - try { - startIfReady() - } catch (e: IOException) { - e.printStackTrace() - } catch (se: SecurityException) { - se.printStackTrace() - } - } - - companion object { - private const val TAG = "CameraSourcePreview" - } -} 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 deleted file mode 100644 index bce7ae1..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentCaptureActivity.kt +++ /dev/null @@ -1,687 +0,0 @@ -@file:Suppress("DEPRECATION") - -package com.acuant.acuantcamera.camera.document.cameraone - -import android.Manifest -import android.annotation.SuppressLint -import android.content.DialogInterface -import android.content.Intent -import android.content.pm.ActivityInfo -import android.content.pm.PackageManager -import android.content.res.Configuration.ORIENTATION_LANDSCAPE -import android.content.res.Configuration.ORIENTATION_PORTRAIT -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Color -import android.graphics.Point -import android.graphics.drawable.Drawable -import android.hardware.Camera -import android.os.Bundle -import android.support.v4.app.ActivityCompat -import android.support.v7.app.AlertDialog -import android.support.v7.app.AppCompatActivity -import android.util.DisplayMetrics -import android.util.Log -import android.util.Size -import android.view.* -import android.widget.RelativeLayout -import android.widget.TextView -import com.acuant.acuantcamera.R -import com.acuant.acuantcamera.camera.AcuantBaseCameraFragment.CameraState -import com.acuant.acuantcamera.camera.AcuantCameraActivity -import com.acuant.acuantcamera.camera.AcuantCameraOptions -import com.acuant.acuantcamera.camera.document.AcuantDocCameraFragment -import com.acuant.acuantcamera.constant.ACUANT_EXTRA_CAMERA_OPTIONS -import com.acuant.acuantcamera.constant.ACUANT_EXTRA_IMAGE_URL -import com.acuant.acuantcamera.constant.ACUANT_EXTRA_PDF417_BARCODE -import com.acuant.acuantcamera.detector.ImageSaver -import com.acuant.acuantcamera.overlay.DocRectangleView -import java.io.ByteArrayOutputStream -import java.io.File -import java.io.FileOutputStream -import java.io.IOException -import java.util.* -import kotlin.math.pow -import kotlin.math.roundToInt -import kotlin.math.sqrt - -/** - * Activity for the multi-tracker app. This app detects barcodes and displays the value with the - * rear facing camera. During detection overlay graphics are drawn to indicate the position, - * size, and ID of each barcode. - */ -class DocumentCaptureActivity : AppCompatActivity(), DocumentCameraSource.PictureCallback, DocumentCameraSource.ShutterCallback { - private var documentCameraSource: DocumentCameraSource? = null - private var mPreview: DocumentCameraSourcePreview? = null - private var capturing = false - private var autoCapture = false - private lateinit var documentProcessor: LiveDocumentProcessor - private var permissionNotGranted = false - private var documentDetector: DocumentDetector? = null - - private lateinit var instructionView: TextView - - private var capturedbarcodeString: String? = null - - private lateinit var rectangleView: DocRectangleView - - private var capturingTextDrawable: Drawable? = null - private var defaultTextDrawable: Drawable? = null - private var holdTextDrawable: Drawable? = null - private var currentDigit: Int = 2 - private var lastTime: Long = System.currentTimeMillis() - private var timeInMsPerDigit: Int = 800 - private var oldPoints: Array? = null - 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. - */ - public override fun onCreate(icicle: Bundle?) { - super.onCreate(icicle) - requestWindowFeature(Window.FEATURE_NO_TITLE) - requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY) - window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN) - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - supportActionBar?.title = "" - supportActionBar?.hide() - - capturingTextDrawable = this.getDrawable(R.drawable.camera_text_config_capturing) - defaultTextDrawable = this.getDrawable(R.drawable.camera_text_config_default) - holdTextDrawable = this.getDrawable(R.drawable.camera_text_config_hold) - - setContentView(R.layout.activity_acu_document_camera) - - parent = findViewById(R.id.cam1_doc_parent) - mPreview = findViewById(R.id.cam1_doc_preview) - rectangleView = findViewById(R.id.cam1_doc_rect) - instructionView = findViewById(R.id.cam1_doc_text) - - setTextFromState(CameraState.Align) - - // Check for the camera permission before accessing the camera. If the - // permission is not granted yet, request permission. - val rc = ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) - if (rc == PackageManager.PERMISSION_GRANTED) { - createCameraSource(useFlash = false) - } else { - requestCameraPermission() - permissionNotGranted = true - } - - setOptions(intent.getSerializableExtra(ACUANT_EXTRA_CAMERA_OPTIONS) as AcuantCameraOptions? ?: AcuantCameraOptions()) - - setTapToCapture(parent) - - displaySize = Point() - this.windowManager.defaultDisplay.getSize(displaySize) - - mOrientationEventListener = object : OrientationEventListener(this.applicationContext) { - override fun onOrientationChanged(orientation: Int) { - if (orientation < 0) { - return // Flip screen, Not take account - } - val curOrientation: Int = when { - orientation <= 45 -> { - ORIENTATION_PORTRAIT - } - orientation <= 135 -> { - ORIENTATION_LANDSCAPE_REVERSE - } - orientation <= 225 -> { - ORIENTATION_PORTRAIT_REVERSE - } - orientation <= 315 -> { - ORIENTATION_LANDSCAPE - } - else -> { - ORIENTATION_PORTRAIT - } - } - if (curOrientation != lastOrientation) { - onChanged(lastOrientation, curOrientation) - lastOrientation = curOrientation - } - } - } - } - - 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) { - capture() - } - } - } - - private fun capture() { - documentCameraSource?.takePicture(this@DocumentCaptureActivity, this@DocumentCaptureActivity) - } - - @Suppress("SameParameterValue") - private fun getColorWithAlpha(color: Int, ratio: Float): Int { - return Color.argb((Color.alpha(color) * ratio).roundToInt(), Color.red(color), Color.green(color), Color.blue(color)) - } - - /** - * Handles the requesting of the camera permission. This includes - * showing a "Snackbar" message of why the permission is needed then - * sending the request. - */ - private fun requestCameraPermission() { - Log.w(TAG, "Camera permission is not granted. Requesting permission") - - val permissions = arrayOf(Manifest.permission.CAMERA) - - val builder = AlertDialog.Builder(this) - builder.setMessage(R.string.cam_perm_request_text) - .setOnCancelListener { - if (!ActivityCompat.shouldShowRequestPermissionRationale(this, - Manifest.permission.CAMERA)) { - ActivityCompat.requestPermissions(this, permissions, RC_HANDLE_CAMERA_PERM) - } else { - finish() - } - } - .setPositiveButton(R.string.ok - ) { dialog, _ -> - dialog.dismiss() - if (!ActivityCompat.shouldShowRequestPermissionRationale(this, - Manifest.permission.CAMERA)) { - ActivityCompat.requestPermissions(this, permissions, RC_HANDLE_CAMERA_PERM) - } else { - finish() - } - } - builder.create().show() - } - - /** - * Creates and starts the camera. Note that this uses a higher resolution in comparison - * to other detection examples to enable the barcode detector to detect small barcodes - * at long distances. - * - * - * Suppressing InlinedApi since there is a check that the minimum version is met before using - * the constant. - */ - @Suppress("SameParameterValue") - @SuppressLint("InlinedApi") - private fun createCameraSource(useFlash: Boolean) { - // Creates and starts the camera. Note that this uses a higher resolution in comparison - // to other detection examples to enable the barcode detector to detect small barcodes - // at long distances. - val displayMetrics = DisplayMetrics() - windowManager.defaultDisplay.getMetrics(displayMetrics) - val height: Int = displayMetrics.heightPixels - val width: Int = displayMetrics.widthPixels - - documentDetector = createDocumentDetector() - var builder: DocumentCameraSource.Builder = DocumentCameraSource.Builder(applicationContext, documentDetector) - .setFacing(DocumentCameraSource.CAMERA_FACING_BACK) - .setRequestedPreviewSize(width, height) - .setRequestedFps(60.0f) - - builder = builder.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE) - - documentCameraSource = builder - .setFlashMode(if (useFlash) Camera.Parameters.FLASH_MODE_TORCH else Camera.Parameters.FLASH_MODE_OFF) - .build() - } - - private fun isDocumentInPreviewFrame(points: Array, frameSize: Size): Boolean { - val minOffset = 0.0025f - if (mPreview != null) { - - //this causes issue on some devices where the scale is very large. - //not needed in method, is performed in the scale for the screen match - //val scaleX = mPreview!!.mSurfaceView.width / frameSize.height.toFloat() - //val scaleY = mPreview!!.mSurfaceView.height / frameSize.width.toFloat() - - val startY = frameSize.height * minOffset// * scaleY - val startX = frameSize.width * minOffset// * scaleX - val endY = frameSize.height * (1 - minOffset)// * scaleY - val endX = frameSize.width * (1 - minOffset)// * scaleX - - for (point in points) { - //Log.d("wtf", "Point: ${point.x.toString().padEnd(5)}, ${point.y.toString().padEnd(5)} Start: ${startX.toString().padEnd(5)}, ${startY.toString().padEnd(5)} End: ${endX.toString().padEnd(5)}, ${endY.toString().padEnd(5)}") - if (point.x < startX || point.y < startY || point.x > endX || point.y > endY) { - //Log.d("wtf", "Failed here") - return false - } - } - - return true - } - - return false - } - - private fun scalePoints(points: Array, frameSize: Size): Array { - val scaledPoints = points.copyOf() - if (mPreview != null) { - val scaleX = mPreview!!.mSurfaceView.width / frameSize.height.toFloat() - val scaleY = mPreview!!.mSurfaceView.height / frameSize.width.toFloat() - rectangleView.setWidth(mPreview!!.mSurfaceView.width.toFloat()) - - scaledPoints.forEach { - it.x = (it.x * scaleY).toInt() - it.y = (it.y * scaleX).toInt() - it.y += mPreview?.pointXOffset ?: 0 - it.x -= mPreview?.pointYOffset ?: 0 - } - } - - return scaledPoints - } - - private fun createDocumentDetector(): DocumentDetector { - documentProcessor = LiveDocumentProcessor() - return documentProcessor.getBarcodeDetector(applicationContext) { - if (it.feedback == DocumentFeedback.Barcode) { - this.capturedbarcodeString = it.barcode - } else { - runOnUiThread { - - 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 - if (points != null && points.size == 4) { - points = fixPoints(points) - - if (!isDocumentInPreviewFrame(points, frameSize)) { - feedback = DocumentFeedback.NotInFrame - } - - //this scale is done to match on screen display better, the frame checks should happen before - points = scalePoints(points, frameSize) - } - - - 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 -> { - - 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() - } - } - - 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) - } - - } - } - } - } - - } - - private fun setOptions(options : AcuantCameraOptions) { - this.timeInMsPerDigit = options.timeInMsPerDigit - this.digitsToShow = options.digitsToShow - autoCapture = options.autoCapture - rectangleView.allowBox = options.allowBox - rectangleView.bracketLengthInHorizontal = options.bracketLengthInHorizontal - rectangleView.bracketLengthInVertical = options.bracketLengthInVertical - rectangleView.defaultBracketMarginHeight = options.defaultBracketMarginHeight - rectangleView.defaultBracketMarginWidth = options.defaultBracketMarginWidth - rectangleView.paintColorCapturing = options.colorCapturing - rectangleView.paintColorHold = options.colorHold - rectangleView.paintColorBracketAlign = options.colorBracketAlign - rectangleView.paintColorBracketCapturing = options.colorBracketCapturing - rectangleView.paintColorBracketCloser = options.colorBracketCloser - rectangleView.paintColorBracketHold = options.colorBracketHold - rectangleView.cardRatio = options.cardRatio - } - - private lateinit var mOrientationEventListener: OrientationEventListener - /** - * Restarts the camera. - */ - override fun onResume() { - super.onResume() - - if (mOrientationEventListener.canDetectOrientation()) { - mOrientationEventListener.enable() - } - startCameraSource() - } - - /** - * Stops the camera. - */ - override fun onPause() { - super.onPause() - if (mPreview != null) { - mPreview!!.stop() - } - mOrientationEventListener.disable() - } - - /** - * Releases the resources associated with the camera source, the associated detectors, and the - * rest of the processing pipeline. - */ - override fun onDestroy() { - super.onDestroy() - if (!permissionNotGranted) { - documentProcessor.stop() - } - if (mPreview != null) { - mPreview!!.release() - } - } - - /** - * Callback for the result from requesting permissions. This method - * is invoked for every call on [.requestPermissions]. - * - * - * **Note:** It is possible that the permissions request interaction - * with the user is interrupted. In this case you will receive empty permissions - * and results arrays which should be treated as a cancellation. - * - * - * @param requestCode The request code passed in [.requestPermissions]. - * @param permissions The requested permissions. Never null. - * @param grantResults The grant results for the corresponding permissions - * which is either [PackageManager.PERMISSION_GRANTED] - * or [PackageManager.PERMISSION_DENIED]. Never null. - * @see .requestPermissions - */ - override fun onRequestPermissionsResult(requestCode: Int, - permissions: Array, - grantResults: IntArray) { - if (requestCode != RC_HANDLE_CAMERA_PERM) { - Log.d(TAG, "Got unexpected permission result: $requestCode") - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - return - } - - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - Log.d(TAG, "Camera permission granted - initialize the camera source") - permissionNotGranted = false - createCameraSource(useFlash = false) - return - } - - Log.e(TAG, "Permission not granted: results len = " + grantResults.size + - " Result code = " + if (grantResults.isNotEmpty()) grantResults[0] else "(empty)") - - val listener = DialogInterface.OnClickListener { _, _ -> finish() } - - val builder = AlertDialog.Builder(this) - builder.setTitle(R.string.camera_load_error) - .setMessage(R.string.no_camera_permission) - .setPositiveButton(R.string.ok, listener) - .show() - } - - /** - * Starts or restarts the camera source, if it exists. If the camera source doesn't exist yet - * (e.g., because onResume was called before the camera source was created), this will be called - * again when the camera source is created. - */ - @Throws(SecurityException::class) - private fun startCameraSource() { - if (documentCameraSource != null && mPreview != null) { - try { - mPreview!!.start(documentCameraSource) - } catch (e: IOException) { - Log.e(TAG, "Unable to start camera source.", e) - documentCameraSource!!.release() - documentCameraSource = null - } - - } - } - - override fun onBackPressed() { - this@DocumentCaptureActivity.finish() - } - - private fun setTextFromState(state: CameraState) { - when(state) { - CameraState.MoveCloser -> { - instructionView.background = defaultTextDrawable - instructionView.text = getString(R.string.acuant_camera_move_closer) - instructionView.setTextColor(Color.WHITE) - instructionView.textSize = 24f - } - CameraState.NotInFrame -> { - instructionView.background = defaultTextDrawable - instructionView.text = getString(R.string.acuant_camera_not_in_frame) - instructionView.setTextColor(Color.WHITE) - instructionView.textSize = 24f - } - CameraState.Hold -> { - instructionView.background = holdTextDrawable - instructionView.text = resources.getQuantityString(R.plurals.acuant_camera_timer, currentDigit, currentDigit) - instructionView.setTextColor(Color.RED) - instructionView.textSize = 48f - } - CameraState.Steady -> { - instructionView.background = defaultTextDrawable - instructionView.text = getString(R.string.acuant_camera_hold_steady) - instructionView.setTextColor(Color.WHITE) - instructionView.textSize = 24f - } - CameraState.Capturing -> { - instructionView.background = capturingTextDrawable - instructionView.text = resources.getQuantityString(R.plurals.acuant_camera_timer, currentDigit, currentDigit) - instructionView.setTextColor(Color.RED) - instructionView.textSize = 48f - } - else -> {//align - instructionView.background = defaultTextDrawable - instructionView.text = getString(R.string.acuant_camera_align) - instructionView.setTextColor(Color.WHITE) - instructionView.textSize = 24f - } - } - } - - private fun resetTimer() { - lastTime = System.currentTimeMillis() - currentDigit = digitsToShow - } - - private fun fixPoints(points: Array): Array { - val fixedPoints = points.copyOf() - if (fixedPoints.size == 4) { - if (fixedPoints[0].y > fixedPoints[2].y && fixedPoints[0].x < fixedPoints[2].x) { - //rotate 2 - var tmp = fixedPoints[0] - fixedPoints[0] = fixedPoints[2] - fixedPoints[2] = tmp - - tmp = fixedPoints[1] - fixedPoints[1] = fixedPoints[3] - fixedPoints[3] = tmp - - } else if (fixedPoints[0].y > fixedPoints[2].y && fixedPoints[0].x > fixedPoints[2].x) { - //rotate 3 - val tmp = fixedPoints[0] - fixedPoints[0] = fixedPoints[1] - fixedPoints[1] = fixedPoints[2] - fixedPoints[2] = fixedPoints[3] - fixedPoints[3] = tmp - - } else if (fixedPoints[0].y < fixedPoints[2].y && fixedPoints[0].x < fixedPoints[2].x) { - //rotate 1 - val tmp = fixedPoints[0] - fixedPoints[0] = fixedPoints[3] - fixedPoints[3] = fixedPoints[2] - fixedPoints[2] = fixedPoints[1] - fixedPoints[1] = tmp - - } - } - return fixedPoints - } - - private fun onChanged(@Suppress("UNUSED_PARAMETER") lastOrientation: Int, curOrientation: Int) { - - runOnUiThread { - if (curOrientation == ORIENTATION_LANDSCAPE_REVERSE) { - rotateView(instructionView, 0f, 270f) - - - } else if (curOrientation == ORIENTATION_LANDSCAPE) { - rotateView(instructionView, 360f, 90f) - - - } - } - - } - - private fun rotateView(view: View?, startDeg: Float, endDeg: Float) { - if (view != null) { - view.rotation = startDeg - view.animate().rotation(endDeg).start() - } - } - - - override fun onPictureTaken(data: ByteArray) { - var updated = data - val image = BitmapFactory.decodeByteArray(data, 0, data.size) - - if((image.width > image.height && this.lastOrientation == ORIENTATION_LANDSCAPE_REVERSE) || - (image.width < image.height && this.lastOrientation == ORIENTATION_LANDSCAPE)){ - val rotated = ImageSaver.rotateImage(image, 180f) - val stream = ByteArrayOutputStream() - rotated.compress(Bitmap.CompressFormat.JPEG, 100, stream) - rotated.recycle() - updated = stream.toByteArray() - - } - - val file = File(this.cacheDir, "${UUID.randomUUID()}.jpg") - saveFile(file, updated) - - this@DocumentCaptureActivity.runOnUiThread { - val result = Intent() - result.putExtra(ACUANT_EXTRA_IMAGE_URL, file.absolutePath) - result.putExtra(ACUANT_EXTRA_PDF417_BARCODE, this.capturedbarcodeString) - setResult(AcuantCameraActivity.RESULT_SUCCESS_CODE, result) - finish() - } - } - - private fun saveFile(file: File, data: ByteArray) { - var output: FileOutputStream? = null - try { - output = FileOutputStream(file).apply { write(data) } - val captureType = if (autoCapture) "AUTO" else "TAP" - ImageSaver.addExif(file, captureType) - } catch (e: IOException) { - e.printStackTrace() - } finally { - output?.let { - try { - it.close() - } catch (e: IOException) { - e.printStackTrace() - } - } - } - } - - override fun onShutter() { - - Log.d("onShutter", "onShutter") - - } - - companion object { - private const val TAG = "Barcode-reader" - - // permission request codes need to be < 256 - private const val RC_HANDLE_CAMERA_PERM = 2 - private const val ORIENTATION_PORTRAIT_REVERSE = 4 - private const val ORIENTATION_LANDSCAPE_REVERSE = 3 - } -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentDetector.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentDetector.kt deleted file mode 100644 index 1332baa..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentDetector.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.acuant.acuantcamera.camera.document.cameraone - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.ImageFormat -import android.graphics.Matrix -import android.graphics.Rect -import android.graphics.YuvImage -import android.util.SparseArray - -import com.google.android.gms.vision.Detector -import com.google.android.gms.vision.Frame -import com.google.android.gms.vision.barcode.Barcode - -import java.io.ByteArrayOutputStream - -class DocumentDetector(private val mDelegate: Detector) : Detector() { - var frame: Bitmap? = null - private set - - override fun detect(frame: Frame): SparseArray { - val yuvImage = YuvImage(frame.grayscaleImageData.array(), ImageFormat.NV21, frame.metadata.width, frame.metadata.height, null) - val byteArrayOutputStream = ByteArrayOutputStream() - yuvImage.compressToJpeg(Rect(0, 0, frame.metadata.width, frame.metadata.height), 100, byteArrayOutputStream) - val jpegArray = byteArrayOutputStream.toByteArray() - this.frame = BitmapFactory.decodeByteArray(jpegArray, 0, jpegArray.size) - return mDelegate.detect(frame) - } - - override fun isOperational(): Boolean { - return mDelegate.isOperational - } - - override fun setFocus(id: Int): Boolean { - return mDelegate.setFocus(id) - } - - private fun rotateBitmap(source: Bitmap, angle: Float): Bitmap { - val matrix = Matrix() - matrix.postRotate(angle) - return Bitmap.createBitmap(source, 0, 0, source.width, source.height, matrix, true) - } - -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentFeedback.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentFeedback.kt deleted file mode 100644 index 8ae0393..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentFeedback.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.acuant.acuantcamera.camera.document.cameraone - -enum class DocumentFeedback { - NoDocument, - SmallDocument, - BadDocument, - GoodDocument, - Barcode, - NotInFrame -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentGraphicTracker.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentGraphicTracker.kt deleted file mode 100644 index 5b6738d..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentGraphicTracker.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.acuant.acuantcamera.camera.document.cameraone - -import android.support.annotation.UiThread -import com.google.android.gms.vision.Detector -import com.google.android.gms.vision.Tracker -import com.google.android.gms.vision.barcode.Barcode - -/** - * Generic tracker which is used for tracking or reading a barcode (and can really be used for - * any type of item). This is used to receive newly detected items, add a graphical representation - * to an overlay, update the graphics as the item changes, and remove the graphics when the item - * goes away. - */ -internal class DocumentGraphicTracker(private val mBarcodeUpdateListener: BarcodeUpdateListener) : Tracker() { - - /** - * Consume the item instance detected from an Activity or Fragment level by implementing the - * BarcodeUpdateListener interface method onBarcodeDetected. - */ - interface BarcodeUpdateListener { - @UiThread - fun onBarcodeDetected(barcode: Barcode?) - } - - /** - * Start tracking the detected item instance within the item overlay. - */ - override fun onNewItem(id: Int, item: Barcode?) { - //mBarcodeUpdateListener.onBarcodeDetected(item); - } - - /** - * Update the position/characteristics of the item within the overlay. - */ - override fun onUpdate(detectionResults: Detector.Detections?, item: Barcode?) { - mBarcodeUpdateListener.onBarcodeDetected(item) - } - - /** - * Hide the graphic when the corresponding object was not detected. This can happen for - * intermediate frames temporarily, for example if the object was momentarily blocked from - * view. - */ - override fun onMissing(detectionResults: Detector.Detections?) { - - } - - /** - * Called when the item is assumed to be gone for good. Remove the graphic annotation from - * the overlay. - */ - override fun onDone() { - - } -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentTrackerFactory.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentTrackerFactory.kt deleted file mode 100644 index f5505be..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/DocumentTrackerFactory.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.acuant.acuantcamera.camera.document.cameraone - -/* - * Copyright (C) The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import com.google.android.gms.vision.MultiProcessor -import com.google.android.gms.vision.Tracker -import com.google.android.gms.vision.barcode.Barcode - -/** - * Factory for creating a tracker and associated graphic to be associated with a new barcode. The - * multi-processor uses this factory to create barcode trackers as needed -- one for each barcode. - */ -internal class DocumentTrackerFactory(private val listener: DocumentGraphicTracker.BarcodeUpdateListener) : MultiProcessor.Factory { - - override fun create(barcode: Barcode): Tracker { - return DocumentGraphicTracker(listener) - } - -} - 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 deleted file mode 100644 index b89a355..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/document/cameraone/LiveDocumentProcessor.kt +++ /dev/null @@ -1,118 +0,0 @@ -package com.acuant.acuantcamera.camera.document.cameraone - -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.graphics.Bitmap -import android.util.Log -import android.util.Size -import com.acuant.acuantcamera.camera.document.AcuantDocCameraFragment -import com.acuant.acuantimagepreparation.AcuantImagePreparation -import com.acuant.acuantimagepreparation.model.DetectData -import com.google.android.gms.vision.MultiProcessor -import com.google.android.gms.vision.barcode.Barcode -import com.google.android.gms.vision.barcode.BarcodeDetector - - -class LiveDocumentProcessor : DocumentGraphicTracker.BarcodeUpdateListener { - private var feedbackListener: ((AcuantDocumentFeedback) -> Unit)? = null - private var finishedCapturing = false - private var documentDetector: DocumentDetector? = null - - fun getBarcodeDetector(context: Context, listener: (AcuantDocumentFeedback) -> Unit): DocumentDetector { - this.feedbackListener = listener - val barcodeDetectorDelagte = BarcodeDetector.Builder(context).setBarcodeFormats(Barcode.PDF417).build() - val barcodeFactory = DocumentTrackerFactory(this) - val processor = MultiProcessor.Builder(barcodeFactory).build() - documentDetector = DocumentDetector(barcodeDetectorDelagte) - documentDetector!!.setProcessor(processor) - - if (!documentDetector!!.isOperational) { - // Note: The first time that an app using the barcode or face API is installed on a - // device, GMS will download a native libraries to the device in order to do detection. - // Usually this completes before the app is run for the first time. But if that - // download has not yet completed, then the above call will not detect any barcodes - // and/or faces. - // - // isOperational() can be used to check if the required native libraries are currently - // available. The detectors will automatically become operational once the library - // downloads complete on device. - - // Check for low storage. If there is low storage, the native library will not be - // downloaded, so detection will not become operational. - val lowstorageFilter = IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW) - val hasLowStorage = context.registerReceiver(null, lowstorageFilter) != null - - if (hasLowStorage) { - Log.d("Live Doc Processor", "Received low storage warning") - } - } - provideFeedback() - - return documentDetector as DocumentDetector - } - - fun stop(){ - finishedCapturing = true - feedbackListener = null - thread?.join() - documentDetector?.release() - thread = null - documentDetector = null - // thread = null - } - - private var thread : Thread? = null - - private fun provideFeedback() { - thread = Thread(object : Runnable { - private var flag = true - private var processing = false - private var frame: Bitmap? = null - override fun run() { - while (flag) { - if (!processing) { - processing = true - frame = documentDetector?.frame - if (frame != null) { - val data = DetectData(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? - - feedback = if (acuantImage.points == null || acuantImage.dpi < 20) { - AcuantDocumentFeedback(DocumentFeedback.NoDocument, null, frameSize, null, elapsed) - } else if (!AcuantDocCameraFragment.isAcceptableDistance(acuantImage.points, frameSize)) { - AcuantDocumentFeedback(DocumentFeedback.SmallDocument, acuantImage.points, frameSize, null, elapsed) - } else if (!acuantImage.isCorrectAspectRatio) { - AcuantDocumentFeedback(DocumentFeedback.BadDocument, acuantImage.points, frameSize, null, elapsed) - } else { - AcuantDocumentFeedback(DocumentFeedback.GoodDocument, acuantImage.points, frameSize, null, elapsed) - } - - feedbackListener?.let { it(feedback) } - } catch (e: Exception) { - e.printStackTrace() - } - - } - processing = false - - } - flag = !finishedCapturing - } - } - }) - - thread!!.start() - } - - override fun onBarcodeDetected(barcode: Barcode?) { - if(barcode?.rawValue != null){ - feedbackListener?.let { it(AcuantDocumentFeedback(DocumentFeedback.Barcode, null, null, barcode.rawValue)) } - } - } -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/AcuantMrzCameraFragment.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/AcuantMrzCameraFragment.kt index e8c6927..a962b2e 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/AcuantMrzCameraFragment.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/AcuantMrzCameraFragment.kt @@ -1,149 +1,63 @@ package com.acuant.acuantcamera.camera.mrz -import android.content.Context import android.graphics.Color import android.graphics.Point +import android.graphics.drawable.Drawable import android.os.Bundle import android.os.Handler import android.os.Looper -import android.support.v4.app.ActivityCompat +import android.util.Size +import android.view.LayoutInflater import android.view.View import android.widget.ImageView import android.widget.TextView +import androidx.appcompat.content.res.AppCompatResources +import androidx.camera.core.ImageAnalysis import com.acuant.acuantcamera.R import com.acuant.acuantcamera.camera.AcuantBaseCameraFragment import com.acuant.acuantcamera.camera.AcuantCameraOptions -import com.acuant.acuantcamera.camera.ICameraActivityFinish -import com.acuant.acuantcamera.constant.ACUANT_EXTRA_CAMERA_OPTIONS -import com.acuant.acuantcamera.detector.ocr.AcuantOcrDetector -import com.acuant.acuantcamera.detector.ocr.AcuantOcrDetectorHandler -import com.acuant.acuantcamera.helper.MrzParser +import com.acuant.acuantcamera.databinding.MrzFragmentUiBinding +import com.acuant.acuantcamera.detector.MrzFrameAnalyzer +import com.acuant.acuantcamera.detector.MrzState import com.acuant.acuantcamera.helper.MrzResult +import com.acuant.acuantcamera.helper.PointsUtils import com.acuant.acuantcamera.overlay.MrzRectangleView +import java.lang.ref.WeakReference import kotlin.math.* +enum class MrzCameraState { Align, MoveCloser, Reposition, Trying, Capturing } -class AcuantMrzCameraFragment : AcuantBaseCameraFragment(), ActivityCompat.OnRequestPermissionsResultCallback, AcuantOcrDetectorHandler { - - /** - * This is the output file for our picture. - */ - private val mrzParser = MrzParser() +class AcuantMrzCameraFragment: AcuantBaseCameraFragment() { + private var rectangleView: MrzRectangleView? = null + private var textView: TextView? = null + private var imageView: ImageView? = null + private var cameraUiContainerBinding: MrzFragmentUiBinding? = null + private var defaultTextDrawable: Drawable? = null private var tries = 0 - private var handler: Handler? = Handler(Looper.getMainLooper()) - private var mrzResult : MrzResult? = null - private var capturing = false - private var allowCapture = false - - - override fun setTextFromState(state: CameraState) { - setTextFromState(activity!!, state, textView, imageView) - } - - override fun onPointsDetected(points: Array?) { - activity!!.runOnUiThread { - if (points != null) { - if (points.size == 4) { - val scaledPointY = textureView.height.toFloat() / previewSize.width.toFloat() - val scaledPointX = textureView.width.toFloat() / previewSize.height.toFloat() - rectangleView.setWidth(textureView.width.toFloat()) + private var handler: Handler? = null + private var oldPoints: Array? = null - points.apply { - this.forEach { - it.x = (it.x * scaledPointY).toInt() - it.y = (it.y * scaledPointX).toInt() - it.y -= pointYOffset - it.x += pointXOffset - } - } - - MrzRectangleView.fixPoints(points) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) - var dist = 0 - if (oldPoints != null && oldPoints!!.size == 4 && 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() - } - } + cameraUiContainerBinding?.root?.let { + fragmentCameraBinding!!.root.removeView(it) + } - if (dist > TOO_MUCH_MOVEMENT) { - resetCapture() - } + cameraUiContainerBinding = MrzFragmentUiBinding.inflate( + LayoutInflater.from(requireContext()), + fragmentCameraBinding!!.root, + true + ) - if (capturing) { - setTextFromState(CameraState.MrzCapturing) - rectangleView.setViewFromState(CameraState.MrzCapturing) - } else if (!isAligned(points) || !isAcceptableAspectRatio(points)) { - resetCapture() - setTextFromState(CameraState.MrzAlign) - rectangleView.setViewFromState(CameraState.MrzAlign) - rectangleView.setAndDrawPoints(null) - } else if(!isAcceptableDistance(points, textureView.height.toFloat())) { - resetCapture() - setTextFromState(CameraState.MrzMoveCloser) - rectangleView.setViewFromState(CameraState.MrzMoveCloser) - rectangleView.setAndDrawPoints(points) - }else if (tries < ALLOWED_ERRORS) { - allowCapture = true - setTextFromState(CameraState.MrzTrying) - rectangleView.setViewFromState(CameraState.MrzTrying) - rectangleView.setAndDrawPoints(points) - } else { - allowCapture = true - setTextFromState(CameraState.MrzReposition) - rectangleView.setViewFromState(CameraState.MrzReposition) - rectangleView.setAndDrawPoints(points) - } - } else if(!capturing) { - resetCapture() - setTextFromState(CameraState.MrzNone) - rectangleView.setViewFromState(CameraState.MrzNone) - rectangleView.setAndDrawPoints(null) - } - } else if(!capturing) { - resetCapture() - setTextFromState(CameraState.MrzNone) - rectangleView.setViewFromState(CameraState.MrzNone) - rectangleView.setAndDrawPoints(null) - } + rectangleView = cameraUiContainerBinding?.mrzRectangle + textView = cameraUiContainerBinding?.mrzTextView + imageView = cameraUiContainerBinding?.mrzImage - oldPoints = points - } - } + setOptions(rectangleView) - private fun resetCapture() { - tries = 0 - allowCapture = false - } + defaultTextDrawable = AppCompatResources.getDrawable(requireContext(), R.drawable.camera_text_config_default) - override fun onOcrDetected(textBlock: String?){ - if (textBlock != null && allowCapture) { - val result = mrzParser.parseMrz(textBlock) - //Log.d("MRZLOG", " " + result?.checkSumResult1 + ", "+ result?.checkSumResult2 +", "+ result?.checkSumResult3 +", "+ result?.checkSumResult4 +", "+ result?.checkSumResult5) - if (result != null) { - if (result.checkSumResult1 && result.checkSumResult2 && result.checkSumResult3 && result.checkSumResult4 && result.checkSumResult5) { - capturing = true - this.isCapturing = true - setTextFromState(CameraState.MrzCapturing) - rectangleView.setViewFromState(CameraState.MrzCapturing) - if (mrzResult == null || (mrzResult?.country == "" && result.country != "")) { - mrzResult = result - } - handler?.postDelayed({ - handler?.removeCallbacksAndMessages(null) - if (activity is ICameraActivityFinish) { - (activity as ICameraActivityFinish).onActivityFinish(result) - } - }, 750) - } - } - ++tries - } - detectors.forEach { - if (it is AcuantOcrDetector) { - it.isProcessing = false - } - } } override fun onPause() { @@ -152,110 +66,166 @@ class AcuantMrzCameraFragment : AcuantBaseCameraFragment(), ActivityCompat.OnReq super.onPause() } - override fun onResume() { handler = Handler(Looper.getMainLooper()) - if (capturing) { - capturing = false - this.isCapturing = false - } + capturing = false super.onResume() } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - detectors = listOf(AcuantOcrDetector(activity!!.applicationContext, this)) - tries = 0 - - - options = arguments?.getSerializable(ACUANT_EXTRA_CAMERA_OPTIONS) as AcuantCameraOptions? - ?: AcuantCameraOptions.MrzCameraOptionsBuilder() - .setAllowBox(isBorderEnabled) - .build() - - capturingTextDrawable = activity!!.getDrawable(R.drawable.camera_text_config_capturing) - defaultTextDrawable = activity!!.getDrawable(R.drawable.camera_text_config_default) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - textureView = view.findViewById(R.id.texture) - rectangleView = view.findViewById(R.id.acu_mrz_rectangle) as MrzRectangleView - - super.onViewCreated(view, savedInstanceState) - } - - override fun setTapToCapture() { - //mrz does not currently support tap to capture - } + private fun onMrzDetection(points: Array?, result: MrzResult?, state: MrzState) { + if (!capturing) { + activity?.runOnUiThread { - companion object { - const val ALLOWED_ERRORS = 7 - - const val TOO_MUCH_MOVEMENT = 200 + var detectedPoints = points + if (detectedPoints != null) { + val camContainer = fragmentCameraBinding?.root + val analyzerSize = imageAnalyzer?.resolutionInfo?.resolution + val previewSize = fragmentCameraBinding?.viewFinder + detectedPoints = PointsUtils.fixPoints(PointsUtils.scalePoints(detectedPoints, camContainer, analyzerSize, previewSize, rectangleView)) + var dist = 0 + if (oldPoints != null && oldPoints!!.size == 4 && 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() + } + } - private fun distance(pointA: Point, pointB: Point): Float { - return sqrt( (pointA.x - pointB.x).toFloat().pow(2) + (pointA.y - pointB.y).toFloat().pow(2)) - } + if (dist > TOO_MUCH_MOVEMENT) { + resetCapture() + } - fun isAligned(points: Array) : Boolean { - if (points.size != 4) - return false - val val1 = distance(points[0], points[2]) - val val2 = distance(points[1], points[3]) - return abs(val1 - val2) < 15 - } + when { + state == MrzState.NoMrz || !PointsUtils.correctDirection(points, previewSize) -> { + resetCapture() + setTextFromState(MrzCameraState.Align) + rectangleView?.setViewFromState(MrzCameraState.Align) + } + state == MrzState.TooFar -> { + resetCapture() - fun isAcceptableAspectRatio(points: Array) : Boolean { - val ratio = distance(points[0], points[3]) / distance(points[0], points[1]) - return ratio > 4f && ratio < 10f - } + setTextFromState(MrzCameraState.MoveCloser) + rectangleView?.setViewFromState(MrzCameraState.MoveCloser) + } + else -> { //good mrz + when { + result != null && result.allCheckSumsPassed -> { + capturing = true + setTextFromState(MrzCameraState.Capturing) + rectangleView?.setViewFromState(MrzCameraState.Capturing) + val handler = handler + if (handler != null) { + handler.postDelayed({ + this.handler?.removeCallbacksAndMessages(null) + cameraActivityListener.onCameraDone(result) + }, 750) + } else { + handler?.removeCallbacksAndMessages(null) + cameraActivityListener.onCameraDone(result) + + } + } + tries < ALLOWED_ERRORS -> { + ++tries + setTextFromState(MrzCameraState.Trying) + rectangleView?.setViewFromState(MrzCameraState.Trying) + } + else -> { //too many errors + setTextFromState(MrzCameraState.Reposition) + rectangleView?.setViewFromState(MrzCameraState.Reposition) + } + } + } + } + } - fun isAcceptableDistance(points: Array, screenSize: Float): Boolean { - val dist = max(distance(points[0], points[1]), distance(points[0], points[3])) - return dist > 0.65 * screenSize + oldPoints = detectedPoints + rectangleView?.setAndDrawPoints(detectedPoints) + } } + } - @JvmStatic fun newInstance(): AcuantBaseCameraFragment = AcuantMrzCameraFragment() - fun setTextFromState(context: Context, state: CameraState, textView: TextView, imageView: ImageView) { - + private fun resetCapture() { + tries = 0 + } + + private fun setTextFromState(state: MrzCameraState) { + if (!isAdded) + return + val imageView = this.imageView + val textView = this.textView + + if (imageView != null && textView != null) { imageView.visibility = View.INVISIBLE textView.visibility = View.VISIBLE when (state) { - CameraState.MrzReposition -> { - textView.background = context.getDrawable(R.drawable.camera_text_config_default) - textView.text = context.resources.getString(R.string.acuant_glare_mrz) + MrzCameraState.MoveCloser -> { + textView.background = defaultTextDrawable + textView.text = getString(R.string.acuant_closer_mrz) textView.setTextColor(Color.WHITE) textView.textSize = 24f - textView.layoutParams.width = context.resources.getDimension(R.dimen.cam_error_width).toInt() } - CameraState.MrzTrying -> { - textView.background = context.getDrawable(R.drawable.camera_text_config_default) - textView.text = context.resources.getString(R.string.acuant_reading_mrz) + MrzCameraState.Reposition -> { + textView.background = defaultTextDrawable + textView.text = getString(R.string.acuant_glare_mrz) textView.setTextColor(Color.WHITE) textView.textSize = 24f - textView.layoutParams.width = context.resources.getDimension(R.dimen.cam_info_width).toInt() + textView.layoutParams.width = + resources.getDimension(R.dimen.cam_error_width).toInt() } - CameraState.MrzCapturing -> { - textView.background = context.getDrawable(R.drawable.camera_text_config_default) - textView.text = context.resources.getString(R.string.acuant_read_mrz) + MrzCameraState.Trying -> { + textView.background = defaultTextDrawable + textView.text = getString(R.string.acuant_reading_mrz) textView.setTextColor(Color.WHITE) textView.textSize = 24f - textView.layoutParams.width = context.resources.getDimension(R.dimen.cam_info_width).toInt() + textView.layoutParams.width = resources.getDimension(R.dimen.cam_info_width).toInt() } - CameraState.MrzMoveCloser -> { - textView.background = context.getDrawable(R.drawable.camera_text_config_default) - textView.text = context.resources.getString(R.string.acuant_closer_mrz) + MrzCameraState.Capturing -> { + textView.background = defaultTextDrawable + textView.text = getString(R.string.acuant_read_mrz) textView.setTextColor(Color.WHITE) textView.textSize = 24f + textView.layoutParams.width = resources.getDimension(R.dimen.cam_info_width).toInt() } - else -> {//none //align + else -> { textView.visibility = View.INVISIBLE imageView.visibility = View.VISIBLE } } } } + + override fun rotateUi(rotation: Int) { + textView?.rotation = rotation.toFloat() + imageView?.rotation = rotation.toFloat() + } + + override fun buildImageAnalyzer(screenAspectRatio: Int, rotation: Int) { + val frameAnalyzer = MrzFrameAnalyzer (WeakReference(requireContext())) { points, result, state -> + onMrzDetection(points, result, state) + } + imageAnalyzer = ImageAnalysis.Builder() + .setTargetResolution(Size(960, 1280)) +// .setTargetAspectRatio(screenAspectRatio) + .setTargetRotation(rotation) + .build() + .also { + it.setAnalyzer(cameraExecutor, frameAnalyzer) + } + } + + companion object { + const val ALLOWED_ERRORS = 7 + + const val TOO_MUCH_MOVEMENT = 200 + + @JvmStatic fun newInstance(acuantOptions: AcuantCameraOptions): AcuantMrzCameraFragment { + val frag = AcuantMrzCameraFragment() + val args = Bundle() + args.putSerializable(INTERNAL_OPTIONS, acuantOptions) + frag.arguments = args + return frag + } + } } \ No newline at end of file diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/AcuantMrzFeedback.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/AcuantMrzFeedback.kt deleted file mode 100644 index 18a2c44..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/AcuantMrzFeedback.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.acuant.acuantcamera.camera.mrz.cameraone - -import android.graphics.Point -import android.util.Size - -data class AcuantMrzFeedback(val feedback: MrzFeedback, val point: Array?, val frameSize: Size?, val barcode: String? = null) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as AcuantMrzFeedback - - if (feedback != other.feedback) return false - if (point != null) { - if (other.point == null) return false - if (!point.contentEquals(other.point)) return false - } else if (other.point != null) return false - if (frameSize != other.frameSize) return false - if (barcode != other.barcode) return false - - return true - } - - override fun hashCode(): Int { - var result = feedback.hashCode() - result = 31 * result + (point?.contentHashCode() ?: 0) - result = 31 * result + (frameSize?.hashCode() ?: 0) - result = 31 * result + (barcode?.hashCode() ?: 0) - return result - } -} \ No newline at end of file diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/LiveMrzProcessor.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/LiveMrzProcessor.kt deleted file mode 100644 index c6fdd3e..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/LiveMrzProcessor.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.acuant.acuantcamera.camera.mrz.cameraone - -import android.content.Context -import android.graphics.Bitmap -import android.util.Log -import com.acuant.acuantcamera.detector.ocr.AcuantOcrDetector -import com.google.android.gms.vision.MultiProcessor -import com.google.android.gms.vision.barcode.Barcode -import com.google.android.gms.vision.barcode.BarcodeDetector - - -class LiveMrzProcessor : MrzGraphicTracker.BarcodeUpdateListener { - private var finishedCapturing = false - private var mrzDetector: MrzDetector? = null - internal var ocrDetector: AcuantOcrDetector? = null - var frame: Bitmap? = null - - fun setOcrDetector(detector: AcuantOcrDetector) { - ocrDetector = detector - } - - fun getDetector(context: Context): MrzDetector { - val barcodeDetectorDelagte = BarcodeDetector.Builder(context).setBarcodeFormats(Barcode.PDF417).build() - val barcodeFactory = MrzTrackerFactory(this) - val processor = MultiProcessor.Builder(barcodeFactory).build() - mrzDetector = MrzDetector(barcodeDetectorDelagte) - mrzDetector!!.setProcessor(processor) - - provideFeedback() - - return mrzDetector as MrzDetector - } - - fun stop(){ - finishedCapturing = true - thread?.join() - mrzDetector?.release() - thread = null - mrzDetector = null - // thread = null - } - - private var thread : Thread? = null - - private fun provideFeedback() { - if (ocrDetector == null) { - Log.e("ocrDetector", "OCR detector can not be null") - //TODO: return error - } - thread = Thread(object : Runnable { - private var flag = true - private var processing = false - override fun run() { - while (flag) { - if (!processing) { - processing = true - frame = mrzDetector?.frame - if (frame != null) { - ocrDetector?.detect(frame) - - } - processing = false - - } - flag = !finishedCapturing - } - } - }) - - thread!!.start() - } - - override fun onBarcodeDetected(barcode: Barcode?) { - //do nothing eventually refactor the code - } -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCameraSource.java b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCameraSource.java deleted file mode 100644 index 2ff2cf8..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCameraSource.java +++ /dev/null @@ -1,1267 +0,0 @@ -package com.acuant.acuantcamera.camera.mrz.cameraone; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.ImageFormat; -import android.graphics.SurfaceTexture; -import android.hardware.Camera; -import android.hardware.Camera.CameraInfo; -import android.os.Build; -import android.os.SystemClock; -import android.support.annotation.Nullable; -import android.support.annotation.RequiresPermission; -import android.support.annotation.StringDef; -import android.util.Log; -import android.view.Surface; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.WindowManager; - -import com.google.android.gms.common.images.Size; -import com.google.android.gms.vision.Detector; -import com.google.android.gms.vision.Frame; - -import java.io.IOException; -import java.lang.Thread.State; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -// Note: This requires Google Play Services 8.1 or higher, due to using indirect byte buffers for -// storing images. - -/** - * Manages the camera in conjunction with an underlying - * {@link Detector}. This receives preview frames from the camera at - * a specified rate, sending those frames to the detector as fast as it is able to process those - * frames. - *

- * This camera source makes a best effort to manage processing on preview frames as fast as - * possible, while at the same time minimizing lag. As such, frames may be dropped if the detector - * is unable to keep up with the rate of frames generated by the camera. You should use - * {@link Builder#setRequestedFps(float)} to specify a frame rate that works well with - * the capabilities of the camera hardware and the detector options that you have selected. If CPU - * utilization is higher than you'd like, then you may want to consider reducing FPS. If the camera - * preview or detector results are too "jerky", then you may want to consider increasing FPS. - *

- * The following Android permission is required to use the camera: - *

    - *
  • android.permissions.CAMERA
  • - *
- */ -@SuppressWarnings("deprecation") -public class MrzCameraSource { - @SuppressLint("InlinedApi") - static final int CAMERA_FACING_BACK = CameraInfo.CAMERA_FACING_BACK; - @SuppressLint("InlinedApi") - private static final int CAMERA_FACING_FRONT = CameraInfo.CAMERA_FACING_FRONT; - - private static final String TAG = "OpenCameraSource"; - - /** - * The dummy surface texture must be assigned a chosen name. Since we never use an OpenGL - * context, we can choose any ID we want here. - */ - private static final int DUMMY_TEXTURE_NAME = 100; - - /** - * If the absolute difference between a preview size aspect ratio and a picture size aspect - * ratio is less than this tolerance, they are considered to be the same aspect ratio. - */ - private static final float ASPECT_RATIO_TOLERANCE = 0.1f; - - @StringDef({ - Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE, - Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, - Camera.Parameters.FOCUS_MODE_AUTO, - Camera.Parameters.FOCUS_MODE_EDOF, - Camera.Parameters.FOCUS_MODE_FIXED, - Camera.Parameters.FOCUS_MODE_INFINITY, - Camera.Parameters.FOCUS_MODE_MACRO - }) - @Retention(RetentionPolicy.SOURCE) - private @interface FocusMode {} - - @StringDef({ - Camera.Parameters.FLASH_MODE_ON, - Camera.Parameters.FLASH_MODE_OFF, - Camera.Parameters.FLASH_MODE_AUTO, - Camera.Parameters.FLASH_MODE_RED_EYE, - Camera.Parameters.FLASH_MODE_TORCH - }) - @Retention(RetentionPolicy.SOURCE) - private @interface FlashMode {} - - private Context mContext; - - private final Object mCameraLock = new Object(); - - // Guarded by mCameraLock - private Camera mCamera; - - private int mFacing = CAMERA_FACING_BACK; - - - /** - * Rotation of the device, and thus the associated preview images captured from the device. - * See {@link Frame.Metadata#getRotation()}. - */ - private int mRotation; - - private com.google.android.gms.common.images.Size mPreviewSize; - - // These values may be requested by the caller. Due to hardware limitations, we may need to - // select close, but not exactly the same values for these. - private float mRequestedFps = 30.0f; - private int mRequestedPreviewWidth = 1600; - private int mRequestedPreviewHeight = 1200; - - - private String mFocusMode = null; - private String mFlashMode = null; - - // These instances need to be held onto to avoid GC of their underlying resources. Even though - // these aren't used outside of the method that creates them, they still must have hard - // references maintained to them. - @SuppressWarnings("FieldCanBeLocal") - private SurfaceView mDummySurfaceView; - @SuppressWarnings("FieldCanBeLocal") - private SurfaceTexture mDummySurfaceTexture; - - /** - * Dedicated thread and associated runnable for calling into the detector with frames, as the - * frames become available from the camera. - */ - private Thread mProcessingThread; - private FrameProcessingRunnable mFrameProcessor; - - /** - * Map to convert between a byte array, received from the camera, and its associated byte - * buffer. We use byte buffers internally because this is a more efficient way to call into - * native code later (avoids a potential copy). - */ - private Map mBytesToByteBuffer = new HashMap<>(); - - //============================================================================================== - // Builder - //============================================================================================== - - /** - * Builder for configuring and creating an associated camera source. - */ - public static class Builder { - private final Detector mDetector; - private MrzCameraSource mrzCameraSource = new MrzCameraSource(); - - /** - * Creates a camera source builder with the supplied context and detector. Camera preview - * images will be streamed to the associated detector upon starting the camera source. - */ - public Builder(Context context, Detector detector) { - if (context == null) { - throw new IllegalArgumentException("No context supplied."); - } - if (detector == null) { - throw new IllegalArgumentException("No detector supplied."); - } - - mDetector = detector; - mrzCameraSource.mContext = context; - } - - /** - * Sets the requested frame rate in frames per second. If the exact requested value is not - * not available, the best matching available value is selected. Default: 30. - */ - public Builder setRequestedFps(float fps) { - if (fps <= 0) { - throw new IllegalArgumentException("Invalid fps: " + fps); - } - mrzCameraSource.mRequestedFps = fps; - return this; - } - - public Builder setFocusMode(@FocusMode String mode) { - mrzCameraSource.mFocusMode = mode; - return this; - } - - - - public Builder setFlashMode(@FlashMode String mode) { - mrzCameraSource.mFlashMode = mode; - return this; - } - - /** - * Sets the desired width and height of the camera frames in pixels. If the exact desired - * values are not available options, the best matching available options are selected. - * Also, we try to select a preview size which corresponds to the aspect ratio of an - * associated full picture size, if applicable. Default: 1024x768. - */ - public Builder setRequestedPreviewSize(int width, int height) { - // Restrict the requested range to something within the realm of possibility. The - // choice of 1000000 is a bit arbitrary -- intended to be well beyond resolutions that - // devices can support. We bound this to avoid int overflow in the code later. - final int MAX = 1000000; - if ((width <= 0) || (width > MAX) || (height <= 0) || (height > MAX)) { - throw new IllegalArgumentException("Invalid preview size: " + width + "x" + height); - } - mrzCameraSource.mRequestedPreviewWidth = width; - mrzCameraSource.mRequestedPreviewHeight = height; - return this; - } - - /** - * Sets the camera to use (either {@link #CAMERA_FACING_BACK} or - * {@link #CAMERA_FACING_FRONT}). Default: back facing. - */ - public Builder setFacing(int facing) { - if ((facing != CAMERA_FACING_BACK) && (facing != CAMERA_FACING_FRONT)) { - throw new IllegalArgumentException("Invalid camera: " + facing); - } - mrzCameraSource.mFacing = facing; - return this; - } - - /** - * Creates an instance of the camera source. - */ - public MrzCameraSource build() { - mrzCameraSource.mFrameProcessor = mrzCameraSource.new FrameProcessingRunnable(mDetector); - return mrzCameraSource; - } - } - - //============================================================================================== - // Bridge Functionality for the Camera1 API - //============================================================================================== - - /** - * Callback interface used to signal the moment of actual image capture. - */ - public interface ShutterCallback { - /** - * Called as near as possible to the moment when a photo is captured from the sensor. This - * is a good opportunity to play a shutter sound or give other feedback of camera operation. - * This may be some time after the photo was triggered, but some time before the actual data - * is available. - */ - void onShutter(); - } - - /** - * Callback interface used to supply image data from a photo capture. - */ - public interface PictureCallback { - /** - * Called when image data is available after a picture is taken. The format of the data - * is a jpeg binary. - */ - void onPictureTaken(byte[] data); - } - - /** - * Callback interface used to notify on completion of camera auto focus. - */ - public interface AutoFocusCallback { - /** - * Called when the camera auto focus completes. If the camera - * does not support auto-focus and autoFocus is called, - * onAutoFocus will be called immediately with a fake value of - * success set to true. - *

- * The auto-focus routine does not lock auto-exposure and auto-white - * balance after it completes. - * - * @param success true if focus was successful, false if otherwise - */ - void onAutoFocus(boolean success); - } - - /** - * Callback interface used to notify on auto focus start and stop. - *

- *

This is only supported in continuous autofocus modes -- {@link - * Camera.Parameters#FOCUS_MODE_CONTINUOUS_VIDEO} and {@link - * Camera.Parameters#FOCUS_MODE_CONTINUOUS_PICTURE}. Applications can show - * autofocus animation based on this.

- */ - public interface AutoFocusMoveCallback { - /** - * Called when the camera auto focus starts or stops. - * - * @param start true if focus starts to move, false if focus stops to move - */ - void onAutoFocusMoving(boolean start); - } - - //============================================================================================== - // Public - //============================================================================================== - - /** - * Stops the camera and releases the resources of the camera and underlying detector. - */ - public void release() { - synchronized (mCameraLock) { - stop(); - mFrameProcessor.release(); - - } - } - - /** - * Opens the camera and starts sending preview frames to the underlying detector. The preview - * frames are not displayed. - * - * @throws IOException if the camera's preview texture or display could not be initialized - */ - @RequiresPermission(Manifest.permission.CAMERA) - public MrzCameraSource start() throws IOException { - synchronized (mCameraLock) { - if (mCamera != null) { - return this; - } - - mCamera = createCamera(); - - // SurfaceTexture was introduced in Honeycomb (11), so if we are running and - // old version of Android. fall back to use SurfaceView. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - mDummySurfaceTexture = new SurfaceTexture(DUMMY_TEXTURE_NAME); - mCamera.setPreviewTexture(mDummySurfaceTexture); - } else { - mDummySurfaceView = new SurfaceView(mContext); - mCamera.setPreviewDisplay(mDummySurfaceView.getHolder()); - } - mCamera.startPreview(); - - mProcessingThread = new Thread(mFrameProcessor); - mFrameProcessor.setActive(true); - mProcessingThread.start(); - } - return this; - } - - /** - * Opens the camera and starts sending preview frames to the underlying detector. The supplied - * surface holder is used for the preview so frames can be displayed to the user. - * - * @param surfaceHolder the surface holder to use for the preview frames - * @throws IOException if the supplied surface holder could not be used as the preview display - */ - @RequiresPermission(Manifest.permission.CAMERA) - public MrzCameraSource start(SurfaceHolder surfaceHolder, MrzCameraSourcePreview preview) throws IOException { - synchronized (mCameraLock) { - if (mCamera != null) { - return this; - } - - try { - mCamera = createCamera(); - } catch (RuntimeException e) { - e.printStackTrace(); - throw new IOException(); - } - mCamera.setPreviewDisplay(surfaceHolder); - mCamera.startPreview(); - - if (preview != null) { - preview.requestLayout(); - } - - mProcessingThread = new Thread(mFrameProcessor); - mFrameProcessor.setActive(true); - mProcessingThread.start(); - } - return this; - } - - /** - * Closes the camera and stops sending frames to the underlying frame detector. - *

- * This camera source may be restarted again by calling {@link #start()} or - * {@link #start(SurfaceHolder, MrzCameraSourcePreview)}. - *

- * Call {@link #release()} instead to completely shut down this camera source and release the - * resources of the underlying detector. - */ - public void stop() { - synchronized (mCameraLock) { - mFrameProcessor.setActive(false); - if (mProcessingThread != null) { - try { - // Wait for the thread to complete to ensure that we can't have multiple threads - // executing at the same time (i.e., which would happen if we called start too - // quickly after stop). - mProcessingThread.join(); - } catch (InterruptedException e) { - Log.d(TAG, "Frame processing thread interrupted on release."); - } - mProcessingThread = null; - } - - // clear the buffer to prevent oom exceptions - mBytesToByteBuffer.clear(); - - if (mCamera != null) { - mCamera.stopPreview(); - mCamera.setPreviewCallbackWithBuffer(null); - try { - // We want to be compatible back to Gingerbread, but SurfaceTexture - // wasn't introduced until Honeycomb. Since the interface cannot use a SurfaceTexture, if the - // developer wants to display a preview we must use a SurfaceHolder. If the developer doesn't - // want to display a preview we use a SurfaceTexture if we are running at least Honeycomb. - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - mCamera.setPreviewTexture(null); - - } else { - mCamera.setPreviewDisplay(null); - } - } catch (Exception e) { - Log.e(TAG, "Failed to clear camera preview: " + e); - } - mCamera.release(); - mCamera = null; - } - } - } - - /** - * Returns the preview size that is currently in use by the underlying camera. - */ - public Size getPreviewSize() { - return mPreviewSize; - } - - /** - * Returns the selected camera; one of {@link #CAMERA_FACING_BACK} or - * {@link #CAMERA_FACING_FRONT}. - */ - public int getCameraFacing() { - return mFacing; - } - - public int doZoom(float scale) { - synchronized (mCameraLock) { - if (mCamera == null) { - return 0; - } - int currentZoom = 0; - int maxZoom; - Camera.Parameters parameters = mCamera.getParameters(); - if (!parameters.isZoomSupported()) { - Log.w(TAG, "Zoom is not supported on this device"); - return currentZoom; - } - maxZoom = parameters.getMaxZoom(); - - currentZoom = parameters.getZoom() + 1; - float newZoom; - if (scale > 1) { - newZoom = currentZoom + scale * (maxZoom / 10); - } else { - newZoom = currentZoom * scale; - } - currentZoom = Math.round(newZoom) - 1; - if (currentZoom < 0) { - currentZoom = 0; - } else if (currentZoom > maxZoom) { - currentZoom = maxZoom; - } - parameters.setZoom(currentZoom); - mCamera.setParameters(parameters); - return currentZoom; - } - } - - /** - * Initiates taking a picture, which happens asynchronously. The camera source should have been - * activated previously with {@link #start()} or {@link #start(SurfaceHolder, MrzCameraSourcePreview)}. The camera - * preview is suspended while the picture is being taken, but will resume once picture taking is - * done. - * - * @param shutter the callback for image capture moment, or null - * @param jpeg the callback for JPEG image data, or null - */ - public void takePicture(ShutterCallback shutter, PictureCallback jpeg) { - synchronized (mCameraLock) { - if (mCamera != null) { - PictureStartCallback startCallback = new PictureStartCallback(); - startCallback.mDelegate = shutter; - PictureDoneCallback doneCallback = new PictureDoneCallback(); - doneCallback.mDelegate = jpeg; - mCamera.takePicture(startCallback, null, null, doneCallback); - } - } - } - - /** - * Gets the current focus mode setting. - * - * @return current focus mode. This value is null if the camera is not yet created. Applications should call {@link - * #autoFocus(AutoFocusCallback)} to start the focus if focus - * mode is FOCUS_MODE_AUTO or FOCUS_MODE_MACRO. - * @see Camera.Parameters#FOCUS_MODE_AUTO - * @see Camera.Parameters#FOCUS_MODE_INFINITY - * @see Camera.Parameters#FOCUS_MODE_MACRO - * @see Camera.Parameters#FOCUS_MODE_FIXED - * @see Camera.Parameters#FOCUS_MODE_EDOF - * @see Camera.Parameters#FOCUS_MODE_CONTINUOUS_VIDEO - * @see Camera.Parameters#FOCUS_MODE_CONTINUOUS_PICTURE - */ - @Nullable - @FocusMode - public String getFocusMode() { - return mFocusMode; - } - - /** - * Sets the focus mode. - * - * @param mode the focus mode - * @return {@code true} if the focus mode is set, {@code false} otherwise - * @see #getFocusMode() - */ - public boolean setFocusMode(@FocusMode String mode) { - synchronized (mCameraLock) { - if (mCamera != null && mode != null) { - Camera.Parameters parameters = mCamera.getParameters(); - if (parameters.getSupportedFocusModes().contains(mode)) { - parameters.setFocusMode(mode); - mCamera.setParameters(parameters); - mFocusMode = mode; - return true; - } - } - - return false; - } - } - - /** - * Gets the current flash mode setting. - * - * @return current flash mode. null if flash mode setting is not - * supported or the camera is not yet created. - * @see Camera.Parameters#FLASH_MODE_OFF - * @see Camera.Parameters#FLASH_MODE_AUTO - * @see Camera.Parameters#FLASH_MODE_ON - * @see Camera.Parameters#FLASH_MODE_RED_EYE - * @see Camera.Parameters#FLASH_MODE_TORCH - */ - @Nullable - @FlashMode - public String getFlashMode() { - return mFlashMode; - } - - /** - * Sets the flash mode. - * - * @param mode flash mode. - * @return {@code true} if the flash mode is set, {@code false} otherwise - * @see #getFlashMode() - */ - public boolean setFlashMode(@FlashMode String mode) { - synchronized (mCameraLock) { - if (mCamera != null && mode != null) { - Camera.Parameters parameters = mCamera.getParameters(); - if (parameters.getSupportedFlashModes().contains(mode)) { - parameters.setFlashMode(mode); - mCamera.setParameters(parameters); - mFlashMode = mode; - return true; - } - } - - return false; - } - } - - /** - * Starts camera auto-focus and registers a callback function to run when - * the camera is focused. This method is only valid when preview is active - * (between {@link #start()} or {@link #start(SurfaceHolder, MrzCameraSourcePreview)} and before {@link #stop()} or {@link #release()}). - *

- *

Callers should check - * {@link #getFocusMode()} to determine if - * this method should be called. If the camera does not support auto-focus, - * it is a no-op and {@link AutoFocusCallback#onAutoFocus(boolean)} - * callback will be called immediately. - *

- *

If the current flash mode is not - * {@link Camera.Parameters#FLASH_MODE_OFF}, flash may be - * fired during auto-focus, depending on the driver and camera hardware.

- * - * @param cb the callback to run - * @see #cancelAutoFocus() - */ - public void autoFocus(@Nullable AutoFocusCallback cb) { - synchronized (mCameraLock) { - if (mCamera != null) { - CameraAutoFocusCallback autoFocusCallback = null; - if (cb != null) { - autoFocusCallback = new CameraAutoFocusCallback(); - autoFocusCallback.mDelegate = cb; - } - mCamera.autoFocus(autoFocusCallback); - } - } - } - - /** - * Cancels any auto-focus function in progress. - * Whether or not auto-focus is currently in progress, - * this function will return the focus position to the default. - * If the camera does not support auto-focus, this is a no-op. - * - * @see #autoFocus(AutoFocusCallback) - */ - public void cancelAutoFocus() { - synchronized (mCameraLock) { - if (mCamera != null) { - mCamera.cancelAutoFocus(); - } - } - } - - /** - * Sets camera auto-focus move callback. - * - * @param cb the callback to run - * @return {@code true} if the operation is supported (i.e. from Jelly Bean), {@code false} otherwise - */ - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - public boolean setAutoFocusMoveCallback(@Nullable AutoFocusMoveCallback cb) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { - return false; - } - - synchronized (mCameraLock) { - if (mCamera != null) { - CameraAutoFocusMoveCallback autoFocusMoveCallback = null; - if (cb != null) { - autoFocusMoveCallback = new CameraAutoFocusMoveCallback(); - autoFocusMoveCallback.mDelegate = cb; - } - mCamera.setAutoFocusMoveCallback(autoFocusMoveCallback); - } - } - - return true; - } - - //============================================================================================== - // Private - //============================================================================================== - - /** - * Only allow creation via the builder class. - */ - private MrzCameraSource() { - } - - /** - * Wraps the camera1 shutter callback so that the deprecated API isn't exposed. - */ - private class PictureStartCallback implements Camera.ShutterCallback { - private ShutterCallback mDelegate; - - @Override - public void onShutter() { - if (mDelegate != null) { - mDelegate.onShutter(); - } - } - } - - /** - * Wraps the final callback in the camera sequence, so that we can automatically turn the camera - * preview back on after the picture has been taken. - */ - private class PictureDoneCallback implements Camera.PictureCallback { - private PictureCallback mDelegate; - - @Override - public void onPictureTaken(byte[] data, Camera camera) { - if (mDelegate != null) { - mDelegate.onPictureTaken(data); - } - synchronized (mCameraLock) { - if (mCamera != null) { - mCamera.startPreview(); - } - } - } - } - - /** - * Wraps the camera1 auto focus callback so that the deprecated API isn't exposed. - */ - private class CameraAutoFocusCallback implements Camera.AutoFocusCallback { - private AutoFocusCallback mDelegate; - - @Override - public void onAutoFocus(boolean success, Camera camera) { - if (mDelegate != null) { - mDelegate.onAutoFocus(success); - } - } - } - - /** - * Wraps the camera1 auto focus move callback so that the deprecated API isn't exposed. - */ - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - private class CameraAutoFocusMoveCallback implements Camera.AutoFocusMoveCallback { - private AutoFocusMoveCallback mDelegate; - - @Override - public void onAutoFocusMoving(boolean start, Camera camera) { - if (mDelegate != null) { - mDelegate.onAutoFocusMoving(start); - } - } - } - - /** - * Opens the camera and applies the user settings. - * - * @throws RuntimeException if the method fails - */ - @SuppressLint("InlinedApi") - private Camera createCamera() { - int requestedCameraId = getIdForRequestedCamera(mFacing); - if (requestedCameraId == -1) { - throw new RuntimeException("Could not find requested camera."); - } - Camera camera = Camera.open(requestedCameraId); - - SizePair sizePair = selectSizePair(camera, mRequestedPreviewWidth, mRequestedPreviewHeight); - if (sizePair == null) { - throw new RuntimeException("Could not find suitable preview size."); - } - Size pictureSize = sizePair.pictureSize(); - mPreviewSize = sizePair.previewSize(); - - int[] previewFpsRange = selectPreviewFpsRange(camera, mRequestedFps); - if (previewFpsRange == null) { - throw new RuntimeException("Could not find suitable preview frames per second range."); - } - - Camera.Parameters parameters = camera.getParameters(); - - if (pictureSize != null) { - parameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight()); - } - - parameters.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); - parameters.setPreviewFpsRange( - previewFpsRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX], - previewFpsRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]); - parameters.setPreviewFormat(ImageFormat.NV21); - - setRotation(camera, parameters, requestedCameraId); - - if (mFocusMode != null) { - if (parameters.getSupportedFocusModes().contains( - mFocusMode)) { - parameters.setFocusMode(mFocusMode); - } else { - Log.i(TAG, "Camera focus mode: " + mFocusMode + " is not supported on this device."); - } - } - - // setting mFocusMode to the one set in the params - mFocusMode = parameters.getFocusMode(); - - if (mFlashMode != null) { - if (parameters.getSupportedFlashModes() != null) { - if (parameters.getSupportedFlashModes().contains( - mFlashMode)) { - parameters.setFlashMode(mFlashMode); - } else { - Log.i(TAG, "Camera flash mode: " + mFlashMode + " is not supported on this device."); - } - } - } - - // setting mFlashMode to the one set in the params - mFlashMode = parameters.getFlashMode(); - - camera.setParameters(parameters); - - // Four frame buffers are needed for working with the camera: - // - // one for the frame that is currently being executed upon in doing detection - // one for the next pending frame to process immediately upon completing detection - // two for the frames that the camera uses to populate future preview images - camera.setPreviewCallbackWithBuffer(new CameraPreviewCallback()); - camera.addCallbackBuffer(createPreviewBuffer(mPreviewSize)); - camera.addCallbackBuffer(createPreviewBuffer(mPreviewSize)); - camera.addCallbackBuffer(createPreviewBuffer(mPreviewSize)); - camera.addCallbackBuffer(createPreviewBuffer(mPreviewSize)); - - return camera; - } - - /** - * Gets the id for the camera specified by the direction it is facing. Returns -1 if no such - * camera was found. - * - * @param facing the desired camera (front-facing or rear-facing) - */ - private static int getIdForRequestedCamera(int facing) { - CameraInfo cameraInfo = new CameraInfo(); - for (int i = 0; i < Camera.getNumberOfCameras(); ++i) { - Camera.getCameraInfo(i, cameraInfo); - if (cameraInfo.facing == facing) { - return i; - } - } - return -1; - } - -// private static SizePair selectSizePair(Camera camera, int desiredWidth, int desiredHeight) { -// List validPreviewSizes = generateValidPreviewSizeList(camera); -// // The method for selecting the best size is to minimize the sum of the differences between -// // the desired values and the actual values for width and height. This is certainly not the -// // only way to select the best size, but it provides a decent tradeoff between using the -// // closest aspect ratio vs. using the closest pixel area. -// SizePair selectedPair = null; -// int maxArea = Integer.MIN_VALUE; -// int maxPict = Integer.MIN_VALUE; -// int area = Integer.MIN_VALUE; -// for (SizePair sizePair : validPreviewSizes) { -// Size size_pict = sizePair.pictureSize(); -// int newArea = size_pict.getWidth()*size_pict.getHeight(); -// if (newArea > maxPict) { -// maxPict = newArea; -// maxArea = Integer.MIN_VALUE; -// Size size = sizePair.previewSize(); -// area = size.getWidth()*size.getHeight(); -// if (maxArea < area) { -// selectedPair = sizePair; -// maxArea = area; -// } -// } -// else if (newArea == maxPict) { -// Size size = sizePair.previewSize(); -// area = size.getWidth()*size.getHeight(); -// if (maxArea < area) { -// selectedPair = sizePair; -// maxArea = area; -// } -// } -// } -// return selectedPair; -// } - - /** - * Selects the most suitable preview and picture size, given the desired width and height. - *

- * Even though we may only need the preview size, it's necessary to find both the preview - * size and the picture size of the camera together, because these need to have the same aspect - * ratio. On some hardware, if you would only set the preview size, you will get a distorted - * image. - * - * @param camera the camera to select a preview size from - * @param desiredWidth the desired width of the camera preview frames - * @param desiredHeight the desired height of the camera preview frames - * @return the selected preview and picture size pair - */ - private static SizePair selectSizePair(Camera camera, int desiredWidth, int desiredHeight) { - List validPreviewSizes = generateValidPreviewSizeList(camera); - long expectedSize = desiredWidth*desiredHeight; - int desiredShortSide = Math.min(desiredHeight, desiredWidth); - int desiredLongSide = Math.max(desiredHeight, desiredWidth); - // The method for selecting the best size is to minimize the sum of the differences between - // the desired values and the actual values for width and height. This is certainly not the - // only way to select the best size, but it provides a decent tradeoff between using the - // closest aspect ratio vs. using the closest pixel area. - SizePair selectedPair = null; - int minDiff = Integer.MAX_VALUE; - for (SizePair sizePair : validPreviewSizes) { - Size size = sizePair.previewSize(); - - - int currentShortSide = Math.min(size.getWidth(), size.getHeight()); - int currentLongSide = Math.max(size.getWidth(), size.getHeight()); - float change = Math.min(Math.min((float) desiredShortSide / currentShortSide, (float) desiredLongSide / currentLongSide), 2f); - currentLongSide *= change; - currentShortSide *= change; - - - int diff = Math.abs(currentShortSide - desiredShortSide) + - Math.abs(currentLongSide - desiredLongSide); - if ((!(currentLongSide > desiredLongSide) && !(currentShortSide > desiredShortSide)) && diff < minDiff && (sizePair.pictureSize().getWidth()*sizePair.pictureSize().getHeight() >= expectedSize * 0.6f)) { - selectedPair = sizePair; - minDiff = diff; - } - } - - if (selectedPair != null) { - return selectedPair; - } else if (validPreviewSizes.size() > 0){ - return validPreviewSizes.get(0); - } else { - return null; - } - } - - - /** - * Stores a preview size and a corresponding same-aspect-ratio picture size. To avoid distorted - * preview images on some devices, the picture size must be set to a size that is the same - * aspect ratio as the preview size or the preview may end up being distorted. If the picture - * size is null, then there is no picture size with the same aspect ratio as the preview size. - */ - private static class SizePair { - private Size mPreview; - private Size mPicture; - - public SizePair(Camera.Size previewSize, - Camera.Size pictureSize) { - mPreview = new Size(previewSize.width, previewSize.height); - if (pictureSize != null) { - mPicture = new Size(pictureSize.width, pictureSize.height); - } - } - - public Size previewSize() { - return mPreview; - } - - @SuppressWarnings("unused") - public Size pictureSize() { - return mPicture; - } - } - - /** - * Generates a list of acceptable preview sizes. Preview sizes are not acceptable if there is - * not a corresponding picture size of the same aspect ratio. If there is a corresponding - * picture size of the same aspect ratio, the picture size is paired up with the preview size. - *

- * This is necessary because even if we don't use still pictures, the still picture size must be - * set to a size that is the same aspect ratio as the preview size we choose. Otherwise, the - * preview images may be distorted on some devices. - */ - private static List generateValidPreviewSizeList(Camera camera) { - Camera.Parameters parameters = camera.getParameters(); - List supportedPreviewSizes = - parameters.getSupportedPreviewSizes(); - List supportedPictureSizes = - parameters.getSupportedPictureSizes(); - List validPreviewSizes = new ArrayList<>(); - for (Camera.Size previewSize : supportedPreviewSizes) { - float previewAspectRatio = (float) previewSize.width / (float) previewSize.height; - - // By looping through the picture sizes in order, we favor the higher resolutions. - // We choose the highest resolution in order to support taking the full resolution - // picture later. - for (Camera.Size pictureSize : supportedPictureSizes) { - float pictureAspectRatio = (float) pictureSize.width / (float) pictureSize.height; - if (Math.abs(previewAspectRatio - pictureAspectRatio) < ASPECT_RATIO_TOLERANCE) { - validPreviewSizes.add(new SizePair(previewSize, pictureSize)); - break; - } - } - } - - // If there are no picture sizes with the same aspect ratio as any preview sizes, allow all - // of the preview sizes and hope that the camera can handle it. Probably unlikely, but we - // still account for it. - if (validPreviewSizes.size() == 0) { - Log.w(TAG, "No preview sizes have a corresponding same-aspect-ratio picture size"); - for (Camera.Size previewSize : supportedPreviewSizes) { - // The null picture size will let us know that we shouldn't set a picture size. - validPreviewSizes.add(new SizePair(previewSize, null)); - } - } - - return validPreviewSizes; - } - - /** - * Selects the most suitable preview frames per second range, given the desired frames per - * second. - * - * @param camera the camera to select a frames per second range from - * @param desiredPreviewFps the desired frames per second for the camera preview frames - * @return the selected preview frames per second range - */ - private int[] selectPreviewFpsRange(Camera camera, float desiredPreviewFps) { - // The camera API uses integers scaled by a factor of 1000 instead of floating-point frame - // rates. - int desiredPreviewFpsScaled = (int) (desiredPreviewFps * 1000.0f); - - // The method for selecting the best range is to minimize the sum of the differences between - // the desired value and the upper and lower bounds of the range. This may select a range - // that the desired value is outside of, but this is often preferred. For example, if the - // desired frame rate is 29.97, the range (30, 30) is probably more desirable than the - // range (15, 30). - int[] selectedFpsRange = null; - int minDiff = Integer.MAX_VALUE; - List previewFpsRangeList = camera.getParameters().getSupportedPreviewFpsRange(); - for (int[] range : previewFpsRangeList) { - int deltaMin = desiredPreviewFpsScaled - range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX]; - int deltaMax = desiredPreviewFpsScaled - range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]; - int diff = Math.abs(deltaMin) + Math.abs(deltaMax); - if (diff < minDiff) { - selectedFpsRange = range; - minDiff = diff; - } - } - return selectedFpsRange; - } - - /** - * Calculates the correct rotation for the given camera id and sets the rotation in the - * parameters. It also sets the camera's display orientation and rotation. - * - * @param parameters the camera parameters for which to set the rotation - * @param cameraId the camera id to set rotation based on - */ - private void setRotation(Camera camera, Camera.Parameters parameters, int cameraId) { - WindowManager windowManager = - (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - int degrees = 0; - int rotation = windowManager.getDefaultDisplay().getRotation(); - switch (rotation) { - case Surface.ROTATION_0: - degrees = 0; - break; - case Surface.ROTATION_90: - degrees = 90; - break; - case Surface.ROTATION_180: - degrees = 180; - break; - case Surface.ROTATION_270: - degrees = 270; - break; - default: - Log.e(TAG, "Bad rotation value: " + rotation); - } - - CameraInfo cameraInfo = new CameraInfo(); - Camera.getCameraInfo(cameraId, cameraInfo); - - int angle; - int displayAngle; - if (cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT) { - angle = (cameraInfo.orientation + degrees) % 360; - displayAngle = (360 - angle) % 360; // compensate for it being mirrored - } else { // back-facing - angle = (cameraInfo.orientation - degrees + 360) % 360; - displayAngle = angle; - } - - // This corresponds to the rotation constants in {@link Frame}. - mRotation = angle / 90; - - camera.setDisplayOrientation(displayAngle); - parameters.setRotation(angle); - } - - /** - * Creates one buffer for the camera preview callback. The size of the buffer is based off of - * the camera preview size and the format of the camera image. - * - * @return a new preview buffer of the appropriate size for the current camera settings - */ - private byte[] createPreviewBuffer(Size previewSize) { - int bitsPerPixel = ImageFormat.getBitsPerPixel(ImageFormat.NV21); - long sizeInBits = previewSize.getHeight() * previewSize.getWidth() * bitsPerPixel; - int bufferSize = (int) Math.ceil(sizeInBits / 8.0d) + 1; - - // - // NOTICE: This code only works when using play services v. 8.1 or higher. - // - - // Creating the byte array this way and wrapping it, as opposed to using .allocate(), - // should guarantee that there will be an array to work with. - byte[] byteArray = new byte[bufferSize]; - ByteBuffer buffer = ByteBuffer.wrap(byteArray); - if (!buffer.hasArray() || (buffer.array() != byteArray)) { - // I don't think that this will ever happen. But if it does, then we wouldn't be - // passing the preview content to the underlying detector later. - throw new IllegalStateException("Failed to create valid buffer for camera source."); - } - - mBytesToByteBuffer.put(byteArray, buffer); - return byteArray; - } - - //============================================================================================== - // Frame processing - //============================================================================================== - - /** - * Called when the camera has a new preview frame. - */ - private class CameraPreviewCallback implements Camera.PreviewCallback { - @Override - public void onPreviewFrame(byte[] data, Camera camera) { - mFrameProcessor.setNextFrame(data, camera); - } - } - - /** - * This runnable controls access to the underlying receiver, calling it to process frames when - * available from the camera. This is designed to run detection on frames as fast as possible - * (i.e., without unnecessary context switching or waiting on the next frame). - *

- * While detection is running on a frame, new frames may be received from the camera. As these - * frames come in, the most recent frame is held onto as pending. As soon as detection and its - * associated processing are done for the previous frame, detection on the mostly recently - * received frame will immediately start on the same thread. - */ - private class FrameProcessingRunnable implements Runnable { - private Detector mDetector; - private long mStartTimeMillis = SystemClock.elapsedRealtime(); - - // This lock guards all of the member variables below. - private final Object mLock = new Object(); - private boolean mActive = true; - - // These pending variables hold the state associated with the new frame awaiting processing. - private long mPendingTimeMillis; - private int mPendingFrameId = 0; - private ByteBuffer mPendingFrameData; - - FrameProcessingRunnable(Detector detector) { - mDetector = detector; - } - - /** - * Releases the underlying receiver. This is only safe to do after the associated thread - * has completed, which is managed in camera source's release method above. - */ - @SuppressLint("Assert") - void release() { - assert mProcessingThread == null || mProcessingThread.getState() == State.TERMINATED; - mDetector.release(); - mDetector = null; - } - - /** - * Marks the runnable as active/not active. Signals any blocked threads to continue. - */ - void setActive(boolean active) { - synchronized (mLock) { - mActive = active; - mLock.notifyAll(); - } - } - - /** - * Sets the frame data received from the camera. This adds the previous unused frame buffer - * (if present) back to the camera, and keeps a pending reference to the frame data for - * future use. - */ - void setNextFrame(byte[] data, Camera camera) { - synchronized (mLock) { - if (mPendingFrameData != null) { - camera.addCallbackBuffer(mPendingFrameData.array()); - mPendingFrameData = null; - } - - if (!mBytesToByteBuffer.containsKey(data)) { - Log.d(TAG, - "Skipping frame. Could not find ByteBuffer associated with the image " + - "data from the camera."); - return; - } - - // Timestamp and frame ID are maintained here, which will give downstream code some - // idea of the timing of frames received and when frames were dropped along the way. - mPendingTimeMillis = SystemClock.elapsedRealtime() - mStartTimeMillis; - mPendingFrameId++; - mPendingFrameData = mBytesToByteBuffer.get(data); - - // Notify the processor thread if it is waiting on the next frame (see below). - mLock.notifyAll(); - } - } - - /** - * As long as the processing thread is active, this executes detection on frames - * continuously. The next pending frame is either immediately available or hasn't been - * received yet. Once it is available, we transfer the frame info to local variables and - * run detection on that frame. It immediately loops back for the next frame without - * pausing. - *

- * If detection takes longer than the time in between new frames from the camera, this will - * mean that this loop will run without ever waiting on a frame, avoiding any context - * switching or frame acquisition time latency. - *

- * If you find that this is using more CPU than you'd like, you should probably decrease the - * FPS setting above to allow for some idle time in between frames. - */ - @Override - public void run() { - Frame outputFrame; - ByteBuffer data; - - while (true) { - synchronized (mLock) { - while (mActive && (mPendingFrameData == null)) { - try { - // Wait for the next frame to be received from the camera, since we - // don't have it yet. - mLock.wait(); - } catch (InterruptedException e) { - Log.d(TAG, "Frame processing loop terminated.", e); - return; - } - } - - if (!mActive) { - // Exit the loop once this camera source is stopped or released. We check - // this here, immediately after the wait() above, to handle the case where - // setActive(false) had been called, triggering the termination of this - // loop. - return; - } - - outputFrame = new Frame.Builder() - .setImageData(mPendingFrameData, mPreviewSize.getWidth(), - mPreviewSize.getHeight(), ImageFormat.NV21) - .setId(mPendingFrameId) - .setTimestampMillis(mPendingTimeMillis) - .setRotation(mRotation) - .build(); - - // Hold onto the frame data locally, so that we can use this for detection - // below. We need to clear mPendingFrameData to ensure that this buffer isn't - // recycled back to the camera before we are done using that data. - data = mPendingFrameData; - mPendingFrameData = null; - } - - // The code below needs to run outside of synchronization, because this will allow - // the camera to add pending frame(s) while we are running detection on the current - // frame. - - try { - mDetector.receiveFrame(outputFrame); - } catch (Throwable t) { - Log.e(TAG, "Exception thrown from receiver.", t); - } finally { - mCamera.addCallbackBuffer(data.array()); - } - } - } - } -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCameraSourcePreview.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCameraSourcePreview.kt deleted file mode 100644 index 2402c16..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCameraSourcePreview.kt +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.acuant.acuantcamera.camera.mrz.cameraone - -import android.Manifest -import android.content.Context -import android.content.res.Configuration -import android.support.annotation.RequiresPermission -import android.support.v7.app.AlertDialog -import android.util.AttributeSet -import android.util.Log -import android.view.SurfaceHolder -import android.view.SurfaceView -import android.view.ViewGroup -import com.acuant.acuantcamera.R - - -import java.io.IOException - -class MrzCameraSourcePreview(private val mContext: Context, attrs: AttributeSet?) : ViewGroup(mContext, attrs) { - var mSurfaceView: SurfaceView - var pointXOffset: Int = 0 - var pointYOffset: Int = 0 - private var mStartRequested: Boolean = false - private var mSurfaceAvailable: Boolean = false - private var mrzCameraSource: MrzCameraSource? = null - - private val isPortraitMode: Boolean - get() { - val orientation = mContext.resources.configuration.orientation - if (orientation == Configuration.ORIENTATION_LANDSCAPE) { - return false - } - if (orientation == Configuration.ORIENTATION_PORTRAIT) { - return true - } - - Log.d(TAG, "isPortraitMode returning false by default") - return false - } - - init { - mStartRequested = false - mSurfaceAvailable = false - - mSurfaceView = SurfaceView(mContext) - mSurfaceView.holder.addCallback(SurfaceCallback()) - addView(mSurfaceView) - } - - @RequiresPermission(Manifest.permission.CAMERA) - @Throws(IOException::class, SecurityException::class) - fun start(mrzCameraSource: MrzCameraSource?) { - if (mrzCameraSource == null) { - stop() - } - - this.mrzCameraSource = mrzCameraSource - - if (this.mrzCameraSource != null) { - mStartRequested = true - startIfReady() - } - } - - fun stop() { - if (mrzCameraSource != null) { - mrzCameraSource!!.stop() - } - } - - fun release() { - if (mrzCameraSource != null) { - mrzCameraSource!!.release() - mrzCameraSource = null - } - } - - @RequiresPermission(Manifest.permission.CAMERA) - @Throws(IOException::class, SecurityException::class) - private fun startIfReady() { - if (mStartRequested && mSurfaceAvailable) { - mrzCameraSource!!.start(mSurfaceView.holder, this) - mStartRequested = false - } - } - - private inner class SurfaceCallback : SurfaceHolder.Callback { - override fun surfaceCreated(surface: SurfaceHolder) { - mSurfaceAvailable = true - try { - startIfReady() - } catch (se: SecurityException) { - Log.e(TAG, "Do not have permission to start the camera", se) - } catch (e: IOException) { - Log.e(TAG, "Could not start camera source.", e) - val dialogBuilder = AlertDialog.Builder(context) - dialogBuilder.setMessage(R.string.error_starting_cam) - .setPositiveButton(R.string.ok - ) { dialog, _ -> - dialog.dismiss() - } - dialogBuilder.create().show() - } - - } - - override fun surfaceDestroyed(surface: SurfaceHolder) { - mSurfaceAvailable = false - } - - override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {} - } - - override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - - val viewWidth = right - left - val viewHeight = bottom - top - - var previewWidth = viewWidth - var previewHeight = viewHeight - if (mrzCameraSource != null) { - val size = mrzCameraSource?.previewSize - if (size != null) { - previewWidth = size.width - previewHeight = size.height - } - } - - // Swap width and height sizes when in portrait, since it will be rotated 90 degrees - if (isPortraitMode) { - val tmp = previewWidth - previewWidth = previewHeight - previewHeight = tmp - } - - val childWidth: Int - val childHeight: Int - val childXOffset: Int - val childYOffset: Int - val widthRatio = viewWidth.toFloat() / previewWidth.toFloat() - val heightRatio = viewHeight.toFloat() / previewHeight.toFloat() - - if (widthRatio < heightRatio) { - childWidth = viewWidth - childHeight = (previewHeight.toFloat() * widthRatio).toInt() - } else { - childWidth = (previewWidth.toFloat() * heightRatio).toInt() - childHeight = viewHeight - } - childXOffset = (childWidth - viewWidth) / 2 - childYOffset = (childHeight - viewHeight) / 2 - - pointXOffset = childXOffset - pointYOffset = childYOffset - - for (i in 0 until childCount) { - getChildAt(i).layout( - -1 * childXOffset, -1 * childYOffset, - childWidth - childXOffset, childHeight - childYOffset) - getChildAt(i).requestLayout() - } - try { - startIfReady() - } catch (e: IOException) { - e.printStackTrace() - } catch (se: SecurityException) { - se.printStackTrace() - } - } - - companion object { - private const val TAG = "CameraSourcePreview" - } -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCaptureActivity.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCaptureActivity.kt deleted file mode 100644 index b38d806..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzCaptureActivity.kt +++ /dev/null @@ -1,538 +0,0 @@ -@file:Suppress("DEPRECATION") - -package com.acuant.acuantcamera.camera.mrz.cameraone - -import android.Manifest -import android.annotation.SuppressLint -import android.content.Context -import android.content.DialogInterface -import android.content.Intent -import android.content.pm.ActivityInfo -import android.content.pm.PackageManager -import android.graphics.Point -import android.hardware.Camera -import android.media.AudioManager -import android.os.Bundle -import android.util.Log -import android.view.OrientationEventListener -import android.view.View -import android.view.Window -import android.view.WindowManager -import android.widget.TextView - -import com.google.android.gms.common.ConnectionResult -import com.google.android.gms.common.GoogleApiAvailability - -import java.io.IOException - -import android.content.res.Configuration.ORIENTATION_LANDSCAPE -import android.content.res.Configuration.ORIENTATION_PORTRAIT -import android.graphics.drawable.Drawable -import android.os.Handler -import android.os.Looper -import android.support.v4.app.ActivityCompat -import android.support.v7.app.AlertDialog -import android.support.v7.app.AppCompatActivity -import android.util.DisplayMetrics -import android.widget.ImageView -import com.acuant.acuantcamera.R -import com.acuant.acuantcamera.camera.* -import com.acuant.acuantcamera.camera.mrz.AcuantMrzCameraFragment -import com.acuant.acuantcamera.camera.mrz.AcuantMrzCameraFragment.Companion.setTextFromState -import com.acuant.acuantcamera.constant.ACUANT_EXTRA_CAMERA_OPTIONS -import com.acuant.acuantcamera.constant.ACUANT_EXTRA_IS_AUTO_CAPTURE -import com.acuant.acuantcamera.constant.ACUANT_EXTRA_MRZ_RESULT -import com.acuant.acuantcamera.detector.ocr.AcuantOcrDetector -import com.acuant.acuantcamera.detector.ocr.AcuantOcrDetectorHandler -import com.acuant.acuantcamera.helper.MrzParser -import com.acuant.acuantcamera.helper.MrzResult -import com.acuant.acuantcamera.overlay.MrzRectangleView -import java.io.File -import java.io.FileOutputStream -import java.util.* -import kotlin.math.pow -import kotlin.math.sqrt - -/** - * Activity for the multi-tracker app. This app detects barcodes and displays the value with the - * rear facing camera. During detection overlay graphics are drawn to indicate the position, - * size, and ID of each barcode. - */ -class MrzCaptureActivity : AppCompatActivity(), MrzCameraSource.PictureCallback, MrzCameraSource.ShutterCallback, AcuantOcrDetectorHandler { - - private var mrzCameraSource: MrzCameraSource? = null - private var mPreview: MrzCameraSourcePreview? = null - private var waitTime = 2 - private var autoCapture = false - private lateinit var mrzProcessor: LiveMrzProcessor - private var permissionNotGranted = false - private var mrzDetector: MrzDetector? = null - - private lateinit var instructionView: TextView - private lateinit var imageView: ImageView - - private var capturedbarcodeString: String? = null - - private lateinit var rectangleView: MrzRectangleView - - private var capturingTextDrawable: Drawable? = null - private var defaultTextDrawable: Drawable? = null - private var oldPoints : Array? = null - private val mrzParser = MrzParser() - private var tries = 0 - private val handler = Handler(Looper.getMainLooper()) - private var mrzResult : MrzResult? = null - private var capturing = false - private var allowCapture = false - - override fun onPointsDetected(points: Array?) { - runOnUiThread { - if (points != null) { - if (points.size == 4) { - val scaledPointY = mPreview!!.mSurfaceView.height.toFloat() / (mrzDetector?.frame?.width?.toFloat() ?: mPreview!!.mSurfaceView.height.toFloat()) - val scaledPointX = mPreview!!.mSurfaceView.width.toFloat() / (mrzDetector?.frame?.height?.toFloat() ?: mPreview!!.mSurfaceView.width.toFloat()) - rectangleView.setWidth(mPreview!!.mSurfaceView.width.toFloat()) - - points.apply { - this.forEach { - it.x = (it.x * scaledPointY).toInt() - it.y = (it.y * scaledPointX).toInt() - it.y += mPreview?.pointXOffset ?: 0 - it.x -= mPreview?.pointYOffset ?: 0 - } - } - - MrzRectangleView.fixPoints(points) - - var dist = 0 - if (oldPoints != null && oldPoints!!.size == 4 && 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 (dist > AcuantMrzCameraFragment.TOO_MUCH_MOVEMENT) { - resetCapture() - } - - if (capturing) { - setTextFromState(this, AcuantBaseCameraFragment.CameraState.MrzCapturing, instructionView, imageView) - rectangleView.setViewFromState(AcuantBaseCameraFragment.CameraState.MrzCapturing) - } else if (!AcuantMrzCameraFragment.isAligned(points) || !AcuantMrzCameraFragment.isAcceptableAspectRatio(points)) { - resetCapture() - setTextFromState(this, AcuantBaseCameraFragment.CameraState.MrzAlign, instructionView, imageView) - rectangleView.setViewFromState(AcuantBaseCameraFragment.CameraState.MrzAlign) - rectangleView.setAndDrawPoints(null) - } else if(!AcuantMrzCameraFragment.isAcceptableDistance(points, mPreview?.mSurfaceView?.height?.toFloat() ?: 0f)) { - resetCapture() - setTextFromState(this, AcuantBaseCameraFragment.CameraState.MrzMoveCloser, instructionView, imageView) - rectangleView.setViewFromState(AcuantBaseCameraFragment.CameraState.MrzMoveCloser) - rectangleView.setAndDrawPoints(points) - }else if (tries < AcuantMrzCameraFragment.ALLOWED_ERRORS) { - allowCapture = true - setTextFromState(this, AcuantBaseCameraFragment.CameraState.MrzTrying, instructionView, imageView) - rectangleView.setViewFromState(AcuantBaseCameraFragment.CameraState.MrzTrying) - rectangleView.setAndDrawPoints(points) - } else { - allowCapture = true - setTextFromState(this, AcuantBaseCameraFragment.CameraState.MrzReposition, instructionView, imageView) - rectangleView.setViewFromState(AcuantBaseCameraFragment.CameraState.MrzReposition) - rectangleView.setAndDrawPoints(points) - } - } else if(!capturing) { - resetCapture() - setTextFromState(this, AcuantBaseCameraFragment.CameraState.MrzNone, instructionView, imageView) - rectangleView.setViewFromState(AcuantBaseCameraFragment.CameraState.MrzNone) - rectangleView.setAndDrawPoints(null) - } - } else if(!capturing) { - resetCapture() - setTextFromState(this, AcuantBaseCameraFragment.CameraState.MrzNone, instructionView, imageView) - rectangleView.setViewFromState(AcuantBaseCameraFragment.CameraState.MrzNone) - rectangleView.setAndDrawPoints(null) - } - - oldPoints = points - } - } - - private fun resetCapture() { - tries = 0 - allowCapture = false - } - - override fun onOcrDetected(textBlock: String?){ - if (textBlock != null && allowCapture) { - val result = mrzParser.parseMrz(textBlock) - - if (result != null) { - if (result.checkSumResult1 && result.checkSumResult2 && result.checkSumResult3 && result.checkSumResult4 && result.checkSumResult5) { - capturing = true - setTextFromState(this, AcuantBaseCameraFragment.CameraState.MrzCapturing, instructionView, imageView) - rectangleView.setViewFromState(AcuantBaseCameraFragment.CameraState.MrzCapturing) - if (mrzResult == null || (mrzResult?.country == "" && result.country != "")) { - mrzResult = result - } - handler.postDelayed({ - handler.removeCallbacksAndMessages(null) - val data = Intent() - data.putExtra(ACUANT_EXTRA_MRZ_RESULT, mrzResult) - setResult(AcuantCameraActivity.RESULT_SUCCESS_CODE, data) - finish() - }, 750) - } - } - ++tries - } - mrzProcessor.ocrDetector?.isProcessing = false - } - - private fun setOptions(options : AcuantCameraOptions?) { - if(options != null) { - rectangleView.allowBox = options.allowBox - rectangleView.bracketLengthInHorizontal = options.bracketLengthInHorizontal - rectangleView.bracketLengthInVertical = options.bracketLengthInVertical - rectangleView.defaultBracketMarginHeight = options.defaultBracketMarginHeight - rectangleView.defaultBracketMarginWidth = options.defaultBracketMarginWidth - rectangleView.paintColorCapturing = options.colorCapturing - rectangleView.paintColorHold = options.colorHold - rectangleView.paintColorBracketAlign = options.colorBracketAlign - rectangleView.paintColorBracketCapturing = options.colorBracketCapturing - rectangleView.paintColorBracketCloser = options.colorBracketCloser - rectangleView.paintColorBracketHold = options.colorBracketHold - rectangleView.cardRatio = options.cardRatio - } - } - - /** - * Initializes the UI and creates the detector pipeline. - */ - public override fun onCreate(icicle: Bundle?) { - super.onCreate(icicle) - waitTime = intent.getIntExtra("WAIT", 2) - autoCapture = intent.getBooleanExtra(ACUANT_EXTRA_IS_AUTO_CAPTURE, true) - - - val options = intent?.getSerializableExtra(ACUANT_EXTRA_CAMERA_OPTIONS) as AcuantCameraOptions? - ?: AcuantCameraOptions - .MrzCameraOptionsBuilder() - .build() - - requestWindowFeature(Window.FEATURE_NO_TITLE) - requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY) - window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN) - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - supportActionBar?.title = "" - supportActionBar?.hide() - - capturingTextDrawable = this.getDrawable(R.drawable.camera_text_config_capturing) - defaultTextDrawable = this.getDrawable(R.drawable.camera_text_config_default) - - setContentView(R.layout.activity_acu_mrz_camera) - - mPreview = findViewById(R.id.cam1_mrz_preview) - rectangleView = findViewById(R.id.cam1_mrz_rect) - instructionView = findViewById(R.id.cam1_mrz_text) - imageView = findViewById(R.id.cam1_mrz_image) - - setOptions(options) - - setTextFromState(this, AcuantBaseCameraFragment.CameraState.Align, instructionView, imageView) - - // Check for the camera permission before accessing the camera. If the - // permission is not granted yet, request permission. - val rc = ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) - if (rc == PackageManager.PERMISSION_GRANTED) { - createCameraSource(useFlash = false) - } else { - requestCameraPermission() - permissionNotGranted = true - } - - mOrientationEventListener = object : OrientationEventListener(this.applicationContext) { - var lastOrientation = 0 - override fun onOrientationChanged(orientation: Int) { - if (orientation < 0) { - return // Flip screen, Not take account - } - val curOrientation: Int = when { - orientation <= 45 -> ORIENTATION_PORTRAIT - orientation <= 135 -> ORIENTATION_LANDSCAPE_REVERSE - orientation <= 225 -> ORIENTATION_PORTRAIT_REVERSE - orientation <= 315 -> ORIENTATION_LANDSCAPE - else -> ORIENTATION_PORTRAIT - } - if (curOrientation != lastOrientation) { - onChanged(lastOrientation, curOrientation) - lastOrientation = curOrientation - } - } - } - } - - /** - * Handles the requesting of the camera permission. This includes - * showing a "Snackbar" message of why the permission is needed then - * sending the request. - */ - private fun requestCameraPermission() { - Log.w(TAG, "Camera permission is not granted. Requesting permission") - - val permissions = arrayOf(Manifest.permission.CAMERA) - - val builder = AlertDialog.Builder(this) - builder.setMessage(R.string.cam_perm_request_text) - .setOnCancelListener { - if (!ActivityCompat.shouldShowRequestPermissionRationale(this, - Manifest.permission.CAMERA)) { - ActivityCompat.requestPermissions(this, permissions, RC_HANDLE_CAMERA_PERM) - } else { - finish() - } - } - .setPositiveButton(R.string.ok - ) { dialog, _ -> - dialog.dismiss() - if (!ActivityCompat.shouldShowRequestPermissionRationale(this, - Manifest.permission.CAMERA)) { - ActivityCompat.requestPermissions(this, permissions, RC_HANDLE_CAMERA_PERM) - } else { - finish() - } - } - builder.create().show() - } - - /** - * Creates and starts the camera. Note that this uses a higher resolution in comparison - * to other detection examples to enable the barcode detector to detect small barcodes - * at long distances. - * - * - * Suppressing InlinedApi since there is a check that the minimum version is met before using - * the constant. - */ - @Suppress("SameParameterValue") - @SuppressLint("InlinedApi") - private fun createCameraSource(useFlash: Boolean) { - // Creates and starts the camera. Note that this uses a higher resolution in comparison - // to other detection examples to enable the barcode detector to detect small barcodes - // at long distances. - val displayMetrics = DisplayMetrics() - windowManager.defaultDisplay.getMetrics(displayMetrics) - val height: Int = displayMetrics.heightPixels - val width: Int = displayMetrics.widthPixels - - mrzDetector = createDocumentDetector() - var builder: MrzCameraSource.Builder = MrzCameraSource.Builder(applicationContext, mrzDetector) - .setFacing(MrzCameraSource.CAMERA_FACING_BACK) - .setRequestedPreviewSize(width, height) - .setRequestedFps(60.0f) - - // make sure that auto focus is an available option - builder = builder.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE) - - mrzCameraSource = builder - .setFlashMode(if (useFlash) Camera.Parameters.FLASH_MODE_TORCH else Camera.Parameters.FLASH_MODE_OFF) - .build() - } - - private fun createDocumentDetector(): MrzDetector { - mrzProcessor = LiveMrzProcessor() - mrzProcessor.setOcrDetector(AcuantOcrDetector(this, this)) - return mrzProcessor.getDetector(applicationContext) - - } - - private lateinit var mOrientationEventListener: OrientationEventListener - /** - * Restarts the camera. - */ - override fun onResume() { - super.onResume() - - capturing = false - - if (mOrientationEventListener.canDetectOrientation()) { - mOrientationEventListener.enable() - } - startCameraSource() - } - - /** - * Stops the camera. - */ - override fun onPause() { - super.onPause() - if (mPreview != null) { - mPreview!!.stop() - } - handler.removeCallbacksAndMessages(null) - mOrientationEventListener.disable() - } - - /** - * Releases the resources associated with the camera source, the associated detectors, and the - * rest of the processing pipeline. - */ - override fun onDestroy() { - super.onDestroy() - if (!permissionNotGranted) { - mrzProcessor.stop() - } - if (mPreview != null) { - mPreview!!.release() - } - } - - /** - * Callback for the result from requesting permissions. This method - * is invoked for every call on [.requestPermissions]. - * - * - * **Note:** It is possible that the permissions request interaction - * with the user is interrupted. In this case you will receive empty permissions - * and results arrays which should be treated as a cancellation. - * - * - * @param requestCode The request code passed in [.requestPermissions]. - * @param permissions The requested permissions. Never null. - * @param grantResults The grant results for the corresponding permissions - * which is either [PackageManager.PERMISSION_GRANTED] - * or [PackageManager.PERMISSION_DENIED]. Never null. - * @see .requestPermissions - */ - override fun onRequestPermissionsResult(requestCode: Int, - permissions: Array, - grantResults: IntArray) { - if (requestCode != RC_HANDLE_CAMERA_PERM) { - Log.d(TAG, "Got unexpected permission result: $requestCode") - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - return - } - - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - Log.d(TAG, "Camera permission granted - initialize the camera source") - permissionNotGranted = false - createCameraSource(useFlash = false) - return - } - - Log.e(TAG, "Permission not granted: results len = " + grantResults.size + - " Result code = " + if (grantResults.isNotEmpty()) grantResults[0] else "(empty)") - - val listener = DialogInterface.OnClickListener { _, _ -> finish() } - - val builder = AlertDialog.Builder(this) - builder.setTitle(R.string.camera_load_error) - .setMessage(R.string.no_camera_permission) - .setPositiveButton(R.string.ok, listener) - .show() - - } - - /** - * Starts or restarts the camera source, if it exists. If the camera source doesn't exist yet - * (e.g., because onResume was called before the camera source was created), this will be called - * again when the camera source is created. - */ - @Throws(SecurityException::class) - private fun startCameraSource() { - // check that the device has play services available. - val code = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable( - applicationContext) - if (code != ConnectionResult.SUCCESS) { - val dlg = GoogleApiAvailability.getInstance().getErrorDialog(this, code, RC_HANDLE_GMS) - dlg.show() - } - - if (mrzCameraSource != null) { - try { - mPreview!!.start(mrzCameraSource) - } catch (e: IOException) { - Log.e(TAG, "Unable to start camera source.", e) - mrzCameraSource!!.release() - mrzCameraSource = null - } - } - } - - override fun onBackPressed() { - this@MrzCaptureActivity.finish() - } - - private fun onChanged(@Suppress("UNUSED_PARAMETER") lastOrientation: Int, curOrientation: Int) { - - runOnUiThread { - if (curOrientation == ORIENTATION_LANDSCAPE_REVERSE) { - rotateView(instructionView, 0f, 270f) - rotateView(imageView, 0f, 270f) - } else if (curOrientation == ORIENTATION_LANDSCAPE) { - rotateView(instructionView, 360f, 90f) - rotateView(imageView, 360f, 90f) - } - } - - } - - private fun rotateView(view: View?, startDeg: Float, endDeg: Float) { - if (view != null) { - view.rotation = startDeg - view.animate().rotation(endDeg).start() - } - } - - - override fun onPictureTaken(data: ByteArray) { - Thread(Runnable { - val mgr = getSystemService(Context.AUDIO_SERVICE) as AudioManager - mgr.setStreamMute(AudioManager.STREAM_SYSTEM, false) - this@MrzCaptureActivity.runOnUiThread { - val result = Intent() - val file = File(this.getExternalFilesDir(null), "${UUID.randomUUID()}.jpg") - saveFile(file, data) - result.putExtra(ACUANT_EXTRA_IMAGE_URL, file.absolutePath) - result.putExtra(ACUANT_EXTRA_PDF417_BARCODE, this.capturedbarcodeString) - setResult(AcuantCameraActivity.RESULT_SUCCESS_CODE, result) - finish() - } - }).start() - - } - - private fun saveFile(file: File, data: ByteArray){ - var output: FileOutputStream? = null - try { - output = FileOutputStream(file).apply {write(data)} - } catch (e: IOException) { - } finally { - output?.let { - try { - it.close() - } catch (e: IOException) { - } - } - } - } - - override fun onShutter() { - - Log.d("onShutter", "onShutter") - - } - - companion object { - private const val TAG = "Barcode-reader" - - // intent request code to handle updating play services if needed. - private const val RC_HANDLE_GMS = 9001 - - const val ACUANT_EXTRA_IMAGE_URL = "img-url" - const val ACUANT_EXTRA_PDF417_BARCODE = "barcode" - - // permission request codes need to be < 256 - private const val RC_HANDLE_CAMERA_PERM = 2 - private const val ORIENTATION_PORTRAIT_REVERSE = 4 - private const val ORIENTATION_LANDSCAPE_REVERSE = 3 - } -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzDetector.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzDetector.kt deleted file mode 100644 index 35fad70..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzDetector.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.acuant.acuantcamera.camera.mrz.cameraone - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.ImageFormat -import android.graphics.Matrix -import android.graphics.Rect -import android.graphics.YuvImage -import android.util.SparseArray - -import com.google.android.gms.vision.Detector -import com.google.android.gms.vision.Frame -import com.google.android.gms.vision.barcode.Barcode - -import java.io.ByteArrayOutputStream - -class MrzDetector(private val mDelegate: Detector) : Detector() { - var frame: Bitmap? = null - private set - - override fun detect(frame: Frame): SparseArray { - val yuvImage = YuvImage(frame.grayscaleImageData.array(), ImageFormat.NV21, frame.metadata.width, frame.metadata.height, null) - val byteArrayOutputStream = ByteArrayOutputStream() - yuvImage.compressToJpeg(Rect(0, 0, frame.metadata.width, frame.metadata.height), 100, byteArrayOutputStream) - val jpegArray = byteArrayOutputStream.toByteArray() - this.frame = BitmapFactory.decodeByteArray(jpegArray, 0, jpegArray.size) - return mDelegate.detect(frame) - } - - override fun isOperational(): Boolean { - return mDelegate.isOperational - } - - override fun setFocus(id: Int): Boolean { - return mDelegate.setFocus(id) - } - - private fun rotateBitmap(source: Bitmap, angle: Float): Bitmap { - val matrix = Matrix() - matrix.postRotate(angle) - return Bitmap.createBitmap(source, 0, 0, source.width, source.height, matrix, true) - } - -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzFeedback.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzFeedback.kt deleted file mode 100644 index 495f1b2..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzFeedback.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.acuant.acuantcamera.camera.mrz.cameraone - -enum class MrzFeedback { - NoDocument, - SmallDocument, - BadDocument, - GoodDocument, - Barcode -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzGraphicTracker.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzGraphicTracker.kt deleted file mode 100644 index 8e2f871..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzGraphicTracker.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.acuant.acuantcamera.camera.mrz.cameraone - -import android.support.annotation.UiThread -import com.google.android.gms.vision.Detector -import com.google.android.gms.vision.Tracker -import com.google.android.gms.vision.barcode.Barcode - -/** - * Generic tracker which is used for tracking or reading a barcode (and can really be used for - * any type of item). This is used to receive newly detected items, add a graphical representation - * to an overlay, update the graphics as the item changes, and remove the graphics when the item - * goes away. - */ -internal class MrzGraphicTracker(private val mBarcodeUpdateListener: BarcodeUpdateListener) : Tracker() { - - /** - * Consume the item instance detected from an Activity or Fragment level by implementing the - * BarcodeUpdateListener interface method onBarcodeDetected. - */ - interface BarcodeUpdateListener { - @UiThread - fun onBarcodeDetected(barcode: Barcode?) - } - - /** - * Start tracking the detected item instance within the item overlay. - */ - override fun onNewItem(id: Int, item: Barcode?) { - //mBarcodeUpdateListener.onBarcodeDetected(item); - } - - /** - * Update the position/characteristics of the item within the overlay. - */ - override fun onUpdate(detectionResults: Detector.Detections?, item: Barcode?) { - mBarcodeUpdateListener.onBarcodeDetected(item) - } - - /** - * Hide the graphic when the corresponding object was not detected. This can happen for - * intermediate frames temporarily, for example if the object was momentarily blocked from - * view. - */ - override fun onMissing(detectionResults: Detector.Detections?) { - - } - - /** - * Called when the item is assumed to be gone for good. Remove the graphic annotation from - * the overlay. - */ - override fun onDone() { - - } -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzTrackerFactory.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzTrackerFactory.kt deleted file mode 100644 index 01cce28..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/camera/mrz/cameraone/MrzTrackerFactory.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.acuant.acuantcamera.camera.mrz.cameraone - -/* - * Copyright (C) The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import com.google.android.gms.vision.MultiProcessor -import com.google.android.gms.vision.Tracker -import com.google.android.gms.vision.barcode.Barcode - -/** - * Factory for creating a tracker and associated graphic to be associated with a new barcode. The - * multi-processor uses this factory to create barcode trackers as needed -- one for each barcode. - */ -internal class MrzTrackerFactory(private val listener: MrzGraphicTracker.BarcodeUpdateListener) : MultiProcessor.Factory { - - override fun create(barcode: Barcode): Tracker { - return MrzGraphicTracker(listener) - } - -} - 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 b5c07e6..7bd9d66 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/constant/Constants.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/constant/Constants.kt @@ -2,14 +2,10 @@ package com.acuant.acuantcamera.constant -@JvmField val REQUEST_CAMERA_PERMISSION = 1 -@JvmField val PIC_FILE_NAME = "pic.jpg" -@JvmField val ACUANT_EXTRA_IMAGE_URL = "imageUrl" -@JvmField val ACUANT_EXTRA_PDF417_BARCODE = "barCodeString" -@JvmField val ACUANT_EXTRA_IS_AUTO_CAPTURE = "isAutoCapture" -@JvmField val ACUANT_EXTRA_BORDER_ENABLED = "borderEnabled" -@JvmField val ACUANT_EXTRA_CAMERA_OPTIONS = "cameraOptions" -@JvmField val ACUANT_EXTRA_FACE_CAPTURE_OPTIONS = "faceCaptureOptions" -@JvmField val ACUANT_EXTRA_MRZ_RESULT = "mrzResult" -@JvmField val MINIMUM_DPI = 20 -@JvmField val MINIMUM_REQUIRED_DPI = 160 \ No newline at end of file +const val ACUANT_EXTRA_IMAGE_URL = "imageUrl" +const val ACUANT_EXTRA_PDF417_BARCODE = "barCodeString" +const val ACUANT_EXTRA_CAMERA_OPTIONS = "cameraOptions" +const val ACUANT_EXTRA_MRZ_RESULT = "mrzResult" +const val ACUANT_EXTRA_ERROR = "error" +const val RESULT_ERROR = -99 +const val MINIMUM_DPI = 20 // a random low number to filter out small detections due to noise but to not filter any documents even too small ones \ No newline at end of file diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/AcuantDetectorWorker.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/AcuantDetectorWorker.kt deleted file mode 100644 index 2cc170b..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/AcuantDetectorWorker.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.acuant.acuantcamera.detector - -import android.graphics.* -import android.os.AsyncTask -import com.acuant.acuantcamera.detector.document.AcuantDocumentDetector -import kotlin.concurrent.thread - -class AcuantDetectorWorker(private val detectors: List, private val bitmap: Bitmap, private val runDocumentDetection: Boolean = true): AsyncTask(){ - override fun doInBackground(vararg params: Void?): Void? { - detectors.forEach{ - if (runDocumentDetection || it !is AcuantDocumentDetector) { - thread { - it.detect(bitmap) - } - } - } - return null - } -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/BaseAcuantDetector.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/BaseAcuantDetector.kt deleted file mode 100644 index a11400d..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/BaseAcuantDetector.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.acuant.acuantcamera.detector - -abstract class BaseAcuantDetector : IAcuantDetector { - - private var lProcessing: Boolean = false - - override var isProcessing: Boolean - get() = lProcessing - set(value) {lProcessing = value} - - override fun clean() { - lProcessing = false - } -} \ No newline at end of file diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/DocumentFrameAnalyzer.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/DocumentFrameAnalyzer.kt new file mode 100644 index 0000000..ddf0312 --- /dev/null +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/DocumentFrameAnalyzer.kt @@ -0,0 +1,102 @@ +package com.acuant.acuantcamera.detector + +import android.graphics.* +import android.util.Size +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageProxy +import com.acuant.acuantcamera.constant.MINIMUM_DPI +import com.acuant.acuantimagepreparation.helper.ImageUtils +import com.acuant.acuantcamera.helper.PointsUtils +import com.acuant.acuantimagepreparation.AcuantImagePreparation +import com.acuant.acuantimagepreparation.model.DetectData +import com.google.mlkit.vision.barcode.BarcodeScannerOptions +import com.google.mlkit.vision.barcode.BarcodeScanning +import com.google.mlkit.vision.barcode.common.Barcode +import com.google.mlkit.vision.common.InputImage +import kotlin.concurrent.thread + +typealias DocumentFrameListener = (points: Array?, state: DocumentState, barcode: String?, detectTime: Long) -> Unit + +enum class DocumentState { NoDocument, TooFar, TooClose, GoodDocument } + +class DocumentFrameAnalyzer internal constructor(private val listener: DocumentFrameListener) : ImageAnalysis.Analyzer { + + private var runningThreads = 0 + private var disableDocDetect = false + + fun disableDocumentDetection() { + disableDocDetect = true + } + + //this is experimental annotation is required due to the weird ownership of the internal image + // essentially it is unsafe to close the image wrapped by the ImageProxy and by adding this + // annotation we acknowledge that + @androidx.camera.core.ExperimentalGetImage + override fun analyze(image: ImageProxy) { + //this should not happen, just a sanity check + if (runningThreads > 0) { + image.close() + return + } + val startTime = System.currentTimeMillis() + var state: DocumentState = DocumentState.NoDocument + var points: Array? = null + var barcode: String? = null + val mediaImage = image.image //don't close this one + runningThreads = 0 + if (mediaImage != null) { + runningThreads = if (disableDocDetect) 1 else 2 + + if (!disableDocDetect) { + val bitmap: Bitmap = ImageUtils.imageToBitmapWithoutClosingSource(mediaImage) + + //document detection + thread { + val origSize = Size(bitmap.width, bitmap.height) + val detectData = DetectData(bitmap) + val detectResult = AcuantImagePreparation.detect(detectData) + points = detectResult.points + state = when { + points == null || detectResult.dpi < MINIMUM_DPI || !detectResult.isCorrectAspectRatio -> DocumentState.NoDocument + PointsUtils.isTooClose(points, origSize, MAX_DIST) -> DocumentState.TooClose + !PointsUtils.isCloseEnough(points, origSize, MIN_DIST) -> DocumentState.TooFar + else -> DocumentState.GoodDocument + } + finishThread(points, state, barcode, startTime) + } + } + + //barcode detection + val barcodeInput = + InputImage.fromMediaImage(mediaImage, image.imageInfo.rotationDegrees) + barcodeScanner.process(barcodeInput).addOnSuccessListener { barcodes -> + if (barcodes.isNotEmpty()) { + barcode = barcodes[0].rawValue + } + }.addOnCompleteListener { //finally + finishThread(points, state, barcode, startTime) + image.close() + } + } else { + finishThread(points, state, barcode, startTime) + image.close() + } + } + + private fun finishThread(points: Array?, state: DocumentState, barcode: String?, startTime: Long) { + --runningThreads + if (runningThreads <= 0) { + listener(points, state, barcode, System.currentTimeMillis() - startTime) + } + } + + companion object { + private const val MIN_DIST = 0.75f + private const val MAX_DIST = 0.9f + + private val barcodeScanner = BarcodeScanning.getClient( + BarcodeScannerOptions.Builder() + .setBarcodeFormats(Barcode.FORMAT_PDF417) + .build()) + } +} \ No newline at end of file diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/IAcuantDetector.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/IAcuantDetector.kt deleted file mode 100644 index d600e65..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/IAcuantDetector.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.acuant.acuantcamera.detector - -import android.graphics.Bitmap - -interface IAcuantDetector{ - - var isProcessing: Boolean - - fun detect(bitmap: Bitmap?) - fun clean() -} \ No newline at end of file diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/ImageSaver.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/ImageSaver.kt deleted file mode 100644 index 588fecf..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/ImageSaver.kt +++ /dev/null @@ -1,180 +0,0 @@ -package com.acuant.acuantcamera.detector - -import android.graphics.* -import android.media.Image -import android.util.Log - -import android.graphics.ImageFormat -import android.graphics.YuvImage -import android.graphics.Bitmap.CompressFormat -import android.graphics.BitmapFactory -import android.support.media.ExifInterface -import com.acuant.acuantimagepreparation.AcuantImagePreparation -import org.json.JSONObject -import java.io.* - - -/** - * Saves a JPEG [Image] into the specified [File]. - */ -interface ImageSaveHandler{ - fun onSave() -} - -class ImageSaver( - /** - * The JPEG image - */ - private val orientation: Int, - private val image: Image, - - /** - * The file we save the image into. - */ - private val file: File, - private val callback: ImageSaveHandler - -) : Runnable { - - private var captureType = "NOT SPECIFIED (implementer's custom camera did not call right method)" - - constructor(orientation: Int, - image: Image, - file: File, - type: String, - callback: ImageSaveHandler - ) : this(orientation, image, file, callback) { - captureType = type - } - - override fun run() { - val buffer = image.planes[0].buffer - var bytes = ByteArray(buffer.remaining()) - buffer.get(bytes) - - //check if it needs to be rotated - if(orientation < DEGREES_TO_ROTATE_IMAGE) { - //code for rotating the image - val rotated = rotateImage(BitmapFactory.decodeByteArray(bytes, 0, bytes.size), 180f) - val stream = ByteArrayOutputStream() - rotated.compress(CompressFormat.JPEG, 100, stream) - rotated.recycle() - bytes = stream.toByteArray() - } - - var output: FileOutputStream? = null - try { - output = FileOutputStream(file).apply {write(bytes)} - addExif(file, captureType) - } catch (e: IOException) { - Log.e(TAG, e.toString()) - } finally { - image.close() - output?.let { - try { - it.close() - } catch (e: IOException) { - Log.e(TAG, e.toString()) - } - } - callback.onSave() - } - } - - - companion object { - /** - * Tag for the [Log]. - */ - private const val TAG = "ImageSaver" - private const val DEGREES_TO_ROTATE_IMAGE = 181; - - @JvmStatic fun imageToByteArray(image: Image, quality: Int = 50): ByteArray { - return NV21toJPEG(YUV420toNV21(image), image.getWidth(), image.getHeight(), 100) - } - - fun addExif(file: File, captureType: String) { - val exif = ExifInterface(file.absolutePath) - val json = JSONObject() - - - json.put(AcuantImagePreparation.CAPTURE_TYPE_TAG, captureType) - //further exif stuff can be added to the JSON here and read in the CroppingData constructor - - exif.setAttribute(ExifInterface.TAG_IMAGE_DESCRIPTION, json.toString()) - exif.saveAttributes() - } - - fun rotateImage(img: Bitmap, degree: Float): Bitmap { - val matrix = Matrix() - matrix.setRotate(degree) - val rotatedImg = Bitmap.createBitmap(img, 0, 0, img.width, img.height, matrix, true) - img.recycle() - return rotatedImg - } - - private fun NV21toJPEG(nv21: ByteArray, width: Int, height: Int, quality: Int): ByteArray { - val out = ByteArrayOutputStream() - val yuv = YuvImage(nv21, ImageFormat.NV21, width, height, null) - yuv.compressToJpeg(Rect(0, 0, width, height), quality, out) - return out.toByteArray() - } - - private fun YUV420toNV21(image: Image): ByteArray { - val crop = image.cropRect - val format = image.format - val width = crop.width() - val height = crop.height() - val planes = image.planes - val data = ByteArray(width * height * ImageFormat.getBitsPerPixel(format) / 8) - val rowData = ByteArray(planes[0].rowStride) - - var channelOffset = 0 - var outputStride = 1 - for (i in planes.indices) { - when (i) { - 0 -> { - channelOffset = 0 - outputStride = 1 - } - 1 -> { - channelOffset = width * height + 1 - outputStride = 2 - } - 2 -> { - channelOffset = width * height - outputStride = 2 - } - } - - val buffer = planes[i].buffer - val rowStride = planes[i].rowStride - val pixelStride = planes[i].pixelStride - - val shift = if (i == 0) 0 else 1 - val w = width shr shift - val h = height shr shift - buffer.position(rowStride * (crop.top shr shift) + pixelStride * (crop.left shr shift)) - for (row in 0 until h) { - val length: Int - if (pixelStride == 1 && outputStride == 1) { - length = w - buffer.get(data, channelOffset, length) - channelOffset += length - } else { - length = (w - 1) * pixelStride + 1 - buffer.get(rowData, 0, length) - for (col in 0 until w) { - data[channelOffset] = rowData[col * pixelStride] - channelOffset += outputStride - } - } - if (row < h - 1) { - buffer.position(buffer.position() + rowStride - length) - } - } - } - return data - } - } -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/MrzFrameAnalyzer.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/MrzFrameAnalyzer.kt new file mode 100644 index 0000000..fd961ae --- /dev/null +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/MrzFrameAnalyzer.kt @@ -0,0 +1,122 @@ +package com.acuant.acuantcamera.detector + +import android.content.Context +import android.graphics.* +import android.util.Size +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageProxy +import com.acuant.acuantimagepreparation.helper.ImageUtils +import com.acuant.acuantcamera.helper.MrzParser +import com.acuant.acuantcamera.helper.MrzResult +import com.acuant.acuantcamera.helper.PointsUtils +import com.acuant.acuantimagepreparation.AcuantImagePreparation +import com.acuant.acuantimagepreparation.model.DetectData +import com.googlecode.tesseract.android.TessBaseAPI +import java.lang.ref.WeakReference +import kotlin.concurrent.thread + +typealias MrzFrameListener = (points: Array?, result: MrzResult?, state: MrzState) -> Unit + +enum class MrzState { NoMrz, TooFar, GoodMrz } + +class MrzFrameAnalyzer internal constructor(contextWeak: WeakReference, private val listener: MrzFrameListener) : ImageAnalysis.Analyzer { + private var running = false + private var tryFlip = false + private val textRecognizer = TessBaseAPI() + private var internalStorage : String? = null + init { + val context = contextWeak.get() + if (context != null) { + internalStorage = context.filesDir?.absolutePath + textRecognizer.init(internalStorage, "ocrb") + textRecognizer.setVariable( + TessBaseAPI.VAR_CHAR_WHITELIST, + "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890<" + ) + } + } + + //this is experimental annotation is required due to the weird ownership of the internal image + // essentially it is unsafe to close the image wrapped by the ImageProxy and by adding this + // annotation we acknowledge that + @androidx.camera.core.ExperimentalGetImage + //must close ImageProxy in each exit path or camera will freeze + override fun analyze(image: ImageProxy) { + //this should not happen, just a sanity check + if (running) { + image.close() + return + } + var state: MrzState = MrzState.NoMrz + var points: Array? = null + var result: MrzResult? = null + val mediaImage = image.image //don't close this one + running = true + if (mediaImage != null) { + + val bitmap: Bitmap = ImageUtils.imageToBitmapWithoutClosingSource(mediaImage) + + //document detection + thread { + val origSize = Size(bitmap.width, bitmap.height) + val detectData = DetectData(bitmap) + val detectResult = AcuantImagePreparation.detectMrz(detectData) + points = detectResult.points + state = when { + points == null || !PointsUtils.isAligned(points) || !isAcceptableAspectRatio(points) -> MrzState.NoMrz + !PointsUtils.isCloseEnough(points, origSize, MIN_DIST) -> MrzState.TooFar + else -> MrzState.GoodMrz + } + if (state == MrzState.GoodMrz) { + + val croppedImage = AcuantImagePreparation.cropMrz(detectData, detectResult) + val processedImg = AcuantImagePreparation.threshold(croppedImage.image) + + val processedImgWithBorder = ImageUtils.addWhiteBorder(processedImg) + //TODO: this method could use improvement, it is too brute force of a solution + val processedImgFinal = if (tryFlip) { + val matrix = Matrix() + matrix.postRotate(180f) + tryFlip = false + Bitmap.createBitmap(processedImgWithBorder, 0, 0, processedImgWithBorder.width, processedImgWithBorder.height, matrix, true) + } else { + tryFlip = true + processedImgWithBorder + } + textRecognizer.setImage(processedImgFinal) + + val mrz = textRecognizer.utF8Text + + if (mrz.isNotBlank()) { + result = parser.parseMrz(mrz) + } + } + finishDetect(points, result, state) + } + } else { + finishDetect(points, result, state) + } + + image.close() + } + + private fun finishDetect(points: Array?, result: MrzResult?, state: MrzState) { + running = false + listener(points, result, state) + } + + companion object { + private val parser = MrzParser() + private const val MIN_DIST = 0.75f + + fun isAcceptableAspectRatio(points: Array?) : Boolean { + if (points == null) + return false + val ratio = PointsUtils.distance(points[0], points[3]) / PointsUtils.distance( + points[0], + points[1] + ) + return ratio > 4f && ratio < 10f + } + } +} \ No newline at end of file diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/barcode/AcuantBarcodeDetector.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/barcode/AcuantBarcodeDetector.kt deleted file mode 100644 index 045c147..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/barcode/AcuantBarcodeDetector.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.acuant.acuantcamera.detector.barcode - -import android.content.Context -import android.graphics.Bitmap -import android.util.Log -import com.acuant.acuantcamera.detector.BaseAcuantDetector -import com.google.android.gms.vision.barcode.BarcodeDetector -import com.acuant.acuantcamera.detector.barcode.tracker.BarcodeTrackerFactory -import com.google.android.gms.vision.MultiProcessor -import com.google.android.gms.vision.Frame - -//NOTE: this class does not use the isProcessing flag due to difficulties with how the gms barcode detector does its callbacks -class AcuantBarcodeDetector(context: Context, callback: AcuantBarcodeDetectorHandler) : BaseAcuantDetector() { - private lateinit var barcodeDetector: BarcodeDetector - private var isInitialized = false - - init { - try { - barcodeDetector = BarcodeDetector.Builder(context).build() - barcodeDetector.setProcessor(MultiProcessor.Builder(BarcodeTrackerFactory(callback)).build()) - isInitialized = true - } catch (e: Exception) { - e.printStackTrace() - Log.e("barcode", "Error initializing barcode") - } - } - - override fun detect(bitmap: Bitmap?) { - if (isInitialized && bitmap != null) { - try { - barcodeDetector.receiveFrame(Frame.Builder() - .setBitmap(bitmap) - .build()) - } - catch (e: Exception) { - Log.e("Barcode", e.toString()) - } - } - } - - override fun clean() { - super.clean() - if (isInitialized) { - isInitialized = false - barcodeDetector.release() - } - } -} \ No newline at end of file diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/barcode/AcuantBarcodeDetectorHandler.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/barcode/AcuantBarcodeDetectorHandler.kt deleted file mode 100644 index 9f9fdb4..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/barcode/AcuantBarcodeDetectorHandler.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.acuant.acuantcamera.detector.barcode - -interface AcuantBarcodeDetectorHandler{ - fun onBarcodeDetected(barcode: String) -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/barcode/tracker/AcuantBarcodeTracker.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/barcode/tracker/AcuantBarcodeTracker.kt deleted file mode 100644 index 6237636..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/barcode/tracker/AcuantBarcodeTracker.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.acuant.acuantcamera.detector.barcode.tracker - -/* - * Copyright (C) The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -import android.util.Log -import com.acuant.acuantcamera.detector.barcode.AcuantBarcodeDetectorHandler -import com.google.android.gms.vision.Tracker -import com.google.android.gms.vision.barcode.Barcode - -/** - * Generic tracker which is used for tracking or reading a barcode (and can really be used for - * any type of item). This is used to receive newly detected items, add a graphical representation - * to an overlay, update the graphics as the item changes, and remove the graphics when the item - * goes away. - */ -class AcuantBarcodeTracker internal constructor(private val mBarcodeUpdateListener: AcuantBarcodeDetectorHandler) : Tracker() { - - /** - * Start tracking the detected item instance within the item overlay. - */ - override fun onNewItem(id: Int, item: Barcode?) { - if (item != null && item.format == Barcode.PDF417) { - mBarcodeUpdateListener.onBarcodeDetected(item.rawValue) - } - } -} \ No newline at end of file diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/barcode/tracker/AcuantBarcodeTrackerFactory.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/barcode/tracker/AcuantBarcodeTrackerFactory.kt deleted file mode 100644 index 3686ba3..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/barcode/tracker/AcuantBarcodeTrackerFactory.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.acuant.acuantcamera.detector.barcode.tracker - -/* - * Copyright (C) The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -import com.acuant.acuantcamera.detector.barcode.AcuantBarcodeDetectorHandler - -import com.google.android.gms.vision.MultiProcessor -import com.google.android.gms.vision.Tracker -import com.google.android.gms.vision.barcode.Barcode - -/** - * Factory for creating a tracker and associated graphic to be associated with a new barcode. The - * multi-processor uses this factory to create barcode trackers as needed -- one for each barcode. - */ -internal class BarcodeTrackerFactory(private val handler: AcuantBarcodeDetectorHandler) : MultiProcessor.Factory { - - override fun create(barcode: Barcode): Tracker { - return AcuantBarcodeTracker(handler) - } - -} \ No newline at end of file diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/document/AcuantDocumentDetector.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/document/AcuantDocumentDetector.kt deleted file mode 100644 index 72e0ae4..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/document/AcuantDocumentDetector.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.acuant.acuantcamera.detector.document - -import android.graphics.Bitmap -import com.acuant.acuantcamera.detector.BaseAcuantDetector -import com.acuant.acuantcommon.model.Image -import com.acuant.acuantimagepreparation.AcuantImagePreparation -import com.acuant.acuantimagepreparation.model.DetectData - -class AcuantDocumentDetector constructor (private val callback: AcuantDocumentDetectorHandler) : BaseAcuantDetector() { - @Suppress("DEPRECATION") - @Deprecated("Handler class had a spelling mistake, pass in AcuantDocumentDetectorHandler instead.") - constructor (callback: AcuantDocumentDectectorHandler) : this (callback as AcuantDocumentDetectorHandler) - - override fun detect(bitmap: Bitmap?) { - if (!isProcessing) { - isProcessing = true - val cropStart = System.currentTimeMillis() - var croppedImage: Image? = null - - if (bitmap != null) { - val data = DetectData(bitmap) - - try { - croppedImage = AcuantImagePreparation.detect(data) - } catch (e: Exception) { - e.printStackTrace() - isProcessing = false - } - } - callback.onDetected(croppedImage, System.currentTimeMillis() - cropStart) - } - } -} \ No newline at end of file diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/document/AcuantDocumentDetectorHandler.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/document/AcuantDocumentDetectorHandler.kt deleted file mode 100644 index 1395526..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/document/AcuantDocumentDetectorHandler.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.acuant.acuantcamera.detector.document - -import com.acuant.acuantcommon.model.Image - - -interface AcuantDocumentDetectorHandler : AcuantDocumentDectectorHandler - -@Deprecated("Class had a spelling mistake, use AcuantDocumentDetectorHandler instead.") -interface AcuantDocumentDectectorHandler{ - fun onDetected(croppedImage: Image?, cropDuration: Long) -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/ocr/AcuantOcrDetector.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/ocr/AcuantOcrDetector.kt deleted file mode 100644 index 22e563f..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/ocr/AcuantOcrDetector.kt +++ /dev/null @@ -1,121 +0,0 @@ -package com.acuant.acuantcamera.detector.ocr - -import android.content.Context -import com.acuant.acuantimagepreparation.AcuantImagePreparation -import java.lang.Exception -import android.graphics.* -import com.acuant.acuantcommon.model.Image -import android.graphics.Bitmap.CompressFormat -import com.acuant.acuantcamera.detector.BaseAcuantDetector -import com.acuant.acuantimagepreparation.model.DetectData -import com.googlecode.tesseract.android.TessBaseAPI -import java.io.File -import java.io.FileOutputStream - - -class AcuantOcrDetector(context: Context, private val callback: AcuantOcrDetectorHandler): BaseAcuantDetector() { - private val textRecognizer = TessBaseAPI() - private var isInitialized = true - private var extStorage : String? = null - private var tryFlip = false - - init { - extStorage = context.getExternalFilesDir(null)?.absolutePath - textRecognizer.init(extStorage, "ocrb") - textRecognizer.setVariable(TessBaseAPI.VAR_CHAR_WHITELIST, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890<") - } - - @Suppress("ConstantConditionIf") - override fun detect(bitmap: Bitmap?) { - var processedImg : Bitmap? = null - var processedImgWithBorder : Bitmap? = null - var processedImgFinal : Bitmap? = null - var croppedImage : Image? = null - if (isInitialized && bitmap != null && !isProcessing) { - try { - isProcessing = true - val data = DetectData(bitmap) - - croppedImage = AcuantImagePreparation.detectMrz(data) - - if (croppedImage.isPassport) { - - croppedImage = AcuantImagePreparation.cropMrz(data, croppedImage) - - callback.onPointsDetected(croppedImage.points) - - processedImg = AcuantImagePreparation.threshold(croppedImage.image) - - if (saveDebug) { - try { - var output = FileOutputStream(File(extStorage, "preThresh.jpg")) - croppedImage.image.compress(CompressFormat.JPEG, 100, output) - output.flush() - output.close() - output = FileOutputStream(File(extStorage, "postThresh.jpg")) - processedImg.compress(CompressFormat.JPEG, 100, output) - output.flush() - output.close() - } catch (e: Exception) { - e.printStackTrace() - } - } - - processedImgWithBorder = addWhiteBorder(processedImg) - //TODO: this method could use improvement, it is too brute force of a solution - processedImgFinal = if (tryFlip) { - val matrix = Matrix() - matrix.postRotate(180f) - tryFlip = false - Bitmap.createBitmap(processedImgWithBorder, 0, 0, processedImgWithBorder.width, processedImgWithBorder.height, matrix, true) - } else { - tryFlip = true - processedImgWithBorder - } - textRecognizer.setImage(processedImgFinal) - - val mrz = textRecognizer.utF8Text - - if (!mrz.isBlank()) { - callback.onOcrDetected(mrz) - } - else { - callback.onOcrDetected("") - } - } else { - callback.onOcrDetected(null) - callback.onPointsDetected(null) - } - } catch (e:Exception) { - callback.onOcrDetected(null) - callback.onPointsDetected(null) - e.printStackTrace() - } finally { - processedImg?.recycle() - processedImgWithBorder?.recycle() - processedImgFinal?.recycle() - croppedImage?.image?.recycle() - } - } - } - - private fun addWhiteBorder(bmp: Bitmap, borderSize: Int = 10): Bitmap { - val bmpWithBorder = Bitmap.createBitmap(bmp.width + borderSize * 2, bmp.height + borderSize * 2, Bitmap.Config.ARGB_8888) - val canvas = Canvas(bmpWithBorder) - canvas.drawColor(Color.WHITE) - canvas.drawBitmap(bmp, borderSize.toFloat(), borderSize.toFloat(), null) - return bmpWithBorder - } - - override fun clean() { - super.clean() - if (isInitialized) { - isInitialized = false - textRecognizer.end() - } - } - - companion object { - private const val saveDebug = false - } -} \ No newline at end of file diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/ocr/AcuantOcrDetectorHandler.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/ocr/AcuantOcrDetectorHandler.kt deleted file mode 100644 index 6b79619..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/detector/ocr/AcuantOcrDetectorHandler.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.acuant.acuantcamera.detector.ocr - -import android.graphics.Point -import android.support.annotation.UiThread - -interface AcuantOcrDetectorHandler{ - @UiThread - fun onOcrDetected(textBlock: String?) - - @UiThread - fun onPointsDetected(points: Array?) -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/AutoFitTextureView.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/AutoFitTextureView.kt deleted file mode 100644 index ece4676..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/AutoFitTextureView.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.acuant.acuantcamera.helper - -import android.content.Context -import android.util.AttributeSet -import android.view.TextureView - -/** - * A [TextureView] that can be adjusted to a specified aspect ratio. - */ -class AutoFitTextureView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyle: Int = 0 -) : TextureView(context, attrs, defStyle) { - - private var mRatioWidth = 0 - private var mRatioHeight = 0 - - /** - * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio - * calculated from the parameters. Note that the actual sizes of parameters don't matter, that - * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result. - * - * @param width Relative horizontal size - * @param height Relative vertical size - */ - fun setAspectRatio(width: Int, height: Int) { - require(!(width < 0 || height < 0)) { "Size cannot be negative." } - mRatioWidth = width - mRatioHeight = height - requestLayout() - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec) - val width = MeasureSpec.getSize(widthMeasureSpec) - val height = MeasureSpec.getSize(heightMeasureSpec) - if (0 == mRatioWidth || 0 == mRatioHeight) { - setMeasuredDimension(width, height) - } else { - if (width/mRatioWidth.toFloat() < height/mRatioHeight.toFloat()) { - setMeasuredDimension(width, (mRatioHeight * width/mRatioWidth.toFloat()).toInt()) - } else { - setMeasuredDimension((mRatioWidth * height/mRatioHeight.toFloat()).toInt(), height) - } - } - } -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/CompareSizesByArea.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/CompareSizesByArea.kt deleted file mode 100644 index acc27f9..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/CompareSizesByArea.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.acuant.acuantcamera.helper - -import android.util.Size -import java.lang.Long.signum - -import java.util.Comparator - -/** - * Compares two `Size`s based on their areas. - */ -internal class CompareSizesByArea : Comparator { - - // We cast here to ensure the multiplications won't overflow - override fun compare(lhs: Size, rhs: Size) = - signum(lhs.width.toLong() * lhs.height - rhs.width.toLong() * rhs.height) - -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/ConfirmationDialog.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/ConfirmationDialog.kt deleted file mode 100644 index 8f1c418..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/ConfirmationDialog.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.acuant.acuantcamera.helper - -import android.Manifest -import android.app.Dialog -import android.content.Context -import android.os.Bundle -import android.support.v4.app.DialogFragment -import android.support.v7.app.AlertDialog -import com.acuant.acuantcamera.R -import com.acuant.acuantcamera.constant.REQUEST_CAMERA_PERMISSION - -/** - * Shows OK/Cancel confirmation dialog about camera permission. - */ -class ConfirmationDialog : DialogFragment() { - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = - AlertDialog.Builder(activity as Context) - .setMessage(R.string.request_permission) - .setPositiveButton(android.R.string.ok) { _, _ -> - parentFragment!!.requestPermissions(arrayOf(Manifest.permission.CAMERA), - REQUEST_CAMERA_PERMISSION) - } - .setNegativeButton(android.R.string.cancel) { _, _ -> - parentFragment!!.activity?.finish() - } - .create() -} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/ErrorDialog.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/ErrorDialog.kt deleted file mode 100644 index e51c4b0..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/ErrorDialog.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.acuant.acuantcamera.helper - -import android.app.Dialog -import android.content.Context -import android.os.Bundle -import android.support.v4.app.DialogFragment -import android.support.v7.app.AlertDialog - -/** - * Shows an error message dialog. - */ -class ErrorDialog : DialogFragment() { - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = - AlertDialog.Builder(activity as Context) - .setMessage(arguments?.getString(ARG_MESSAGE)) - .setPositiveButton(android.R.string.ok) { _, _ -> activity!!.finish() } - .create() - - companion object { - - @JvmStatic private val ARG_MESSAGE = "message" - - @JvmStatic fun newInstance(message: String): ErrorDialog = ErrorDialog().apply { - arguments = Bundle().apply { putString(ARG_MESSAGE, message) } - } - } - -} \ No newline at end of file diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/MrzParser.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/MrzParser.kt index 819202d..22436ba 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/MrzParser.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/MrzParser.kt @@ -1,6 +1,6 @@ package com.acuant.acuantcamera.helper -class MrzParser{ +class MrzParser { companion object { private const val FILLER = '<' @@ -8,7 +8,7 @@ class MrzParser{ private const val CHECK_SUM_ARRAY = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" } - fun parseMrz(mrz:String): MrzResult?{ + internal fun parseMrz(mrz:String): MrzResult? { val mrzWithNoSpaces = mrz.replace(" ", "") val mrzLinesTmp = mrzWithNoSpaces.split('\n') val mrzLines: MutableList = mutableListOf() @@ -19,16 +19,13 @@ class MrzParser{ } if (mrzLines.size == 2) { - //Log.d("MRZLOG", "Follows:\n" + mrzLines[0] + "\n" +mrzLines[1]) var result = parseFirstLineOfTwoLine(mrzLines[0]) //not checking result of first line as it is not strictly needed for echip reading result = parseSecondLineOfTwoLine(mrzLines[1], result) result?.threeLineMrz = false - //Log.d("MRZLOG", " " + result?.checkSumResult1 + ", "+ result?.checkSumResult2 +", "+ result?.checkSumResult3 +", "+ result?.checkSumResult4 +", "+ result?.checkSumResult5) return result } else if (mrzLines.size == 3) { - //Log.d("MRZLOG", "Follows:\n" + mrzLines[0] + "\n" +mrzLines[1]) var result = parseFirstLineOfThreeLine(mrzLines[0]) if (result != null) { result = parseSecondLineOfThreeLine(mrzLines[1], result) diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/MrzResult.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/MrzResult.kt index 55e094f..2f11b13 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/MrzResult.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/MrzResult.kt @@ -2,20 +2,25 @@ package com.acuant.acuantcamera.helper import java.io.Serializable -data class MrzResult(var surName:String = "", - var givenName:String= "", - var country:String= "", - var passportNumber: String= "", - var nationality:String= "", - var dob: String= "", - var gender: String= "", - var passportExpiration: String= "", - var personalDocNumber: String= "", - internal var optionalField1: String= "", - var checkSumResult1: Boolean = false, - var checkSumResult2: Boolean = false, - var checkSumResult3: Boolean = false, - var checkSumResult4: Boolean = false, - var checkSumResult5: Boolean = false, - var threeLineMrz: Boolean = false, - internal var checkChar1: Char = '<'): Serializable +data class MrzResult( + var surName:String = "", + var givenName:String= "", + var country:String= "", + var passportNumber: String= "", + var nationality:String= "", + var dob: String= "", + var gender: String= "", + var passportExpiration: String= "", + var personalDocNumber: String= "", + internal var optionalField1: String= "", + var checkSumResult1: Boolean = false, + var checkSumResult2: Boolean = false, + var checkSumResult3: Boolean = false, + var checkSumResult4: Boolean = false, + var checkSumResult5: Boolean = false, + var threeLineMrz: Boolean = false, + internal var checkChar1: Char = '<' +): Serializable { + val allCheckSumsPassed: Boolean + get() = checkSumResult1 && checkSumResult2 && checkSumResult3 && checkSumResult4 && checkSumResult5 +} diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/PointsUtils.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/PointsUtils.kt new file mode 100644 index 0000000..3992ec7 --- /dev/null +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/helper/PointsUtils.kt @@ -0,0 +1,114 @@ +package com.acuant.acuantcamera.helper + +import android.graphics.Point +import android.util.Log +import android.util.Size +import android.view.ViewGroup +import com.acuant.acuantcamera.overlay.BaseRectangleView +import kotlin.math.* + +object PointsUtils { + + internal fun isAligned(points: Array?) : Boolean { + if (points == null || points.size != 4) + return false + val val1 = distance(points[0], points[2]) + val val2 = distance(points[1], points[3]) + return abs(val1 - val2) < 15 + } + + internal fun isTooClose(points: Array?, screenSize: Size, maxDist: Float): Boolean { + if (points != null) { + val shortSide = min( + distance(points[0], points[1]), + distance(points[0], points[3]) + ) + val largeSide = max( + distance(points[0], points[1]), + distance(points[0], points[3]) + ) + val screenShortSide = min(screenSize.width, screenSize.height).toFloat() + val screenLargeSide = max(screenSize.width, screenSize.height).toFloat() + + if (shortSide > maxDist * screenShortSide || largeSide > maxDist * screenLargeSide) { + return true + } + } + return false + } + + internal fun isCloseEnough(points: Array?, screenSize: Size, minDist: Float): Boolean { + if (points != null) { + val shortSide = min(distance(points[0], points[1]), distance(points[0], points[3])) + val largeSide = max(distance(points[0], points[1]), distance(points[0], points[3])) + val screenShortSide = min(screenSize.width, screenSize.height).toFloat() + val screenLargeSide = max(screenSize.width, screenSize.height).toFloat() + + if (shortSide > minDist * screenShortSide || largeSide > minDist * screenLargeSide) { + return true + } + } + return false + } + + internal fun distance(pointA: Point, pointB: Point): Float { + return sqrt( (pointA.x - pointB.x).toFloat().pow(2) + (pointA.y - pointB.y).toFloat().pow(2)) + } + + internal fun fixPoints(points: Array) : Array { + if (points.size == 4) { + if(points[0].y > points[2].y && points[0].x < points[2].x) { + //rotate 2 + var tmp = points[0] + points[0] = points[2] + points[2] = tmp + tmp = points[1] + points[1] = points[3] + points[3] = tmp + } else if (points[0].y > points[2].y && points[0].x > points[2].x) { + //rotate 3 + val tmp = points[0] + points[0] = points[1] + points[1] = points[2] + points[2] = points[3] + points[3] = tmp + } else if (points[0].y < points[2].y && points[0].x < points[2].x) { + //rotate 1 + val tmp = points[0] + points[0] = points[3] + points[3] = points[2] + points[2] = points[1] + points[1] = tmp + } + } + return points + } + + internal fun scalePoints(points: Array, camContainer: ViewGroup?, analyzerSize: Size?, previewSize: ViewGroup?, rectangleView: BaseRectangleView?) : Array { + if (camContainer != null && previewSize != null && analyzerSize != null) { + + val offset = ((camContainer.height - previewSize.height) / 2 ) + val scaledPointY: Float = previewSize.height.toFloat() / analyzerSize.width.toFloat() + val scaledPointX: Float = previewSize.width.toFloat() / analyzerSize.height.toFloat() + rectangleView?.setWidth(camContainer.width.toFloat()) + + points.onEach { + it.x = (it.x * scaledPointX).toInt() + it.y = (it.y * scaledPointY).toInt() + it.x += offset + } + } + + return points + } + + fun correctDirection(points: Array?, view: ViewGroup?): Boolean { + if (view == null || points == null || points.size != 4) + return false + return if (view.width > view.height) { + distance(points[0], points[1]) > distance(points[0], points[3]) + } else { + distance(points[0], points[1]) < distance(points[0], points[3]) + } + } +} \ No newline at end of file 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 b65eb79..e66f4f9 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/initializer/MrzCameraInitializer.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/initializer/MrzCameraInitializer.kt @@ -5,7 +5,7 @@ import android.util.Log import com.acuant.acuantcommon.initializer.IAcuantPackage import com.acuant.acuantcommon.initializer.IAcuantPackageCallback import com.acuant.acuantcommon.model.Credential -import com.acuant.acuantcommon.model.Error +import com.acuant.acuantcommon.model.AcuantError import com.acuant.acuantcommon.model.ErrorCodes import com.acuant.acuantcommon.model.ErrorDescriptions import java.io.* @@ -18,21 +18,22 @@ class MrzCameraInitializer : IAcuantPackage { if(credential.secureAuthorizations != null) { try { initializeOcr(context) - callback.onInitializeSuccess() } catch (e: Exception) { e.printStackTrace() callback.onInitializeFailed( - listOf(Error(ErrorCodes.ERROR_FailedToLoadOcrFiles, - ErrorDescriptions.ERROR_DESC_FailedToLoadOcrFiles))) + listOf(AcuantError(ErrorCodes.ERROR_FailedToLoadOcrFiles, + ErrorDescriptions.ERROR_DESC_FailedToLoadOcrFiles, e.toString()))) + return } + callback.onInitializeSuccess() } else { - callback.onInitializeFailed(listOf(Error(ErrorCodes.ERROR_InvalidCredentials, ErrorDescriptions.ERROR_DESC_InvalidCredentials))) + callback.onInitializeFailed(listOf(AcuantError(ErrorCodes.ERROR_InvalidCredentials, ErrorDescriptions.ERROR_DESC_InvalidCredentials))) } } @Throws(IOException::class) private fun initializeOcr(context: Context) { - val tessDataDirectory = File(context.getExternalFilesDir(null)?.absolutePath + "/tessdata") + val tessDataDirectory = File(context.filesDir?.absolutePath + "/tessdata") if(!tessDataDirectory.exists()){ tessDataDirectory.mkdirs() } @@ -55,7 +56,7 @@ class MrzCameraInitializer : IAcuantPackage { var out: OutputStream? = null try { inputSteam = assetManager.open("tesseract/$filename") - val outFile = File(context.getExternalFilesDir(null)?.absolutePath + "/tessdata/", filename) + val outFile = File(context.filesDir?.absolutePath + "/tessdata/", filename) out = FileOutputStream(outFile) copyFile(inputSteam, out) } catch (e: IOException) { diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/interfaces/IAcuantSavedImage.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/interfaces/IAcuantSavedImage.kt new file mode 100644 index 0000000..7b997d4 --- /dev/null +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/interfaces/IAcuantSavedImage.kt @@ -0,0 +1,7 @@ +package com.acuant.acuantcamera.interfaces + +import com.acuant.acuantcommon.background.AcuantListener + +interface IAcuantSavedImage: AcuantListener { + fun onSaved(uri: String) +} \ 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 new file mode 100644 index 0000000..566e27e --- /dev/null +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/interfaces/ICameraActivityFinish.kt @@ -0,0 +1,11 @@ +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?) + fun onCameraDone(mrzResult: MrzResult) + fun onCameraDone(barCodeString: String) + fun onCancel() +} \ No newline at end of file diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/AcuantOrientationListener.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/AcuantOrientationListener.kt deleted file mode 100644 index 7878d06..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/AcuantOrientationListener.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.acuant.acuantcamera.overlay - -import android.content.Context -import android.hardware.SensorManager -import android.view.OrientationEventListener -import android.view.View -import android.widget.ImageView -import android.widget.TextView -import java.lang.ref.WeakReference - -class AcuantOrientationListener(context: Context, private val textView: WeakReference, private val imageView: WeakReference? = null) : OrientationEventListener(context, SensorManager.SENSOR_DELAY_UI) { - var previousAngle = 270 - override fun onOrientationChanged(orientation: Int) { - val textViewLocal = textView.get() - val imageViewLocal = imageView?.get() - if(orientation in 30..150 && previousAngle !in 30..150){ - previousAngle = orientation - rotateView(textViewLocal, 90f, 270f) - rotateView(imageViewLocal, 90f, 270f) - } - else if(orientation in 210..330 && previousAngle !in 210..330){ - previousAngle = orientation - rotateView(textViewLocal, 270f, 90f) - rotateView(imageViewLocal, 270f, 90f) - } - } - private fun rotateView(view: View?, startDeg:Float, endDeg:Float) { - if(view != null) { - view.rotation = startDeg - view.animate().rotation(endDeg).start() - } - } -} \ No newline at end of file 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 4af8cf8..4236939 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/BaseRectangleView.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/BaseRectangleView.kt @@ -9,7 +9,7 @@ import android.util.AttributeSet import android.view.View import android.view.animation.LinearInterpolator -abstract class BaseRectangleView(context: Context, attr: AttributeSet?) : View(context, attr), ISetFromState { +abstract class BaseRectangleView(context: Context, attr: AttributeSet?) : View(context, attr) { internal val paint: Paint = Paint() internal val paintBracket: Paint = Paint() @@ -98,14 +98,13 @@ abstract class BaseRectangleView(context: Context, attr: AttributeSet?) : View(c paint.color = paintColorAlign paint.style = Paint.Style.STROKE paint.strokeWidth = 5.0f - } - fun setWidth(width: Float){ + fun setWidth(width: Float) { textureViewWidth = width } - override fun setAndDrawPoints(points:Array?){ + fun setAndDrawPoints(points:Array?){ if(this.points != null) { oldPoints = this.points } @@ -147,13 +146,12 @@ abstract class BaseRectangleView(context: Context, attr: AttributeSet?) : View(c } override fun onDraw(canvas: Canvas) { - if (animateTarget && points != null && points!!.size == 4) { canvas.save() // need to restore after drawing canvas.translate(textureViewWidth, 0.0f) // reset where 0,0 is located canvas.scale(-1.0f, 1.0f) // invert - if(drawBox) { + if (drawBox) { paint.style = Paint.Style.FILL paint.alpha = 80 @@ -169,7 +167,6 @@ abstract class BaseRectangleView(context: Context, attr: AttributeSet?) : View(c } private fun drawBrackets(canvas: Canvas) { - paintBracket.style = Paint.Style.STROKE paintBracket.strokeCap = bracketPaintCap paintBracket.alpha = 0xFF @@ -203,7 +200,6 @@ abstract class BaseRectangleView(context: Context, attr: AttributeSet?) : View(c } private fun drawBracketsFromCords(canvas: Canvas, x0 : Float, y0 : Float, x1 : Float, y1 : Float, x2 : Float, y2 : Float, x3 : Float, y3 : Float) { - // top-left corner drawn canvas.drawLine((y3 - bracketWidth / bracketWidthDivider), x3, (y3 + bracketLengthInHorizontal), x3, paintBracket) canvas.drawLine(y3, (x3 - bracketWidth / bracketWidthDivider), y3, (x3+ bracketLengthInVertical), paintBracket) @@ -219,40 +215,23 @@ abstract class BaseRectangleView(context: Context, attr: AttributeSet?) : View(c // bottom-left corner drawn canvas.drawLine((y0 - bracketWidth / bracketWidthDivider), x0, (y0 + bracketLengthInHorizontal), x0, paintBracket) canvas.drawLine(y0, (x0 - bracketWidth / bracketWidthDivider), y0, (x0 - bracketLengthInVertical), paintBracket) - - } - - open fun end() { - bracketAnimator?.cancel() } - override fun onDetachedFromWindow() { bracketAnimator?.cancel() super.onDetachedFromWindow() } - /** - * @param width - * @param height - * @return - */ private fun calculatePreviewFrame(width: Int, height: Int): Rect { - // it will be calculated a frame where the croppedCard's image has to fit. - - // width and height rectangle where the frame has to fit, including margins val widthLessMargin = width - 2 * defaultBracketMarginWidth val heightLessMargin = height - 2 * defaultBracketMarginHeight - - // calculate widthBracket, heightBracket, the width and height of - // the brackets, the max rectangle with the given aspect ratio in - // the width and height window. val left: Int val top: Int val right: Int val bottom: Int var widthBracket: Int var heightBracket: Int + heightBracket = (widthLessMargin / this.cardRatio).toInt() widthBracket = widthLessMargin if (heightBracket > heightLessMargin) { @@ -260,7 +239,6 @@ abstract class BaseRectangleView(context: Context, attr: AttributeSet?) : View(c widthBracket = (heightBracket * this.cardRatio).toInt() } - // center the frame right = (widthLessMargin + widthBracket) / 2 + defaultBracketMarginWidth top = (heightLessMargin - heightBracket) / 2 + defaultBracketMarginHeight left = (widthLessMargin - widthBracket) / 2 + defaultBracketMarginWidth 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 53ac557..8331e72 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/DocRectangleView.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/DocRectangleView.kt @@ -1,39 +1,37 @@ package com.acuant.acuantcamera.overlay import android.content.Context -import android.graphics.Point import android.util.AttributeSet -import com.acuant.acuantcamera.camera.AcuantBaseCameraFragment +import com.acuant.acuantcamera.camera.document.DocumentCameraState class DocRectangleView(context: Context, attr: AttributeSet?) : BaseRectangleView(context, attr) { - - override fun setViewFromState(state: AcuantBaseCameraFragment.CameraState) { + fun setViewFromState(state: DocumentCameraState) { when(state) { - AcuantBaseCameraFragment.CameraState.MoveCloser -> { + DocumentCameraState.MoveCloser -> { setDrawBox(false) paint.color = paintColorCloser paintBracket.color = paintColorBracketCloser animateTarget = false } - AcuantBaseCameraFragment.CameraState.NotInFrame -> { + DocumentCameraState.MoveBack -> { setDrawBox(false) paint.color = paintColorCloser paintBracket.color = paintColorBracketCloser animateTarget = false } - AcuantBaseCameraFragment.CameraState.Hold -> { + DocumentCameraState.CountingDown -> { setDrawBox(true) paint.color = paintColorHold paintBracket.color = paintColorBracketHold animateTarget = true } - AcuantBaseCameraFragment.CameraState.Steady -> { + DocumentCameraState.HoldSteady -> { setDrawBox(true) paint.color = paintColorHold paintBracket.color = paintColorBracketHold animateTarget = true } - AcuantBaseCameraFragment.CameraState.Capturing -> { + DocumentCameraState.Capturing -> { setDrawBox(true) paint.color = paintColorCapturing paintBracket.color = paintColorBracketCapturing @@ -47,42 +45,4 @@ class DocRectangleView(context: Context, attr: AttributeSet?) : BaseRectangleVie } } } - - companion object { - - internal fun fixPoints(points: Array) : Array { - val fixedPoints = points.copyOf() - if(fixedPoints.size == 4) { - if(fixedPoints[0].y > fixedPoints[2].y && fixedPoints[0].x < fixedPoints[2].x) { - //rotate 2 - var tmp = fixedPoints[0] - fixedPoints[0] = fixedPoints[2] - fixedPoints[2] = tmp - - tmp = fixedPoints[1] - fixedPoints[1] = fixedPoints[3] - fixedPoints[3] = tmp - - } else if(fixedPoints[0].y > fixedPoints[2].y && fixedPoints[0].x > fixedPoints[2].x) { - //rotate 3 - val tmp = fixedPoints[0] - fixedPoints[0] = fixedPoints[1] - fixedPoints[1] = fixedPoints[2] - fixedPoints[2] = fixedPoints[3] - fixedPoints[3] = tmp - - } else if(fixedPoints[0].y < fixedPoints[2].y && fixedPoints[0].x < fixedPoints[2].x) { - //rotate 1 - val tmp = fixedPoints[0] - fixedPoints[0] = fixedPoints[3] - fixedPoints[3] = fixedPoints[2] - fixedPoints[2] = fixedPoints[1] - fixedPoints[1] = tmp - - } - } - return fixedPoints - } - } - } \ No newline at end of file diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/ISetFromState.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/ISetFromState.kt deleted file mode 100644 index 18aca35..0000000 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/ISetFromState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.acuant.acuantcamera.overlay - -import android.graphics.Point -import com.acuant.acuantcamera.camera.AcuantBaseCameraFragment - -interface ISetFromState { - fun setViewFromState(state: AcuantBaseCameraFragment.CameraState) - fun setAndDrawPoints(points:Array?) -} \ No newline at end of file diff --git a/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/MrzRectangleView.kt b/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/MrzRectangleView.kt index f84923e..e248313 100644 --- a/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/MrzRectangleView.kt +++ b/acuantcamera/src/main/java/com/acuant/acuantcamera/overlay/MrzRectangleView.kt @@ -1,82 +1,38 @@ package com.acuant.acuantcamera.overlay import android.content.Context -import android.graphics.Point import android.util.AttributeSet -import android.util.Log -import com.acuant.acuantcamera.camera.AcuantBaseCameraFragment +import com.acuant.acuantcamera.camera.mrz.MrzCameraState class MrzRectangleView(context: Context, attr: AttributeSet?) : BaseRectangleView(context, attr) { - override fun setViewFromState(state: AcuantBaseCameraFragment.CameraState) { - - Log.d("CamStateView", state.toString()) - when(state) { - AcuantBaseCameraFragment.CameraState.MrzNone -> { + fun setViewFromState(state: MrzCameraState) { + when (state) { + MrzCameraState.Align -> { setDrawBox(false) - paint.color = paintColorAlign - paintBracket.color = paintColorBracketAlign animateTarget = false - } - AcuantBaseCameraFragment.CameraState.MrzAlign -> { - setDrawBox(false) paintBracket.color = paintColorBracketAlign paint.color = paintColorAlign - animateTarget = false } - else -> { + MrzCameraState.Trying -> { setDrawBox(true) - when (state) { - AcuantBaseCameraFragment.CameraState.MrzTrying -> { - paintBracket.color = paintColorBracketHold - paint.color = paintColorHold - } - AcuantBaseCameraFragment.CameraState.MrzCapturing -> { - paintBracket.color = paintColorBracketCapturing - paint.color = paintColorCapturing - } - else -> { - paintBracket.color = paintColorBracketCloser - paint.color = paintColorCloser - } - } animateTarget = true + paintBracket.color = paintColorBracketHold + paint.color = paintColorHold } - } - visibility = VISIBLE - } - - companion object { - fun fixPoints(points: Array?) { - if(points != null && points.size == 4) { - if(points[0].y > points[2].y && points[0].x < points[2].x) { - //rotate 2 - var tmp = points[0] - points[0] = points[2] - points[2] = tmp - - tmp = points[1] - points[1] = points[3] - points[3] = tmp - - } else if(points[0].y > points[2].y && points[0].x > points[2].x) { - //rotate 3 - val tmp = points[0] - points[0] = points[1] - points[1] = points[2] - points[2] = points[3] - points[3] = tmp - - } else if(points[0].y < points[2].y && points[0].x < points[2].x) { - //rotate 1 - val tmp = points[0] - points[0] = points[3] - points[3] = points[2] - points[2] = points[1] - points[1] = tmp - - } + MrzCameraState.Capturing -> { + setDrawBox(true) + animateTarget = true + paintBracket.color = paintColorBracketCapturing + paint.color = paintColorCapturing + } + else -> { //Move Closer or Reposition + setDrawBox(true) + animateTarget = true + paintBracket.color = paintColorBracketCloser + paint.color = paintColorCloser } } + visibility = VISIBLE } } \ No newline at end of file diff --git a/acuantcamera/src/main/res/drawable-hdpi/ic_action_info.png b/acuantcamera/src/main/res/drawable-hdpi/ic_action_info.png deleted file mode 100644 index 32bd1aabcabb85ded957230533c00e735183a323..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1025 zcmV+c1pfPpP)UDI7BaW>`N=bRlk|E2%^dB8hrR55et*8d|IcHT75QTnx&CD( z0RP&9B@dK5;L-x65HcIEwzf8qPN%o}e7;SFVXWg@h5G(8m&<)+eJL+59~&4LNV_!r zJh>+x2#3Sl{CdQ#bT}>ywQR&8iWzy)M+Q2ECY~)rE;`M%!}m*2L~T%H1|=Rs$~z==CdX= z48rmho`|t8O+~w5HL)!Nz)I((+8|_dYHF%o7dC|2VU#(bK)xovUn~V!!l|;d@(MD7 z$QZ@DcdfYA5&%`_FTxPs#yu+u3*fUT6H!Ve0B~QYkR&!V=On%G_H8SD!V&>jgyx}+Knp;|1FUifM$%zBVA`u=>gGED zq&&B>W-!17-PoZ8pu*>pbXb_S!SK5k1xd4g)>g@d_8I_WOpy-#W+|Ck0J!uB4ECW!8~_iN3BVZu zM3MEDkbUMBz)2YFhG8#Z(6Jl<2)`3DFCpta03QGM0E#<=iav?WWt8LK<;7mY% z3dxyac6RorXJyoBBXbJC^F}6?buvmg1pu84baGnrW|R;B`7n}q8^hfEjBmA(6>OMO zoz?C*-2~m-v6H8ldj5Q=dS1|RS_C^d&`6ihclsRfvKD*(IM9&9?Zd;vNh?iLJ4%?D z6zE845IW5TKT_s_(B@n7HxeEl>YLS3QbfqjMBxtg3WS%}Dot{PRD74`S|#s^V!qg# zPm9H_7TGYtTG>~N=HxEg%iV=#PR0&OU=>NZ*?hgu`!uJkZ!7kC-AcVK)E--XpioM< v;adXW#(G}RmOS8v72WVCdBBbJyrBIJ%ovMLu~&(q00000NkvXXu0mjfpn%#$ diff --git a/acuantcamera/src/main/res/drawable-hdpi/ic_launcher.png b/acuantcamera/src/main/res/drawable-hdpi/ic_launcher.png deleted file mode 100644 index ac6cf278d3fd5275257879e9d2b90de8b674f5a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4251 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 diff --git a/acuantcamera/src/main/res/drawable-hdpi/tile.9.png b/acuantcamera/src/main/res/drawable-hdpi/tile.9.png deleted file mode 100644 index 135862883e26eddce2b19db021adf62e10357ad0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196 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 diff --git a/acuantcamera/src/main/res/drawable-xhdpi/ic_action_info.png b/acuantcamera/src/main/res/drawable-xhdpi/ic_action_info.png deleted file mode 100644 index ba143ea7a80f03b0e850775ad672ccb2d6195e4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1355 zcmV-R1+@B!P)75wB?6-CWk+3Ez~DWohQ@%>jMdQA1c3hj{%fPr=n<5-3k6~tfy!wCFb|Kf3x&e@#l^+Hz3{3u)^7kJ zSp5(dKa9|EdqNahe-)7XjcFXF`O`@%(u@ozCtg#MS;!CX<8kdJtYc7Vj0j?7f+pnP0u| zma(1zsGz*K=QJ_;2=4hb0yr^WQ{^2z%0b}w6k+xe*Qyd@)c}y~@1x8; zYy>M|jSsb!@9pjFknu}PO9hR`7!aOuLKv?&OXDQfL48yOfYkX|EH-JCs%flnS~0tn z%clTvSjNyK%~9Sy)>skmlZtcDgu+@rpC7NLPpAq2CV!NP?=_R>nEWkG!bAB3DF3nz z7**t*OeXWaTM#8NVuV+%>hKjtIOBG_L#GBn+FMG{8&-$+zP8jQ5{WzD@scepjnKQ< zZ1y{i0#YKOf8H1(4~Va8+h#QYENUJg-j5hTmr?#rjnIa+1>v9NUKEfc3!e`p8nnFN z^BQCJ%Q%d3j$H7ChKCw}(b3TfYhm*Vf?w23Xw#C+_Q*5IQ&xgi9RMKR;uA7h7Eq~F zYC^+E1%O%7o-i)Awz9Hv%x!zC5lTbb-Q6AU>gt-p`n!!69M>$RRRDnYolGK)h#VQL ze^Mi?Lo0a%YaO@8;#DWK@#~C7uIIQ#r;KLCJS`;cqg$n_I)O}Ujz1pux#0psnhE$a3-b$iwAM78vQ z!NI{!HdQ9g9G3|Jk<-CbTfdn870jt~uG6|9c0$&DwX@qLf2h^t#@~aj06M(w5 z!ctfKSPc0IKwTF@0)QXOVV?o0?{e6W4H3TpXvl_$@q)+bz3(xtr+Q1w4*(jrB_>LH z-4vyDy@o9@*;l-Bu$^^=*7_e;{VPNb-4qoe{n!?Ft86)4!?(qaK*4@7A|M3))!4Qp z0K&~&Ua;N|2Iu9d6Pp!4kQbN1cs=fbAeo&_iKEgimL;uao}2pv2}|MNej;OAy|1tD zx_CT(*sqVI*fRg7SS+5a=H=&3rB<~iwiC1R{{#D(TqU5c_Ms#dq`>(XedXxYF diff --git a/acuantcamera/src/main/res/drawable-xhdpi/ic_launcher.png b/acuantcamera/src/main/res/drawable-xhdpi/ic_launcher.png deleted file mode 100644 index 6fd13181e4c1f2ec8e4fc5e737f9f44df2053def..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5911 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 - - - - - - - - - diff --git a/acuantcamera/src/main/res/layout/activity_acu_document_camera.xml b/acuantcamera/src/main/res/layout/activity_acu_document_camera.xml deleted file mode 100644 index 5db4937..0000000 --- a/acuantcamera/src/main/res/layout/activity_acu_document_camera.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - diff --git a/acuantcamera/src/main/res/layout/activity_acu_mrz_camera.xml b/acuantcamera/src/main/res/layout/activity_acu_mrz_camera.xml deleted file mode 100644 index c0668b4..0000000 --- a/acuantcamera/src/main/res/layout/activity_acu_mrz_camera.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - diff --git a/acuantcamera/src/main/res/layout/activity_acu_camera.xml b/acuantcamera/src/main/res/layout/activity_camera.xml similarity index 100% rename from acuantcamera/src/main/res/layout/activity_acu_camera.xml rename to acuantcamera/src/main/res/layout/activity_camera.xml diff --git a/acuantcamera/src/main/res/layout/barcode_fragment_ui.xml b/acuantcamera/src/main/res/layout/barcode_fragment_ui.xml new file mode 100644 index 0000000..1611a27 --- /dev/null +++ b/acuantcamera/src/main/res/layout/barcode_fragment_ui.xml @@ -0,0 +1,40 @@ + + + + + + + + \ No newline at end of file diff --git a/acuantcamera/src/main/res/layout/document_fragment_ui.xml b/acuantcamera/src/main/res/layout/document_fragment_ui.xml new file mode 100644 index 0000000..811a985 --- /dev/null +++ b/acuantcamera/src/main/res/layout/document_fragment_ui.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/acuantcamera/src/main/res/layout/fragment_camera.xml b/acuantcamera/src/main/res/layout/fragment_camera.xml new file mode 100644 index 0000000..0d8e4c2 --- /dev/null +++ b/acuantcamera/src/main/res/layout/fragment_camera.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/acuantcamera/src/main/res/layout/fragment_camera2_basic.xml b/acuantcamera/src/main/res/layout/fragment_camera2_basic.xml deleted file mode 100644 index 97a60a7..0000000 --- a/acuantcamera/src/main/res/layout/fragment_camera2_basic.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/acuantcamera/src/main/res/layout/mrz_fragment_ui.xml b/acuantcamera/src/main/res/layout/mrz_fragment_ui.xml new file mode 100644 index 0000000..c877de7 --- /dev/null +++ b/acuantcamera/src/main/res/layout/mrz_fragment_ui.xml @@ -0,0 +1,50 @@ + + + + + + + + + + \ No newline at end of file diff --git a/acuantcamera/src/main/res/values/base-strings.xml b/acuantcamera/src/main/res/values/base-strings.xml deleted file mode 100644 index 5b1bda4..0000000 --- a/acuantcamera/src/main/res/values/base-strings.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - Camera2Basic - - - - diff --git a/acuantcamera/src/main/res/values/template-styles.xml b/acuantcamera/src/main/res/values/template-styles.xml index 0ca7ef2..403ad1f 100644 --- a/acuantcamera/src/main/res/values/template-styles.xml +++ b/acuantcamera/src/main/res/values/template-styles.xml @@ -33,7 +33,6 @@