How to Recognize US Driver’s License on Android Mobile Apps

According to the American Association of Motor Vehicle Administrators (AAMVA) specification, PDF417 symbology is used for storing personal information on US driver’s license. In this article, I will use Google mobile vision APIs to recognize a driver’s license.  Besides, I will create a similar Android barcode reader app to extract information from PDF417 symbology by Dynamsoft Barcode Reader SDK.

Driver’s License Recognition on Android

Here is a forged driver’s license used for testing.

driver's license

Google Barcode Detection

Mobile Vision APIs are capable of detecting some mainstream barcode symbologies. There is a DriverLicense class that defines common fields existing on all barcode standards.

To quickly verify Google’s API, I downloaded the sample code of Android Vision.

Create a ResultActivity.java file for displaying the information of a driver’s license:

public class ResultActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        TextView tv = new TextView(this);
        tv.setVerticalScrollBarEnabled(true);
        tv.setText("");
        tv.setMovementMethod(new ScrollingMovementMethod());

        Intent intent = getIntent();
        if (intent != null) {
            Barcode.DriverLicense driverLicense = (Barcode.DriverLicense) intent.getParcelableExtra("DriverLicense");
            if (driverLicense != null) {
                String documentType = driverLicense.documentType;
                tv.append("Document Type:\n" + documentType + "\n\n");
                String firstName = driverLicense.firstName;
                tv.append("First Name:\n" + firstName + "\n\n");
                String middleName = driverLicense.middleName;
                tv.append("Middle Name:\n" + middleName + "\n\n");
                String lastName = driverLicense.lastName;
                tv.append("Last Name:\n" + lastName + "\n\n");
                String gender = driverLicense.gender;
                tv.append("Gender: \n" + gender + "\n\n");
                String addressStreet = driverLicense.addressStreet;
                tv.append("Street:\n" + addressStreet + "\n\n");
                String addressCity = driverLicense.addressCity;
                tv.append("City:\n" + addressCity + "\n\n");
                String addressState = driverLicense.addressState;
                tv.append("State:\n" + addressState + "\n\n");
                String addressZip = driverLicense.addressZip;
                tv.append("Zip:\n" + addressZip + "\n\n");
                String licenseNumber = driverLicense.licenseNumber;
                tv.append("License Number:\n" + licenseNumber + "\n\n");
                String issueDate = driverLicense.issueDate;
                tv.append("Issue Date:\n" + issueDate + "\n\n");
                String expiryDate = driverLicense.expiryDate;
                tv.append("Expiry Date:\n" + expiryDate + "\n\n");
                String birthDate = driverLicense.birthDate;
                tv.append("Birth Date:\n" + birthDate + "\n\n");
                String issuingCountry = driverLicense.issuingCountry;
                tv.append("Issue Country:\n" + issuingCountry + "\n\n");
            }
        }

        setContentView(tv);
    }
    @Override
    public void onBackPressed() {
        super.onBackPressed();
    }
}

Find the onBarcodeDetected(Barcode barcode) function in BarcodeCaptureActivity.java, and then add the following code to start the ResultActivity class when a barcode format is detected as PDF417:

if (barcode.format == Barcode.PDF417) {

    Barcode.DriverLicense driverLicense = barcode.driverLicense;
    if (driverLicense != null) {
        Intent intent = new Intent(BarcodeCaptureActivity.this, ResultActivity.class);
        intent.putExtra("DriverLicense", driverLicense);
        startActivity(intent);
    }
}

Configure the activity in AndroidManifest.xml:

<activity android:name=".ResultActivity" />

Build and launch the app.

Google driver license

Dynamsoft Barcode Detection

Create an Android camera project using fotoapparat:

implementation 'io.fotoapparat.fotoapparat:library:2.3.1'

Get camera frames in the callback function process(Frame frame):

class CodeFrameProcesser implements FrameProcessor {
   @Override
   public void process(@NonNull Frame frame) {
      //isDetected = false;
      if (fotPreviewSize == null) {
         handler.sendEmptyMessage(0);
      }
      if (!detectStart && isCameraOpen) {
         detectStart = true;
         wid = frame.getSize().width;
         hgt = frame.getSize().height;
         Message message = decodeHandler.obtainMessage();
         message.obj = frame;
         decodeHandler.sendMessage(message);
      }
   }
}

Decode barcodes from the frame by calling decodeBuffer() function. The image data format is NV21:

Frame frame = (Frame) msg.obj;
PointResult pointResult = new PointResult();
pointResult.textResults = reader.decodeBuffer(frame.getImage(), frame.getSize().width, frame.getSize().height, frame.getSize().width, EnumImagePixelFormat.IPF_NV21, "");

Dynamsoft Barcode Reader SDK does not provide a driver’s license class yet. We can create a custom class by referring to the AAMVA standard and Google’s Mobile Vision.

Create a class named DriverLicense:

public class DriverLicense implements Parcelable {
    public String documentType;
    public String firstName;
    public String middleName;
    public String lastName;
    public String gender;
    public String addressStreet;
    public String addressCity;
    public String addressState;
    public String addressZip;
    public String licenseNumber;
    public String issueDate;
    public String expiryDate;
    public String birthDate;
    public String issuingCountry;

    public DriverLicense() {

    }
    protected DriverLicense(Parcel in) {
       documentType = in.readString();
       firstName = in.readString();
       middleName = in.readString();
       lastName = in.readString();
       gender = in.readString();
       addressStreet = in.readString();
       addressCity = in.readString();
       addressState = in.readString();
       addressZip = in.readString();
       licenseNumber = in.readString();
       issueDate = in.readString();
       expiryDate = in.readString();
       birthDate = in.readString();
       issuingCountry = in.readString();
    }

    public static final Creator<DriverLicense> CREATOR = new Creator<DriverLicense>() {
        @Override
        public DriverLicense createFromParcel(Parcel in) {
            return new DriverLicense(in);
        }

        @Override
        public DriverLicense[] newArray(int size) {
            return new DriverLicense[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeString(documentType);
        parcel.writeString(firstName);
        parcel.writeString(middleName);
        parcel.writeString(lastName);
        parcel.writeString(gender);
        parcel.writeString(addressStreet);
        parcel.writeString(addressCity);
        parcel.writeString(addressState);
        parcel.writeString(addressZip);
        parcel.writeString(licenseNumber);
        parcel.writeString(issueDate);
        parcel.writeString(expiryDate);
        parcel.writeString(birthDate);
        parcel.writeString(issuingCountry);
    }
}

Create a DBRDriverLicenseUtil class to define the common AAMVA fields:

public static final String CITY = "DAI";
public static final String STATE = "DAJ";
public static final String STREET = "DAG";
public static final String ZIP = "DAK";
public static final String BIRTH_DATE = "DBB";
public static final String EXPIRY_DATE = "DBA";
public static final String FIRST_NAME = "DAC";
public static final String GENDER = "DBC";
public static final String ISSUE_DATE = "DBD";
public static final String ISSUING_COUNTRY = "DCG";
public static final String LAST_NAME = "DCS";
public static final String LICENSE_NUMBER = "DAQ";
public static final String MIDDLE_NAME = "DAD";

Check whether the string recognized from PDF417 complying with the AAMVA standard:

public static boolean ifDriverLicense(String barcodeText) {
    if (barcodeText == null || barcodeText.length() < 21) {
        return false;
    }
    String str = barcodeText.trim().replace("\r", "\n");
    String[] strArray = str.split("\n");
    ArrayList<String> strList = new ArrayList<>();
    for (int i = 0; i < strArray.length; i++) {
        if (strArray[i].length() != 0) {
            strList.add(strArray[i]);
        }
    }
    if (strList.get(0).equals("@")) {
        byte[] data = strList.get(2).getBytes();
        if (((data[0] == 'A' && data[1] == 'N' && data[2] == 'S' && data[3] == 'I' && data[4] == ' ') || (data[0] == 'A' && data[1] == 'A' && data[2] == 'M' && data[3] == 'V' && data[4] == 'A'))
                && (data[5] >= '0' && data[5] <= '9') && (data[6] >= '0' && data[6] <= '9') && (data[7] >= '0' && data[7] <= '9')
                && (data[8] >= '0' && data[8] <= '9') && (data[9] >= '0' && data[9] <= '9') && (data[10] >= '0' && data[10] <= '9')
                && (data[11] >= '0' && data[11] <= '9') && (data[12] >= '0' && data[12] <= '9')
                && (data[13] >= '0' && data[13] <= '9') && (data[14] >= '0' && data[14] <= '9')
        ) {
            return true;
        }
    }
    return false;
}

Fetch the driver’s license information and save data to a HashMap:

public static HashMap<String, String> readUSDriverLicense(String resultText) {
    HashMap<String, String> resultMap = new HashMap<String, String>();
    resultText = resultText.substring(resultText.indexOf("\n") + 1);
    int end = resultText.indexOf("\n");
    String firstLine = resultText.substring(0, end + 1);
    boolean findFirstLine = false;
    for (Map.Entry<String, String> entry : DRIVER_LICENSE_INFO.entrySet()) {
        try {
            int startIndex = resultText.indexOf("\n" + entry.getKey());
            if (startIndex != -1) {
                int endIndex = resultText.indexOf("\n", startIndex + entry.getKey().length() + 1);
                String value = resultText.substring(startIndex + entry.getKey().length() + 1, endIndex);
                resultMap.put(entry.getKey(), value);
            } else if (!findFirstLine) {
                int index = firstLine.indexOf(entry.getKey());
                if (index != -1) {
                    int endIndex = firstLine.indexOf("\n", entry.getKey().length() + 1);
                    String value = firstLine.substring(index + entry.getKey().length(), endIndex);
                    resultMap.put(entry.getKey(), value);
                    findFirstLine = true;
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    return resultMap;
}

Create an instance of DriverLicense class and send it to ResultActivity :

HashMap<String, String> resultMaps = DBRDriverLicenseUtil.readUSDriverLicense(result.barcodeText);
Intent intent = new Intent(MainActivity.this, ResultActivity.class);
DriverLicense driverLicense = new DriverLicense();
driverLicense.documentType = "DL";
driverLicense.firstName = resultMaps.get(DBRDriverLicenseUtil.FIRST_NAME);
driverLicense.middleName = resultMaps.get(DBRDriverLicenseUtil.MIDDLE_NAME);
driverLicense.lastName = resultMaps.get(DBRDriverLicenseUtil.LAST_NAME);
driverLicense.gender = resultMaps.get(DBRDriverLicenseUtil.GENDER);
driverLicense.addressStreet = resultMaps.get(DBRDriverLicenseUtil.STREET);
driverLicense.addressCity = resultMaps.get(DBRDriverLicenseUtil.CITY);
driverLicense.addressState = resultMaps.get(DBRDriverLicenseUtil.STATE);
driverLicense.addressZip = resultMaps.get(DBRDriverLicenseUtil.ZIP);
driverLicense.licenseNumber = resultMaps.get(DBRDriverLicenseUtil.LICENSE_NUMBER);
driverLicense.issueDate = resultMaps.get(DBRDriverLicenseUtil.ISSUE_DATE);
driverLicense.expiryDate = resultMaps.get(DBRDriverLicenseUtil.EXPIRY_DATE);
driverLicense.birthDate = resultMaps.get(DBRDriverLicenseUtil.BIRTH_DATE);
driverLicense.issuingCountry = resultMaps.get(DBRDriverLicenseUtil.ISSUING_COUNTRY);

intent.putExtra("DriverLicense", driverLicense);
startActivity(intent);

After launching the app, I could get the same results as Google’s.

Dynamsoft driver's license

One More Thing: Google vs. Dynamsoft

Since I have implemented two Android barcode reader apps, why not make a comparison?

Google vs Dynamsoft

The left one is based on Google Mobile Vision SDK and the right one is based on Dynamsoft Barcode Reader SDK.

Source Code

https://github.com/yushulx/android-driver-license