How to Build a Simple MRZ and VIN Scanner Application for Android
Dynamsoft’s pre-built Android MRZ (Machine Readable Zone) Scanner component, which wraps low-level Dynamsoft Capture Vision API, is open-source and available for download on GitHub. An Android VIN (Vehicle Identification Number) Scanner has not been officially released yet, but a relevant example project is available. This article will guide you through enhancing the existing Android MRZ Scanner component by adding VIN scanning capability.
This article is Part 1 in a 4-Part Series.
- Part 1 - How to Build a Simple MRZ and VIN Scanner Application for Android
- Part 2 - Developing a Web MRZ and VIN Scanner with JavaScript and HTML5
- Part 3 - How to Build an iOS MRZ and VIN Scanner App in Swift
- Part 4 - How to Build a Flutter MRZ and VIN Scanner for Android, iOS, Web, Windows, and Linux
Android MRZ/VIN Scanner Demo Video
Prerequisites
- Free Trial License for Dynamsoft Capture Vision.
- Android MRZ Scanner: This official repository includes two projects: an Android Module and an Android Application. The module is the MRZ Scanner component, while the application demonstrates how to use it.
- Android VIN Scanner: An official sample project demonstrating how to scan VINs using Dynamsoft Capture Vision.
Make sure to request a license key and download both projects before getting started.
Importing the Android MRZ Scanner Module
- Open the
mrz-scanner-mobile\android\samples\ScanMRZ
project in Android Studio. -
Select File > New > Import Module, and add the
mrz-scanner-mobile\android\src\DynamsoftMRZScannerBundle
module. Note: the module name isdynamsoftmrzscannerbundle
. -
Modify the dependency in the
build.gradle
file as follows:dependencies { // implementation 'com.dynamsoft:mrzscannerbundle:2.0.1' implementation project(':dynamsoftmrzscannerbundle') }
- Build the project to ensure the module source code is ready for integration.
Integrating VIN Recognition Capability into the MRZ Scanner Module
MRZ and VIN recognition rely on model files that are encapsulated into AAR packages. To use them, navigate to the build.gradle
file of the dynamsoftmrzscannerbundle
module and add the following lines to the dependencies
section:
dependencies {
api "com.dynamsoft:dynamsoftcapturevisionbundle:2.6.1003"
api 'com.dynamsoft:dynamsoftmrz:3.4.20'
api "com.dynamsoft:dynamsoftvin:3.4.20"
}
The Dynamsoft Capture Vision engine will automatically load the appropriate model files when you specify a template name in the startCapturing()
method. For MRZ recognition, use the template name ReadPassportAndId
, and for VIN recognition, use ReadVINText
. No major code changes are required. We will only add support for the VIN scanning scenario and create corresponding data structures.
Detection Type Enum
The source code was initially designed for MRZ scanning only. To support VIN scanning, create an EnumDetectionType.java
file in the com.dynamsoft.mrzscannerbundle.ui
package. This enum will distinguish between detection types:
package com.dynamsoft.mrzscannerbundle.ui;
public enum EnumDetectionType {
MRZ,
VIN,
}
VIN Data Structure
Based on MRZData.java
and MRZScanResult.java
, create VINData.java
and VINScanResult.java
in the same package. These classes handle VIN data extraction and scan results.
-
VINData.java
package com.dynamsoft.mrzscannerbundle.ui; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.util.HashMap; public class VINData { public String vinString; public String wmi; public String region; public String vds; public String checkDigit; public String modelYear; public String plantCode; public String serialNumber; public static VINData extractItem(@Nullable HashMap<String, String> item) { if (item == null) return null; VINData data = new VINData(); data.vinString = item.get("vinString") == null ? "" : item.get("vinString"); data.wmi = item.get("WMI") == null ? "" : item.get("WMI"); data.region = item.get("region") == null ? "" : item.get("region"); data.vds = item.get("VDS") == null ? "" : item.get("VDS"); data.checkDigit = item.get("checkDigit") == null ? "" : item.get("checkDigit"); data.modelYear = item.get("modelYear") == null ? "" : item.get("modelYear"); data.plantCode = item.get("plantCode") == null ? "" : item.get("plantCode"); data.serialNumber = item.get("serialNumber") == null ? "" : item.get("serialNumber"); return data; } @NonNull public String toString() { return "VIN String: " + vinString + "\n" + "WMI: " + wmi + "\n" + "Region: " + region + "\n" + "VDS: " + vds + "\n" + "Check Digit: " + checkDigit + "\n" + "Model Year: " + modelYear + "\n" + "Manufacturer plant: " + plantCode + "\n" + "Serial Number: " + serialNumber; } }
-
VINScanResult.java
package com.dynamsoft.mrzscannerbundle.ui; import static com.dynamsoft.mrzscannerbundle.ui.ScannerActivity.EXTRA_RESULT; import static com.dynamsoft.mrzscannerbundle.ui.ScannerActivity.EXTRA_STATUS_CODE; import android.content.Intent; import java.util.HashMap; public class VINScanResult extends CommonResult { private VINData vinData; public VINScanResult(int resultCode, Intent data, EnumDetectionType detectionType) { if (data != null) { super.resultStatus = data.getIntExtra(EXTRA_STATUS_CODE, resultCode); assembleMap((HashMap<String, String>) data.getSerializableExtra(EXTRA_RESULT)); super.detectionType = detectionType; } } @Override public void assembleMap(HashMap<String, String> entry) { vinData = VINData.extractItem(entry); } public VINData getData() { return vinData; } }
Common Result Class
Create a shared CommonResult.java
to handle common fields and methods for both MRZ and VIN scanning results. This class will be used as a base class for both MRZScanResult
and VINScanResult
.
package com.dynamsoft.mrzscannerbundle.ui;
import java.util.HashMap;
public class CommonResult {
protected EnumDetectionType detectionType;
@EnumResultStatus
protected int resultStatus;
protected String errorString;
protected int errorCode;
public void assembleMap(HashMap<String, String> entry) {}
public EnumDetectionType getDetectionType() {
return detectionType;
}
public @interface EnumResultStatus {
int RS_FINISHED = 0;
int RS_CANCELED = 1;
int RS_EXCEPTION = 2;
}
@EnumResultStatus
public int getResultStatus() {
return resultStatus;
}
public String getErrorString() {
return errorString;
}
public int getErrorCode() {
return errorCode;
}
}
Scanner Configuration
- Rename
MrzScannerConfig.java
toScannerConfig.java
and modify the class name accordingly. This class is responsible for configuring the scanner settings. -
Add support for detection type configuration:
private EnumDetectionType detectionType = EnumDetectionType.MRZ; public EnumDetectionType getDetectionType() { return detectionType; } public void setDetectionType(EnumDetectionType detectionType) { this.detectionType = detectionType; }
Scanner Activity
- Rename
MrzScannerActivity.java
toScannerActivity.java
and modify the class name accordingly. This class is responsible for handling the camera preview and scanning process. -
Modify UI and logic based on the detection type:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_scanner); findViewById(R.id.btn_capture).setOnClickListener(v -> { enableCapture = true; }); ... switch (configuration.getDetectionType()) { case MRZ: { guideFrame.setVisibility(isGuideFrameVisible ? View.VISIBLE : View.GONE); try { mCamera.enableEnhancedFeatures(EnumEnhancerFeatures.EF_FRAME_FILTER); } catch (CameraEnhancerException e) { throw new RuntimeException(e); } } break; case VIN: { guideFrame.setVisibility(View.GONE); try { mCamera.setScanRegion(new DSRect(0.1f, 0.4f, 0.9f, 0.6f, true)); } catch (CameraEnhancerException e) { e.printStackTrace(); } } break; default: break; } }
-
Configure the template for VIN scanning in the
configCVR()
method:private void configCVR() { ... switch (configuration.getDetectionType()) { case MRZ: { if (configuration.getDocumentType() != null) { switch (configuration.getDocumentType()) { case DT_ALL: mCurrentTemplate = "ReadPassportAndId"; break; case DT_ID: mCurrentTemplate = "ReadId"; break; case DT_PASSPORT: mCurrentTemplate = "ReadPassport"; break; } } } break; case VIN: mCurrentTemplate = "ReadVINText"; break; } ... }
-
Parse the result accordingly:
@Override public CommonResult parseResult(int i, @Nullable Intent intent) { switch (config.getDetectionType()) { case MRZ: { return new MRZScanResult(i, intent, config.getDetectionType()); } case VIN: { return new VINScanResult(i, intent, config.getDetectionType()); } } return null; }
Modifying the Application Project to Support MRZ and VIN Scanning
-
Add a raido group in
activity_main.xml
to choose the scanning mode:<RadioGroup android:id="@+id/radio_group_mode" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_centerHorizontal="true" android:layout_marginTop="40dp"> <RadioButton android:id="@+id/radio_mrz" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="MRZ" android:checked="true" android:buttonTint="@color/orange" android:textColor="@android:color/black" /> <RadioButton android:id="@+id/radio_vin" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="VIN" android:buttonTint="@color/orange" android:textColor="@android:color/black" /> </RadioGroup>
-
In
MainActivity.java
, pass the selected detection type toScannerActivity
:@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.btn_scan).setOnClickListener(v -> { Intent intent = new Intent(MainActivity.this, ScannerActivity.class); if (findViewById(R.id.radio_mrz).isChecked()) { intent.putExtra("detectionType", EnumDetectionType.MRZ.name()); } else { intent.putExtra("detectionType", EnumDetectionType.VIN.name()); } startActivity(intent); }); }
-
Display scan results based on the detection type:
launcher = registerForActivityResult(new ScannerActivity.ResultContract(), commonResult -> { if (commonResult == null) return; if (commonResult.getResultStatus() == CommonResult.EnumResultStatus.RS_FINISHED) { switch (commonResult.getDetectionType()) { case MRZ: { MRZScanResult result = (MRZScanResult) commonResult; if (result.getData() != null) { MRZData data = result.getData(); content.removeAllViews(); content.addView(childView("Name:", data.getFirstName() + " " + data.getLastName())); content.addView(childView("Sex:", data.getSex() == null ? "" : data.getSex().substring(0, 1).toUpperCase() + data.getSex().substring(1))); content.addView(childView("Age:", data.getAge() + "")); content.addView(childView("Document Type:", data.getDocumentType())); content.addView(childView("Document Number:", data.getDocumentNumber())); content.addView(childView("Issuing State:", data.getIssuingState())); content.addView(childView("Nationality:", data.getNationality())); content.addView(childView("Date of Birth(YYYY-MM-DD):", data.getDateOfBirth())); content.addView(childView("Date of Expiry(YYYY-MM-DD):", data.getDateOfExpire())); } } break; case VIN: { VINScanResult result = (VINScanResult) commonResult; if (result.getData() != null) { VINData data = result.getData(); content.removeAllViews(); content.addView(childView("VIN:", data.vinString)); content.addView(childView("WMI:", data.wmi)); content.addView(childView("Region:", data.region)); content.addView(childView("VDS:", data.vds)); content.addView(childView("Check Digit:", data.checkDigit)); content.addView(childView("Model Year:", data.modelYear)); content.addView(childView("Plant Code:", data.plantCode)); content.addView(childView("Serial Number:", data.serialNumber)); } } break; } } ... });
Running the Android MRZ/VIN Scanner Application
Android MRZ Scanner
MRZ Scanner UI
MRZ Result
Android VIN Scanner
VIN Scanner UI
VIN Result
Source Code
https://github.com/yushulx/android-camera-barcode-mrz-document-scanner/tree/main/examples/MrzVin