diff --git a/app/src/main/java/org/newlogic/smartscanner/MainActivity.kt b/app/src/main/java/org/newlogic/smartscanner/MainActivity.kt index bcc4c7cc..c566f04c 100644 --- a/app/src/main/java/org/newlogic/smartscanner/MainActivity.kt +++ b/app/src/main/java/org/newlogic/smartscanner/MainActivity.kt @@ -68,12 +68,13 @@ class MainActivity : AppCompatActivity() { override fun onStart() { super.onStart() - // Choose scan type + // Choose scan modes binding.itemBarcode.item.setOnClickListener { scanBarcode(BarcodeOptions.default) } binding.itemIdpassLite.item.setOnClickListener { scanIDPassLite() } binding.itemMrz.item.setOnClickListener { scanMRZ() } - binding.itemQr.item.setOnClickListener { scanQRCode() } binding.itemNfc.item.setOnClickListener { scanNFC() } + binding.itemPdf417.item.setOnClickListener { scanPDF417() } + binding.itemQr.item.setOnClickListener { scanQRCode() } // Change language binding.languageSettings.setOnClickListener { startActivity(Intent(this, SettingsActivity::class.java)) @@ -151,6 +152,20 @@ class MainActivity : AppCompatActivity() { } + private fun scanPDF417() { + val intent = Intent(this, SmartScannerActivity::class.java) + intent.putExtra( + SmartScannerActivity.SCANNER_OPTIONS, + ScannerOptions( + mode = Modes.PDF_417.value, + language = getLanguage(preference), + scannerSize = ScannerSize.SMALL.value, + config = sampleConfig(false) + ) + ) + startActivityForResult(intent, OP_SCANNER) + } + private fun scanQRCode() { val intent = ScannerIntent.intentQRCode( isGzipped = true, diff --git a/app/src/main/res/drawable/ic_scan_pdf417.xml b/app/src/main/res/drawable/ic_scan_pdf417.xml new file mode 100644 index 00000000..4da9d112 --- /dev/null +++ b/app/src/main/res/drawable/ic_scan_pdf417.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index af70d56e..b2701d37 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -9,119 +9,146 @@ android:orientation="vertical" tools:context="org.newlogic.smartscanner.MainActivity"> - + app:layout_constraintTop_toTopOf="parent" /> - + app:layout_constraintTop_toBottomOf="@+id/language_settings"> - + - + - + - + - + - + - + + + + + + + + + + - - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_pdf417.xml b/app/src/main/res/layout/item_pdf417.xml new file mode 100644 index 00000000..2239e890 --- /dev/null +++ b/app/src/main/res/layout/item_pdf417.xml @@ -0,0 +1,46 @@ + + + + + + + + + + diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index c3ec197c..145e2db2 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -13,6 +13,7 @@ معرف باس لايت كود MRZ الجواز الالكتروني/بطاقة الهوية (NFC) + PDF417 كيو ار كود diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9d15bae3..7df55010 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,6 +13,7 @@ ID PASS Lite MRZ Code E-Passport/ID Card (NFC) + PDF417 QR Code diff --git a/core-lib/build.gradle b/core-lib/build.gradle index 032133c4..90b5e703 100644 --- a/core-lib/build.gradle +++ b/core-lib/build.gradle @@ -117,4 +117,7 @@ dependencies { def work_version = "2.7.0" implementation "androidx.work:work-runtime:$work_version" implementation "androidx.work:work-runtime-ktx:$work_version" + // Barcode + implementation('com.journeyapps:zxing-android-embedded:4.3.0') { transitive = false } + implementation 'com.google.zxing:core:3.3.0' } \ No newline at end of file diff --git a/core-lib/src/androidTest/java/org/idpass/smartscanner/lib/ExampleInstrumentedTest.kt b/core-lib/src/androidTest/java/org/idpass/smartscanner/lib/ExampleInstrumentedTest.kt index 91495333..9563400a 100644 --- a/core-lib/src/androidTest/java/org/idpass/smartscanner/lib/ExampleInstrumentedTest.kt +++ b/core-lib/src/androidTest/java/org/idpass/smartscanner/lib/ExampleInstrumentedTest.kt @@ -1,22 +1,12 @@ package org.idpass.smartscanner.lib -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Assert.assertEquals -import org.junit.Test import org.junit.runner.RunWith +import org.junit.runners.JUnit4 /** * Instrumented test, which will execute on an Android device. * * See [testing documentation](http://d.android.com/tools/testing). */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("org.idpass.smartscanner.test", appContext.packageName) - } -} \ No newline at end of file +@RunWith(JUnit4::class) +class ExampleInstrumentedTest \ No newline at end of file diff --git a/core-lib/src/main/java/org/idpass/smartscanner/lib/SmartScannerActivity.kt b/core-lib/src/main/java/org/idpass/smartscanner/lib/SmartScannerActivity.kt index 8eca0f8d..a6a162c8 100644 --- a/core-lib/src/main/java/org/idpass/smartscanner/lib/SmartScannerActivity.kt +++ b/core-lib/src/main/java/org/idpass/smartscanner/lib/SmartScannerActivity.kt @@ -48,16 +48,19 @@ import androidx.core.content.ContextCompat import androidx.core.content.res.ResourcesCompat import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions.bitmapTransform -import com.google.android.gms.common.ConnectionResult -import com.google.android.gms.common.GoogleApiAvailability import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.snackbar.Snackbar import com.google.gson.Gson +import com.google.zxing.BarcodeFormat.* +import com.journeyapps.barcodescanner.DecoratedBarcodeView +import com.journeyapps.barcodescanner.ViewfinderView import io.sentry.Sentry import io.sentry.SentryOptions import org.idpass.lite.android.IDPassLite import org.idpass.smartscanner.api.ScannerConstants import org.idpass.smartscanner.lib.barcode.BarcodeAnalyzer +import org.idpass.smartscanner.lib.barcode.BarcodeResult +import org.idpass.smartscanner.lib.barcode.pdf417.PDF417DecoderFactory import org.idpass.smartscanner.lib.barcode.qr.QRCodeAnalyzer import org.idpass.smartscanner.lib.idpasslite.IDPassLiteAnalyzer import org.idpass.smartscanner.lib.idpasslite.IDPassManager @@ -68,11 +71,13 @@ import org.idpass.smartscanner.lib.platform.BaseActivity import org.idpass.smartscanner.lib.platform.extension.* import org.idpass.smartscanner.lib.platform.utils.CameraUtils.isLedFlashAvailable import org.idpass.smartscanner.lib.platform.utils.LanguageUtils +import org.idpass.smartscanner.lib.platform.utils.PlayStoreUtils import org.idpass.smartscanner.lib.platform.utils.transform.CropTransformation import org.idpass.smartscanner.lib.scanner.ImageResult import org.idpass.smartscanner.lib.scanner.SmartScannerException import org.idpass.smartscanner.lib.scanner.config.* import java.io.File +import java.util.* import java.util.concurrent.ExecutorService import java.util.concurrent.Executors @@ -111,6 +116,7 @@ class SmartScannerActivity : BaseActivity(), OnClickListener { private var captureLabelText: TextView? = null private var captureHeaderText: TextView? = null private var captureSubHeaderText: TextView? = null + private var barcodeScannerView: DecoratedBarcodeView? = null private lateinit var modelLayoutView: View private lateinit var coordinatorLayoutView: View @@ -126,6 +132,7 @@ class SmartScannerActivity : BaseActivity(), OnClickListener { coordinatorLayoutView = findViewById(R.id.coordinator_layout) modelLayoutView = findViewById(R.id.view_layout) viewFinder = findViewById(R.id.view_finder) + barcodeScannerView = findViewById(R.id.view_finder_barcode) flashButton = findViewById(R.id.flash_button) closeButton = findViewById(R.id.close_button) rectangle = findViewById(R.id.rect_image) @@ -186,26 +193,30 @@ class SmartScannerActivity : BaseActivity(), OnClickListener { private fun setupConfiguration() { runOnUiThread { - val isMLKit = isPlayServicesAvailable() + val isMLKit = PlayStoreUtils.isPlayServicesAvailable(this) var analyzer : ImageAnalysis.Analyzer? = null - var isPdf417 = false + var hasPDF417 = false if (mode == Modes.BARCODE.value) { val barcodeStrings = scannerOptions?.barcodeOptions?.barcodeFormats ?: BarcodeFormat.default val barcodeFormats = barcodeStrings.map { BarcodeFormat.valueOf(it).value } - isPdf417 = barcodeStrings.find { it == "PDF_417" }?.isNotEmpty() == true + hasPDF417 = barcodeStrings.find { it == "PDF_417" }?.isNotEmpty() == true analyzer = BarcodeAnalyzer( activity = this, intent = intent, imageResultType = config?.imageResultType ?: ImageResultType.PATH.value, - isPDF417 = isPdf417, + hasPDF417 = hasPDF417, barcodeFormats = barcodeFormats ) + viewFinder.visibility = VISIBLE + barcodeScannerView?.visibility = GONE } if (mode == Modes.QRCODE.value) { analyzer = QRCodeAnalyzer( activity = this, intent = intent ) + viewFinder.visibility = VISIBLE + barcodeScannerView?.visibility = GONE } if (mode == Modes.IDPASS_LITE.value) { val loaded = IDPassLite.initialize(cacheDir, assets) @@ -219,6 +230,8 @@ class SmartScannerActivity : BaseActivity(), OnClickListener { } } ) + viewFinder.visibility = VISIBLE + barcodeScannerView?.visibility = GONE } if (mode == Modes.MRZ.value) { analyzer = MRZAnalyzer( @@ -231,6 +244,8 @@ class SmartScannerActivity : BaseActivity(), OnClickListener { analyzeStart = System.currentTimeMillis() ) rectangleMRZGuide?.visibility = VISIBLE + viewFinder.visibility = VISIBLE + barcodeScannerView?.visibility = GONE } if (mode == Modes.NFC_SCAN.value) { val nfcOptions = scannerOptions?.nfcOptions @@ -254,22 +269,90 @@ class SmartScannerActivity : BaseActivity(), OnClickListener { ?: false, // default is false, logging is disabled analyzeStart = System.currentTimeMillis() ) + viewFinder.visibility = VISIBLE + barcodeScannerView?.visibility = GONE } - // set Analyzer and start camera - analyzer?.let { - startCamera(analyzer, isPdf417) - } ?: run { - if (mode == Modes.CAPTURE_ONLY.value) { - startCamera() - captureOptions = scannerOptions?.captureOptions ?: CaptureOptions.default - } else throw SmartScannerException("Image Analysis Scanner is null. Please check the scanner options sent to SmartScanner.") + if (mode == Modes.PDF_417.value) { + // Set zxing barcode view finder + val viewFinderBarcode = findViewById(R.id.zxing_viewfinder_view) + viewFinder.visibility = GONE + barcodeScannerView?.visibility = VISIBLE + barcodeScannerView?.initializeFromIntent(intent) + // remove black border, text info and laser + barcodeScannerView?.setStatusText("") + barcodeScannerView?.viewFinder?.visibility = GONE + viewFinderBarcode.setLaserVisibility(false) + viewFinderBarcode.setMaskColor(ContextCompat.getColor(this, R.color.transparent)) + // set PDF417 decoder and autofocus settings + barcodeScannerView?.barcodeView?.decoderFactory = PDF417DecoderFactory() + barcodeScannerView?.cameraSettings?.isContinuousFocusEnabled = true + barcodeScannerView?.cameraSettings?.isAutoFocusEnabled = true + barcodeScannerView?.decodeContinuous { barcodePdf417 -> + Log.d(TAG, "Success from PDF417") + Log.d(TAG, "value: $barcodePdf417") + // Add checking to only output PDF417 barcode format response + if (barcodePdf417.barcodeFormat == PDF_417) { + val bitmapResult = barcodePdf417.bitmap + val filePath = this.cacheImagePath() + bitmapResult?.cropCenter()?.cacheImageToLocal( + filePath, + 0, + if (config?.imageResultType == ImageResultType.BASE_64.value) 30 else 80 + ) + val corners = barcodePdf417.resultPoints + val builder = StringBuilder() + for (corner in corners) { + builder.append("${corner?.x},${corner?.y} ") + } + val cornersString = builder.toString() + val rawValue = barcodePdf417.text + val imageFile = File(filePath) + val imageResult = if (config?.imageResultType == ImageResultType.BASE_64.value) imageFile.encodeBase64() else filePath + val barcodeResult = BarcodeResult(imagePath = filePath, image = imageResult, corners = cornersString, value = rawValue) + + val data = Intent() + val result = Gson().toJson(barcodeResult) + data.putExtra(SCANNER_RESULT, result) + setResult(Activity.RESULT_OK, data) + this.finish() + } + } + barcodeScannerView?.resume() + } else { + // Set Analyzer and start camera + analyzer?.let { + startCamera(analyzer, hasPDF417) + } ?: run { + if (mode == Modes.CAPTURE_ONLY.value) { + startCamera() + captureOptions = scannerOptions?.captureOptions ?: CaptureOptions.default + } else throw SmartScannerException("Image Analysis Scanner is null. Please check the scanner options sent to SmartScanner.") + } } } setupViews() } + override fun onResume() { + super.onResume() + if (mode != null && mode == Modes.PDF_417.value) { + if (barcodeScannerView != null) { + barcodeScannerView?.resume() + } + } + } + + override fun onPause() { + super.onPause() + if (mode != null && mode == Modes.PDF_417.value) { + if (barcodeScannerView != null) { + barcodeScannerView?.pause() + } + } + } + @SuppressLint("ClickableViewAccessibility") - private fun startCamera(analyzer: ImageAnalysis.Analyzer? = null, isPdf417: Boolean = false) { + private fun startCamera(analyzer: ImageAnalysis.Analyzer? = null, hasPDF417: Boolean = false) { viewFinder.post { this.getSystemService(Context.CAMERA_SERVICE) as CameraManager val cameraProviderFuture = ProcessCameraProvider.getInstance(this) @@ -280,7 +363,7 @@ class SmartScannerActivity : BaseActivity(), OnClickListener { preview = Preview.Builder().build() val imageAnalysisBuilder = ImageAnalysis.Builder() val resolution = when { - isPdf417 -> Size(1080, 1920) + hasPDF417 -> Size(1080, 1920) mode == Modes.QRCODE.value || mode == Modes.IDPASS_LITE.value -> Size(720, 1280) else -> Size(480, 640) } @@ -321,10 +404,10 @@ class SmartScannerActivity : BaseActivity(), OnClickListener { ) } // Adjust initial zoom ratio of camera to aid high resolution capture of Pdf417 or QR Code or ID PASS Lite - if (isPdf417 || mode == Modes.QRCODE.value || mode == Modes.IDPASS_LITE.value) { + if (hasPDF417 || mode == Modes.QRCODE.value || mode == Modes.IDPASS_LITE.value) { camera?.cameraControl?.setZoomRatio( when { - isPdf417 -> 0.5F + hasPDF417 -> 0.5F else -> 1.2F } ) @@ -373,10 +456,6 @@ class SmartScannerActivity : BaseActivity(), OnClickListener { Log.e(TAG, "Use case binding failed", exc) } }, ContextCompat.getMainExecutor(this)) - // assign camera click listeners - closeButton?.setOnClickListener(this) - flashButton?.setOnClickListener(this) - manualCapture?.setOnClickListener(this) } } @@ -448,12 +527,10 @@ class SmartScannerActivity : BaseActivity(), OnClickListener { if (orientation == Orientation.LANDSCAPE.value) { this.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE } - } - - private fun isPlayServicesAvailable(): Boolean { - val availability = GoogleApiAvailability.getInstance() - val resultCode = availability.isGooglePlayServicesAvailable(this) - return resultCode == ConnectionResult.SUCCESS + // assign camera click listeners + closeButton?.setOnClickListener(this) + flashButton?.setOnClickListener(this) + manualCapture?.setOnClickListener(this) } override fun onRequestPermissionsResult( @@ -493,10 +570,10 @@ class SmartScannerActivity : BaseActivity(), OnClickListener { flashButton?.let { if (it.isSelected) { it.isSelected = false - camera?.cameraControl?.enableTorch(false) + enableFlashlight(false) } else { it.isSelected = true - camera?.cameraControl?.enableTorch(true) + enableFlashlight(true) } } } @@ -555,6 +632,14 @@ class SmartScannerActivity : BaseActivity(), OnClickListener { } } + private fun enableFlashlight(torch: Boolean) { + if (barcodeScannerView != null && barcodeScannerView?.visibility == VISIBLE) { + if (torch) barcodeScannerView?.setTorchOn() else barcodeScannerView?.setTorchOff() + } else { + camera?.cameraControl?.enableTorch(torch) + } + } + @SuppressLint("InflateParams") private fun showIDPassLiteVerification(qrBytes: ByteArray) { val bottomSheetDialog = BottomSheetDialog(this) diff --git a/core-lib/src/main/java/org/idpass/smartscanner/lib/barcode/BarcodeAnalyzer.kt b/core-lib/src/main/java/org/idpass/smartscanner/lib/barcode/BarcodeAnalyzer.kt index 6c6e976d..62e49ad1 100644 --- a/core-lib/src/main/java/org/idpass/smartscanner/lib/barcode/BarcodeAnalyzer.kt +++ b/core-lib/src/main/java/org/idpass/smartscanner/lib/barcode/BarcodeAnalyzer.kt @@ -42,7 +42,7 @@ class BarcodeAnalyzer( override val activity: Activity, override val intent: Intent, override val mode: String = Modes.BARCODE.value, - private val isPDF417: Boolean, + private val hasPDF417: Boolean, private val imageResultType: String, private val barcodeFormats: List ) : BaseImageAnalyzer() { @@ -86,7 +86,7 @@ class BarcodeAnalyzer( builder.append("${corner.x},${corner.y} ") } } - val bitmapResult = if (isPDF417) bf.resize(640, 480) else bf + val bitmapResult = if (hasPDF417) bf.resize(640, 480) else bf bitmapResult?.cropCenter()?.cacheImageToLocal( filePath, imageProxy.imageInfo.rotationDegrees, diff --git a/core-lib/src/main/java/org/idpass/smartscanner/lib/barcode/pdf417/PDF417DecoderFactory.java b/core-lib/src/main/java/org/idpass/smartscanner/lib/barcode/pdf417/PDF417DecoderFactory.java new file mode 100644 index 00000000..f652ebc5 --- /dev/null +++ b/core-lib/src/main/java/org/idpass/smartscanner/lib/barcode/pdf417/PDF417DecoderFactory.java @@ -0,0 +1,69 @@ +package org.idpass.smartscanner.lib.barcode.pdf417; + + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.DecodeHintType; +import com.google.zxing.MultiFormatReader; +import com.journeyapps.barcodescanner.Decoder; +import com.journeyapps.barcodescanner.DecoderFactory; +import com.journeyapps.barcodescanner.InvertedDecoder; +import com.journeyapps.barcodescanner.MixedDecoder; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumMap; +import java.util.Map; + +public class PDF417DecoderFactory implements DecoderFactory { + + private final Collection decodeFormats; + private Map hints; + private String characterSet; + private final int scanType; + + public PDF417DecoderFactory() { + this.decodeFormats = new ArrayList(){{ + add(BarcodeFormat.PDF_417); + }}; + this.scanType = 2; + } + + public PDF417DecoderFactory(Map hints, String characterSet, int scanType) { + this.decodeFormats = new ArrayList(){{ + add(BarcodeFormat.PDF_417); + }}; + this.hints = hints; + this.characterSet = characterSet; + this.scanType = scanType; + } + @Override + public Decoder createDecoder(Map baseHints) { + Map hints = new EnumMap<>(DecodeHintType.class); + + hints.putAll(baseHints); + + if (this.hints != null) { + hints.putAll(this.hints); + } + + if (this.decodeFormats != null) { + hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats); + } + + if (characterSet != null) { + hints.put(DecodeHintType.CHARACTER_SET, characterSet); + } + + MultiFormatReader reader = new MultiFormatReader(); + reader.setHints(hints); + + switch (scanType){ + case 1: + return new InvertedDecoder(reader); + case 2: + return new MixedDecoder(reader); + default: + return new Decoder(reader); + } + } +} diff --git a/core-lib/src/main/java/org/idpass/smartscanner/lib/platform/utils/PlayStoreUtils.kt b/core-lib/src/main/java/org/idpass/smartscanner/lib/platform/utils/PlayStoreUtils.kt new file mode 100644 index 00000000..c6546d4d --- /dev/null +++ b/core-lib/src/main/java/org/idpass/smartscanner/lib/platform/utils/PlayStoreUtils.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 Newlogic Pte. Ltd. + * + * 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 org.idpass.smartscanner.lib.platform.utils + +import android.content.Context +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GoogleApiAvailability + +object PlayStoreUtils { + + fun isPlayServicesAvailable(context: Context): Boolean { + val availability = GoogleApiAvailability.getInstance() + val resultCode = availability.isGooglePlayServicesAvailable(context) + return resultCode == ConnectionResult.SUCCESS + } +} diff --git a/core-lib/src/main/java/org/idpass/smartscanner/lib/scanner/config/Modes.kt b/core-lib/src/main/java/org/idpass/smartscanner/lib/scanner/config/Modes.kt index 68370473..6b0f7bd4 100644 --- a/core-lib/src/main/java/org/idpass/smartscanner/lib/scanner/config/Modes.kt +++ b/core-lib/src/main/java/org/idpass/smartscanner/lib/scanner/config/Modes.kt @@ -23,5 +23,6 @@ enum class Modes (val value : String) { IDPASS_LITE("idpass-lite"), MRZ("mrz"), NFC_SCAN("nfc-scan"), + PDF_417("pdf417"), QRCODE("qrcode") } \ No newline at end of file diff --git a/core-lib/src/main/res/layout/activity_smart_scanner.xml b/core-lib/src/main/res/layout/activity_smart_scanner.xml index c62fcbb4..e6d4fdd8 100644 --- a/core-lib/src/main/res/layout/activity_smart_scanner.xml +++ b/core-lib/src/main/res/layout/activity_smart_scanner.xml @@ -88,6 +88,20 @@ app:layout_constraintTop_toTopOf="parent" app:scaleType="fillCenter"/> + +