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

ListView with Flutter (Part 3)

Trapezium View for Android

4 Cool Facts About Jetpack Compose

Get Your Hand Dirty With Jetpack Datastore.

Datastore implementation

ViaMichelin GPS Route Planner

How to Get Started With Android Application Development

Disable Shift Label Animation from Bottom Navigation — Android.

Nutsack

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

Why should I choose Android Development as a beginner?

New Guide To App Architecture: UI, Domain, And Data layers 2021

App Storage Options

Android build speed optimized cool Tech-Rocketx