The Java – zxing activity scans the embedded QR code

zxing activity scans the embedded QR code… here is a solution to the problem.

zxing activity scans the embedded QR code

Here I expect zxing-core to have some activities that can be launched from other apps, and that’s how they integrate without having to launch some 3rd party app to scan the QR code

I tried adding

compile 'com.google.zxing:core:3.1.0'
compile 'com.google.zxing:android-integration:3.1.0'

Going to the build.gradle file, I can see that these jars have been imported, but I don’t see this class present in the jar

com.google.zxing.client.android.CaptureActivity

In the side jar

$ unzip -l ./modules-2/files-2.1/com.google.zxing/core/3.1.0/908e18674f895e3e1fa8f4a954b8c637a23d2801/core-3.1.0.jar | grep -i 'activity'

I don’t see the class part of the core module here

What am I doing wrong?

Suppressed: java.lang.ClassNotFoundException: com.google.zxing.client.android.CaptureActivity
        at java.lang.Class.classForName(Native Method)
        at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
        at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:504)

I’m using this method :

Intent intent = new Intent("com.google.zxing.client.android.SCAN");
                intent.putExtra("com.google.zxing.client.android.SCAN.SCAN_MODE", "QR_CODE_MODE");
                startActivityForResult(intent, 0);

Just to make sure: my expectation is that the activity is part of the use case library when the user wants to use the embedded scanner instead of delegateing the scan to another application

Solution

This is a long answer, so read it carefully to make sure you don’t miss anything.

You can see a sample project on my Github Repo

Ok, first you have to add the core zxing library jar to the libs folder in the project directory. You must also create a package for the zxing class. See below

foo.bar.yourpackagename.zxing
foo.bar.yourpackagename.zxing.view

Add the following classes to the first package, foo.bar.yourpackagename.zxing


CameraManager.java

import android.app.Activity;
import android.graphics.Rect;
import android.hardware.Camera;
import android.util.Log;
import android.view.Surface;
import com.google.zxing.PlanarYUVLuminanceSource;

/**
 * Camera manager
 */
public class CameraManager {
    /**
     * Fraction of bounds size in view
     */
    private static final double BOUNDS_FRACTION = 0.6;
    /**
     * Fraction of height of bounds in view
     */
    private static final double VERTICAL_HEIGHT_FRACTION = 0.3;

/**
     * Camera instance
     */
    private Camera camera;
    /**
     * Id of camera instance
     */
    private int cameraId;
    /**
     * Current orientation of camera
     * Possible values : 0, 90, 180, 270
     */
    private int orientation;

public CameraManager() {
        this.camera = getCameraInstance();
    }

/**
     * Getter for camera
     *
     * @return camera instance, if it has been initialized
     */
    public Camera getCamera() {
        return camera;
    }

/**
     * Starts preview of camera, if it has been initialized
     */
    public synchronized void startPreview() {
        if (camera != null) {
            camera.startPreview();
        }
    }

/**
     * Stops preview of camera, if it has been initialized
     */
    public synchronized void stopPreview() {
        if (camera != null) {
            camera.stopPreview();
        }
    }

/**
     * Release camera, if it has been initialized
     */
    public synchronized void release() {
        if (camera != null) {
            camera.release();
            camera = null;
        }
    }

/**
     * @return if camera has been initialized<br/>( <code>camera != null</code> )
     */
    public synchronized boolean hasCamera() {
        return camera != null;
    }

/**
     * @return bounding rect for ui
     */
    public final synchronized Rect getBoundingRectUi(int uiWidth, int uiHeight) {
        double heightFraction = BOUNDS_FRACTION;
        double widthFraction = BOUNDS_FRACTION;
        if (orientation == 90 || orientation == 270) {
            heightFraction = VERTICAL_HEIGHT_FRACTION;
        }

Log.d("CameraManager", "dimsUI[" + uiWidth + ", " + uiHeight + "]");

int height = (int) (uiHeight * heightFraction);
        int width = (int) (uiWidth * widthFraction);
        int left = (int) (uiWidth * ((1 - widthFraction) / 2));
        int top = (int) (uiHeight * ((1 - heightFraction) / 2));
        int right = left + width;
        int bottom = top + height;

return new Rect(left, top, right, bottom);
    }

/**
     * @return bounding rect for camera
     */
    public final synchronized Rect getBoundingRect() {
        if (camera != null) {
            Camera.Size previewSize = camera.getParameters().getPreviewSize();
            int previewHeight = previewSize.height;
            int previewWidth = previewSize.width;

double heightFraction = BOUNDS_FRACTION;
            double widthFraction = BOUNDS_FRACTION;
            if (orientation == 90 || orientation == 270) {
                widthFraction = VERTICAL_HEIGHT_FRACTION;
            }
            Log.d("CameraManager", "dimsPrev[" + previewWidth + ", " + previewHeight + "]");

int height = (int) (previewHeight * heightFraction);
            int width = (int) (previewWidth * widthFraction);
            int left = (int) (previewWidth * ((1 - widthFraction) / 2));
            int top = (int) (previewHeight * ((1 - heightFraction) / 2));
            int right = left + width;
            int bottom = top + height;

return new Rect(left, top, right, bottom);
        }
        return null;
    }

/**
     * executes <br/> <code>camera.setOneShotPreviewCallback(callback)</code> if <br/>
     * <code>camera != null</code>
     * @param callback callback to provide
     */
    public synchronized void requestNextFrame(Camera.PreviewCallback callback) {
        if (camera != null) {
            camera.setOneShotPreviewCallback(callback);
        }
    }

/**
     * A factory method to build the appropriate LuminanceSource object based on the format
     * of the preview buffers, as described by Camera.Parameters.
     *
     * @param data   A preview frame.
     * @param width  The width of the image.
     * @param height The height of the image.
     * @return A PlanarYUVLuminanceSource instance.
     */
    public synchronized PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height, Rect boundingRect) {
        switch (orientation) {
            case 0:
                data = flip(data);
                break;
            case 90:
                rotate90(data, width, height);
                return new PlanarYUVLuminanceSource(data, height, width, boundingRect.top, boundingRect.left,
                        boundingRect.height(), boundingRect.width(), false);

case 180:
                break;
            case 270:
                rotate90(data, width, height);
                break;
        }

return new PlanarYUVLuminanceSource(data, width, height, boundingRect.left, boundingRect.top,
                boundingRect.width(), boundingRect.height(), false);
    }

/**
     * Rotates image data
     * @param data raw image data
     * @param width width of image
     * @param height height of image
     */
    public void rotate90(byte[] data, int width, int height) {
        int length = height * width;
        int lengthDec = length - 1;
        int i = 0;
        do {
            int k = (i * height) % lengthDec;
            while (k > i) k = (height * k) % lengthDec;
            if (k != i) swap(data, k, i);
        } while (++i <= (length - 2));
    }

/**
     * Sets camera display orientation depending on current activity orientation
     * @param activity activity, which holds camera preview
     */
    public void setCameraDisplayOrientation(Activity activity) {
        android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
        android.hardware.Camera.getCameraInfo(cameraId, info);
        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }

int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;  compensate the mirror
        } else { // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        orientation = result;
        camera.setDisplayOrientation(result);
    }

/**
     * A safe way to get an instance of the Camera object.
     */
    private Camera getCameraInstance() {
        Camera c = null;
        try {
            cameraId = 0;
            c = Camera.open();  attempt to get a Camera instance
            Camera.Parameters p = c.getParameters();
            p.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
            c.setParameters(p);
        } catch (Exception e) {
            Log.e(CameraManager.class.getSimpleName(), "Camera error", e);
             Camera is not available (in use or does not exist)
        }
        return c;  returns null if camera is unavailable
    }

/**
     * Swaps two elements in array
     * @param data array
     * @param k first element to swap
     * @param i second element to swap
     */
    private static void swap(byte[] data, int k, int i) {
        byte temp = data[k];
        data[k] = data[i];
        data[i] = temp;
    }
}

CaptureHandler.java

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.widget.Toast;

/**
 */
public class CaptureHandler extends Handler {
    public static final String DECODED_DATA = "decoded_data";
    private CameraManager cameraManager;
    private Context context;
    private OnDecodedCallback callback;

public CaptureHandler(CameraManager cameraManager, Context context, OnDecodedCallback callback) {
        this.cameraManager = cameraManager;
        this.context = context;
        this.callback = callback;
    }

@Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case R.id.decoded:
                String data = msg.getData().getString(DECODED_DATA);
                Toast.makeText(context, data, Toast.LENGTH_LONG).show();
                if (callback != null){
                    callback.onDecoded(data);
                }
                break;
            case R.id.decode_failed:
                getting new frame
                cameraManager.requestNextFrame(new PreviewCallback(this, cameraManager));
                break;
        }
    }

public static interface OnDecodedCallback {
        void onDecoded(String decodedData);
    }
}

PreviewCallback.java

import android.hardware.Camera;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import com.google.zxing.*;
import com.google.zxing.common.HybridBinarizer;
import foo.bar.yourpackagename.R;

/**
 * Camera preview callback
 */
public class PreviewCallback implements Camera.PreviewCallback {
    private static final String TAG = PreviewCallback.class.getSimpleName();
    /**
     * Xzing multi format reader
     */
    private final MultiFormatReader multiFormatReader = new MultiFormatReader();
    /**
     * Handler to send messages
     *
     * @see CaptureHandler
     */
    private Handler handler;
    /**
     * Camera manager
     */
    private CameraManager cameraManager;

public PreviewCallback(Handler handler, CameraManager cameraManager) {
        this.handler = handler;
        this.cameraManager = cameraManager;
    }

@Override
    public void onPreviewFrame(byte[] bytes, Camera camera) {
        try {
            Camera.Size previewSize = camera.getParameters().getPreviewSize();
            new DecodeAsyncTask(previewSize.width, previewSize.height).execute(bytes);
        } catch (Exception e) {
            Random errors from zxing
        }
    }

/**
     * Asynchronous task for decoding and finding barcode
     */
    private class DecodeAsyncTask extends AsyncTask<byte[], Void, Result> {
        /**
         * Width of image
         */
        private int width;
        /**
         * Height of image
         */
        private int height;

/**
         * @param width  Width of image
         * @param height Height of image
         */
        private DecodeAsyncTask(int width, int height) {
            this.width = width;
            this.height = height;
        }

@Override
        protected void onPostExecute(Result result) {
            if (result != null) {
                Log.i(TAG, "Decode success.");
                if (handler != null) {
                    Message message = Message.obtain(handler, R.id.decoded);
                    Bundle bundle = new Bundle();
                    bundle.putString(CaptureHandler.DECODED_DATA, result.toString());
                    message.setData(bundle);
                    message.sendToTarget();
                }
            } else {
                Log.i(TAG, "Decode fail.");
                if (handler != null) {
                    Message message = Message.obtain(handler, R.id.decode_failed);
                    message.sendToTarget();
                }
            }
        }

@Override
        protected Result doInBackground(byte[]... datas) {
            if (!cameraManager.hasCamera()) {
                return null;
            }
            Result rawResult = null;
            final PlanarYUVLuminanceSource source =
                    cameraManager.buildLuminanceSource(datas[0], width,
                            height, cameraManager.getBoundingRect());
            if (source != null) {
                BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
                try {
                    rawResult = multiFormatReader.decodeWithState(bitmap);
                } catch (Exception re) {
                    Log.e(TAG, "ERROR", re);
                } finally {
                    multiFormatReader.reset();
                }
            }

return rawResult;
        }
    }
}

And then
After you add these three classes to the foo.bar.yourpackagename.zxing package, you must add the following classes to the foo.bar.yourpackagename.zxing.view package


BoundingView.java

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import foo.bar.yourpackagename.zxing.CameraManager;

/**
 * View for displaying bounds for active camera region
 */
public class BoundingView extends View {
    /**
     * Camera manager
     */
    private CameraManager cameraManager;

public BoundingView(Context context) {
        super(context);
    }

public BoundingView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

/**
     * Sets camera manger
     * @param cameraManager
     */
    public void setCameraManager(CameraManager cameraManager) {
        this.cameraManager = cameraManager;
        invalidate();
    }

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (cameraManager != null) {
             Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
             paint.setColor(Color.parseColor("#00000000"));

             Rect boundingRect = cameraManager.getBoundingRectUi(canvas.getWidth(), canvas.getHeight());
             Rect boundingRect = cameraManager.getBoundingRect();
             canvas.drawRect(boundingRect, paint);

paint = new Paint(Paint.ANTI_ALIAS_FLAG);
             paint.setColor(Color.parseColor("#44FF0000"));
             paint.setStrokeWidth(5);
             int incY = (canvas.getHeight() - boundingRect.height())/2;
             int incX = (canvas.getWidth() - boundingRect.width())/2;

int y = (boundingRect.height()/2) + incY;
             int x = boundingRect.width() + incX;
             canvas.drawLine(incX, y, x, y, paint);
             canvas.drawRect(new Rect(incX, boundingRect.top, x, boundingRect.bottom), paint);
        }
    }
}

CameraPreviewView.java

import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import foo.bar.yourpackagename.zxing.CameraManager;

import java.io.IOException;

/**
 * Camera preview view. Shows camera preview data
 */
public class CameraPreviewView extends SurfaceView implements SurfaceHolder.Callback {
    private static final String TAG = CameraPreviewView.class.getSimpleName();

/**
     * Surface holder for camera preview data
     */
    private SurfaceHolder surfaceHolder;
    /**
     * Camera manager
     */
    private CameraManager cameraManager;

public CameraPreviewView(Context context) {
        super(context);

surfaceHolder = getHolder();
        surfaceHolder.addCallback(this);

 deprecated setting, but required on Android versions prior to 3.0
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

public CameraPreviewView(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);

surfaceHolder = getHolder();
        surfaceHolder.addCallback(this);

 deprecated setting, but required on Android versions prior to 3.0
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

/**
     * Setter for camera manager
     * @param cameraManager camera manager to set
     */
    public void setCameraManager(CameraManager cameraManager) {
        this.cameraManager = cameraManager;
    }

@Override
    public void surfaceCreated(SurfaceHolder holder) {
         The Surface has been created, now tell the camera where to draw the preview.
        try {
            cameraManager.setCameraDisplayOrientation((Activity) getContext());
            cameraManager.getCamera().setPreviewDisplay(holder);
            cameraManager.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

@Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
         empty. Taking care of releasing the Camera preview in activity.
    }

@Override
    public void surfaceDestroyed(SurfaceHolder holder) {
         If your preview can change or rotate, take care of those events here.
         Make sure to stop the preview before resizing or reformatting it.

if (surfaceHolder.getSurface() == null) {
             preview surface does not exist
            return;
        }

 stop preview before making changes
        try {
            cameraManager.stopPreview();
        } catch (Exception e) {
             ignore: tried to stop a non-existent preview
        }

 set preview size and make any resize, rotate or
         reformatting changes here

 start preview with new settings
        try {
            cameraManager.setCameraDisplayOrientation((Activity) getContext());
            cameraManager.getCamera().setPreviewDisplay(surfaceHolder);
            cameraManager.startPreview();

} catch (Exception e) {
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }
}

When you’re done, you can start implementing the Activity. Note that some classes require you to create IDs for certain states, for example

R.id.decode_failed

You need to create these IDs in the application’s resources folder. Next we can start implementing the Activity.


CaptureActivity.java

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.RelativeLayout;

import foo.bar.yourpackagename.R;
import foo.bar.yourpackagename.zxing.CameraManager;
import foo.bar.yourpackagename.zxing.CaptureHandler;
import foo.bar.yourpackagename.zxing.PreviewCallback;
import foo.bar.yourpackagename.zxing.view.BoundingView;
import foo.bar.yourpackagename.zxing.view.CameraPreviewView;

/**
 * Capture activity (camera barcode activity)
 */
public class CaptureActivity extends Activity {
    public static String PUBLIC_STATIC_STRING_IDENTIFIER;

/**
     * Camera preview view
     */
    private CameraPreviewView cameraPreview;
    /**
     * Camera manager
     */
    private CameraManager cameraManager;

/**
     * Capture handler
     */
    private Handler captureHandler;

@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

setContentView(getContentView());

initializeCamera();
        initializeCameraPreview();
        initializeBoundingView();
    }

private View getContentView() {
        RelativeLayout contentView = new RelativeLayout(this);
        contentView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        contentView.addView(getBoundingView());
        contentView.addView(getCameraPreviewView());

return contentView;
    }

private CameraPreviewView getCameraPreviewView() {
        This is the camera SurfaceView
        CameraPreviewView camPreview = new CameraPreviewView(this);
        camPreview.setId(R.id.camera_preview);
        camPreview.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

return camPreview;
    }

private BoundingView getBoundingView() {
        Displays the bounding content
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        params.addRule(RelativeLayout.CENTER_IN_PARENT);

BoundingView boundingView = new BoundingView(this);
        boundingView.setId(R.id.bounding_view);
        boundingView.setLayoutParams(params);

return boundingView;
    }

private void initializeCamera() {
         Create an instance of Camera
        cameraManager = new CameraManager();
        captureHandler = new CaptureHandler(cameraManager, this, new OnDecoded());

requesting next frame for decoding
        cameraManager.requestNextFrame(new PreviewCallback(captureHandler, cameraManager));
    }

private void initializeCameraPreview() {
         Create our Preview view and set it as the content of our activity.
        cameraPreview = (CameraPreviewView) findViewById(R.id.camera_preview);
        cameraPreview.setCameraManager(cameraManager);
    }

private void initializeBoundingView() {
        Set the cameraManager to the bounding view
        ((BoundingView) findViewById(R.id.bounding_view)).setCameraManager(cameraManager);
    }

@Override
    protected void onPause() {
        super.onPause();

We don't want the cameraManager to take up unneeded 
        resources so we release it from memory onPause
        cameraManager.release();
    }

/**
     * This handles the decoded content from the Zxing framework. As
     * soon as the framework has completed the decoding process the CaptureHandler
     * will pass it back via this listener for you to handle it further.
     *
     */
    private class OnDecoded implements CaptureHandler.OnDecodedCallback {
        @Override
        public void onDecoded(String decodedData) {
            Intent resultIntent = new Intent();
            resultIntent.putExtra(PUBLIC_STATIC_STRING_IDENTIFIER, decodedData);
            setResult(Activity.RESULT_OK, resultIntent);
            finish();
        }
    }
}

To start an activity, you simply call

startActivity(new Intent(this, CaptureActivity.class));

Also remember to add the following permissions

<uses-permission android:name="android.permission.CAMERA" />

<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.camera.flash" android:required="false" />

And your Activity

<activity
    android:name=". CaptureActivity"
    android:configChanges="orientation|keyboardHidden"
    android:screenOrientation="landscape"
    android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
    android:windowSoftInputMode="stateAlwaysHidden" >
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    <intent-filter>
        <action android:name="com.google.zxing.client.android.SCAN" />

<category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

to list!

If you implement all these classes correctly, then you should be able to start scanning barcodes.

Related Problems and Solutions