The Quickest Way to Create an Android QR Code Scanner

Prerequisites

  • CameraX
<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
dependencies {
...
def camerax_version = "1.0.1"
implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-lifecycle:$camerax_version"
implementation "androidx.camera:camera-view:1.0.0-alpha27"
}
  • Dynamsoft Camera Enhancer
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
...
maven{url "https://download.dynamsoft.com/maven/dce/aar"}
}
}
dependencies {
...
implementation 'com.dynamsoft:dynamsoftcameraenhancer:2.1.0@aar'
}
  • Dynamsoft Barcode Reader
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
...
maven{url "https://download.dynamsoft.com/maven/dbr/aar"}
}
}
dependencies {
...
implementation 'com.dynamsoft:dynamsoftbarcodereader:8.9.0@aar'
}

Creating Android Camera Preview within 5 Minutes

Three Steps to Implement Camera Preview with CameraX

  1. Create the UI layout which contains the CameraX preview view:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CameraXActivity">

<androidx.camera.view.PreviewView
android:id="@+id/camerax_viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camerax_main);
previewView = findViewById(R.id.camerax_viewFinder);

if (!CameraUtils.allPermissionsGranted(this)) {
CameraUtils.getRuntimePermissions(this);
} else {
startCamera();
}
}

private static String[] getRequiredPermissions(Context context) {
try {
PackageInfo info =
context.getPackageManager()
.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
String[] ps = info.requestedPermissions;
if (ps != null && ps.length > 0) {
return ps;
} else {
return new String[0];
}
} catch (Exception e) {
return new String[0];
}
}

public static boolean allPermissionsGranted(Context context) {
for (String permission : getRequiredPermissions(context)) {
if (!isPermissionGranted(context, permission)) {
return false;
}
}
return true;
}

public static void getRuntimePermissions(Activity activity) {
List<String> allNeededPermissions = new ArrayList<>();
for (String permission : getRequiredPermissions(activity)) {
if (!isPermissionGranted(activity, permission)) {
allNeededPermissions.add(permission);
}
}

if (!allNeededPermissions.isEmpty()) {
ActivityCompat.requestPermissions(
activity, allNeededPermissions.toArray(new String[0]), PERMISSION_REQUESTS);
}
}

private static boolean isPermissionGranted(Context context, String permission) {
if (ContextCompat.checkSelfPermission(context, permission)
== PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "Permission granted: " + permission);
return true;
}
Log.i(TAG, "Permission NOT granted: " + permission);
return false;
}
private void startCamera() {
ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
ProcessCameraProvider.getInstance(getApplication());
cameraProviderFuture.addListener(
() -> {
try {
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();

Preview.Builder builder = new Preview.Builder();
Preview previewUseCase = builder.build();
previewUseCase.setSurfaceProvider(previewView.getSurfaceProvider());
CameraSelector cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;
cameraProvider.unbindAll();
cameraProvider.bindToLifecycle(this, cameraSelector, previewUseCase);
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, "Unhandled exception", e);
}
},
ContextCompat.getMainExecutor(getApplication()));
}

Two Steps to Implement Camera Preview with Dynamsoft Camera Enhancer

  1. Create the UI layout which contains the DCE preview view:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CameraXActivity">

<com.dynamsoft.dce.DCECameraView
android:id="@+id/dce_viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dce_main);
previewView = findViewById(R.id.dce_viewFinder);

cameraEnhancer = new CameraEnhancer(this);
cameraEnhancer.setCameraView(previewView);
cameraEnhancer.addListener(this);
}

@Override
protected void onResume() {
super.onResume();
try {
cameraEnhancer.open();
} catch (CameraEnhancerException e) {
e.printStackTrace();
}
}

@Override
protected void onPause() {
super.onPause();
try {
cameraEnhancer.close();
} catch (CameraEnhancerException e) {
e.printStackTrace();
}
}

All in One

package com.example.qrcodescanner;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;

import androidx.appcompat.app.AppCompatActivity;

public class EntryChoiceActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.entry_choice);

findViewById(R.id.camerax_entry_point).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(EntryChoiceActivity.this, CameraXActivity.class);
startActivity(intent);
}
});

findViewById(R.id.dce_entry_point).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(EntryChoiceActivity.this, DceActivity.class);
startActivity(intent);
}
});
}
}

Turning Android Camera into QR Code Scanner

How to Set the Camera Frame Callback

ImageAnalysis analysisUseCase = new ImageAnalysis.Builder().build();
analysisUseCase.setAnalyzer(cameraExecutor,
imageProxy -> {
// image processing
// Must call close to keep receiving frames.
imageProxy.close();
});
cameraProvider.bindToLifecycle(this, cameraSelector, previewUseCase, analysisUseCase);
public class DceActivity extends AppCompatActivity implements DCEFrameListener {
@Override
public void frameOutputCallback(DCEFrame dceFrame, long l) {
// image processing
}
}

Decoding QR Code

analysisUseCase.setAnalyzer(cameraExecutor,
imageProxy -> {
TextResult[] results = null;
ByteBuffer buffer = imageProxy.getPlanes()[0].getBuffer();
int nRowStride = imageProxy.getPlanes()[0].getRowStride();
int nPixelStride = imageProxy.getPlanes()[0].getPixelStride();
int length = buffer.remaining();
byte[] bytes = new byte[length];
buffer.get(bytes);
try {
results = reader.decodeBuffer(bytes, imageProxy.getWidth(), imageProxy.getHeight(), nRowStride * nPixelStride, EnumImagePixelFormat.IPF_NV21, "");
} catch (BarcodeReaderException e) {
e.printStackTrace();
}

// Must call close to keep receiving frames.
imageProxy.close();
});
public void frameOutputCallback(DCEFrame dceFrame, long l) {
TextResult[] results = null;
try {
results = reader.decodeBufferedImage(dceFrame.toBitmap(), "");
} catch (BarcodeReaderException e) {
e.printStackTrace();
}
}

Using Zoom and Torch to Boost the Frame Quality

Android Camera Zoom

public class ZoomController {
public final static String TAG = "ZoomController";
private float currentFactor = 1.0f;
private float minZoomRatio = 1.0f, maxZoomRatio = 1.0f;
private ZoomStatus zoomStatus;
private ScaleGestureDetector scaleGestureDetector;
private ScaleGestureDetector.OnScaleGestureListener scaleGestureListener = new ScaleGestureDetector.OnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
Log.i(TAG, "onScale: " + detector.getScaleFactor());
currentFactor = detector.getScaleFactor() * currentFactor;
if (currentFactor < minZoomRatio) currentFactor = minZoomRatio;
if (currentFactor > maxZoomRatio) currentFactor = maxZoomRatio;

if (zoomStatus != null) {
zoomStatus.onZoomChange(currentFactor);
}

return true;
}

@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}

@Override
public void onScaleEnd(ScaleGestureDetector detector) {
}
};

public ZoomController(Activity activity) {
scaleGestureDetector = new ScaleGestureDetector(activity, scaleGestureListener);
}

public interface ZoomStatus {
void onZoomChange(float ratio);
}

public void addListener(ZoomStatus zoomStatus) {
this.zoomStatus = zoomStatus;
}

public void initZoomRatio(float minZoomRatio, float maxZoomRatio) {
this.minZoomRatio = minZoomRatio;
this.maxZoomRatio = maxZoomRatio;
}

public boolean onTouchEvent(MotionEvent event) {
return scaleGestureDetector.onTouchEvent(event);
}
}
if (camera != null) {
camera.getCameraControl().setZoomRatio(ratio);
}
try {
cameraEnhancer.setZoom(ratio);
} catch (CameraEnhancerException e) {
e.printStackTrace();
}

Android Camera Torch

public class AutoTorchController implements SensorEventListener {
public final static String TAG = "AutoTorchController";
private SensorManager sensorManager;
private TorchStatus torchStatus;

public interface TorchStatus {
void onTorchChange(boolean status);
}

public AutoTorchController(Activity activity) {
sensorManager = (SensorManager)activity.getSystemService(SENSOR_SERVICE);
}

public void onStart() {
Sensor lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
if(lightSensor != null){
sensorManager.registerListener(
this,
lightSensor,
SensorManager.SENSOR_DELAY_NORMAL);

}
}

public void onStop() {
sensorManager.unregisterListener(this);
}

@Override
public void onSensorChanged(SensorEvent event) {
if(event.sensor.getType() == Sensor.TYPE_LIGHT){
if (event.values[0] < 20) {
if (torchStatus != null) torchStatus.onTorchChange(true);
}
else {
if (torchStatus != null) torchStatus.onTorchChange(false);
}
}
}

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}

public void addListener(TorchStatus torchStatus) {
this.torchStatus = torchStatus;
}
}
if (camera != null) camera.getCameraControl().enableTorch(status);
if (status) {
try {
cameraEnhancer.turnOnTorch();
} catch (CameraEnhancerException e) {
e.printStackTrace();
}
}
else {
try {
cameraEnhancer.turnOffTorch();
} catch (CameraEnhancerException e) {
e.printStackTrace();
}
}

Source Code

https://github.com/yushulx/android-camera2-preview-qr-code-scanner

--

--

--

Manager of Dynamsoft Open Source Projects | Tech Lover

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Android Alert Dialogs in Kotlin — Tutorial

Jetpack Compose: @PreviewParameter causes Preview to disappear!

Make COVID — 19 App with Kotlin

Velvet Taco

How memory is allocated and deallocated for classes and variables in android

Image Recognition / Classification Android App using TensorFlow model | Easy and fast…

RecyclerView with nested scrolling inside items

Downloading Media through Exoplayer Part 1

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Xiao Ling

Xiao Ling

Manager of Dynamsoft Open Source Projects | Tech Lover

More from Medium

Room Database using ViewModel and live data-Kotlin-Part 1.

Create a custom BLE peripheral device using Android mobile

Notes on Android Dev — Lecture 2 — Intro to Kotlin

Import AOSP framework jar file