This commit is contained in:
Benoit Marty 2023-06-23 18:58:42 +02:00
parent 69680a9856
commit e9f9decf00
20 changed files with 1425 additions and 6 deletions

View File

@ -239,7 +239,6 @@ ext.groups = [
regex: [
],
group: [
'me.dm7.barcodescanner',
]
]
]

View File

@ -0,0 +1,26 @@
apply plugin: 'com.android.library'
android {
namespace "me.dm7.barcodescanner.core"
compileSdk versions.compileSdk
defaultConfig {
minSdk versions.minSdk
targetSdk versions.targetSdk
}
compileOptions {
sourceCompatibility versions.sourceCompat
targetCompatibility versions.targetCompat
}
}
dependencies {
implementation 'androidx.annotation:annotation-jvm:1.6.0'
}
afterEvaluate {
tasks.findAll { it.name.startsWith("lint") }.each {
it.enabled = false
}
}

View File

@ -0,0 +1,339 @@
package me.dm7.barcodescanner.core;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.hardware.Camera;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import androidx.annotation.ColorInt;
public abstract class BarcodeScannerView extends FrameLayout implements Camera.PreviewCallback {
private CameraWrapper mCameraWrapper;
private CameraPreview mPreview;
private IViewFinder mViewFinderView;
private Rect mFramingRectInPreview;
private CameraHandlerThread mCameraHandlerThread;
private Boolean mFlashState;
private boolean mAutofocusState = true;
private boolean mShouldScaleToFill = true;
private boolean mIsLaserEnabled = true;
@ColorInt private int mLaserColor = getResources().getColor(R.color.viewfinder_laser);
@ColorInt private int mBorderColor = getResources().getColor(R.color.viewfinder_border);
private int mMaskColor = getResources().getColor(R.color.viewfinder_mask);
private int mBorderWidth = getResources().getInteger(R.integer.viewfinder_border_width);
private int mBorderLength = getResources().getInteger(R.integer.viewfinder_border_length);
private boolean mRoundedCorner = false;
private int mCornerRadius = 0;
private boolean mSquaredFinder = false;
private float mBorderAlpha = 1.0f;
private int mViewFinderOffset = 0;
private float mAspectTolerance = 0.1f;
public BarcodeScannerView(Context context) {
super(context);
init();
}
public BarcodeScannerView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
TypedArray a = context.getTheme().obtainStyledAttributes(
attributeSet,
R.styleable.BarcodeScannerView,
0, 0);
try {
setShouldScaleToFill(a.getBoolean(R.styleable.BarcodeScannerView_shouldScaleToFill, true));
mIsLaserEnabled = a.getBoolean(R.styleable.BarcodeScannerView_laserEnabled, mIsLaserEnabled);
mLaserColor = a.getColor(R.styleable.BarcodeScannerView_laserColor, mLaserColor);
mBorderColor = a.getColor(R.styleable.BarcodeScannerView_borderColor, mBorderColor);
mMaskColor = a.getColor(R.styleable.BarcodeScannerView_maskColor, mMaskColor);
mBorderWidth = a.getDimensionPixelSize(R.styleable.BarcodeScannerView_borderWidth, mBorderWidth);
mBorderLength = a.getDimensionPixelSize(R.styleable.BarcodeScannerView_borderLength, mBorderLength);
mRoundedCorner = a.getBoolean(R.styleable.BarcodeScannerView_roundedCorner, mRoundedCorner);
mCornerRadius = a.getDimensionPixelSize(R.styleable.BarcodeScannerView_cornerRadius, mCornerRadius);
mSquaredFinder = a.getBoolean(R.styleable.BarcodeScannerView_squaredFinder, mSquaredFinder);
mBorderAlpha = a.getFloat(R.styleable.BarcodeScannerView_borderAlpha, mBorderAlpha);
mViewFinderOffset = a.getDimensionPixelSize(R.styleable.BarcodeScannerView_finderOffset, mViewFinderOffset);
} finally {
a.recycle();
}
init();
}
private void init() {
mViewFinderView = createViewFinderView(getContext());
}
public final void setupLayout(CameraWrapper cameraWrapper) {
removeAllViews();
mPreview = new CameraPreview(getContext(), cameraWrapper, this);
mPreview.setAspectTolerance(mAspectTolerance);
mPreview.setShouldScaleToFill(mShouldScaleToFill);
if (!mShouldScaleToFill) {
RelativeLayout relativeLayout = new RelativeLayout(getContext());
relativeLayout.setGravity(Gravity.CENTER);
relativeLayout.setBackgroundColor(Color.BLACK);
relativeLayout.addView(mPreview);
addView(relativeLayout);
} else {
addView(mPreview);
}
if (mViewFinderView instanceof View) {
addView((View) mViewFinderView);
} else {
throw new IllegalArgumentException("IViewFinder object returned by " +
"'createViewFinderView()' should be instance of android.view.View");
}
}
/**
* <p>Method that creates view that represents visual appearance of a barcode scanner</p>
* <p>Override it to provide your own view for visual appearance of a barcode scanner</p>
*
* @param context {@link Context}
* @return {@link android.view.View} that implements {@link ViewFinderView}
*/
protected IViewFinder createViewFinderView(Context context) {
ViewFinderView viewFinderView = new ViewFinderView(context);
viewFinderView.setBorderColor(mBorderColor);
viewFinderView.setLaserColor(mLaserColor);
viewFinderView.setLaserEnabled(mIsLaserEnabled);
viewFinderView.setBorderStrokeWidth(mBorderWidth);
viewFinderView.setBorderLineLength(mBorderLength);
viewFinderView.setMaskColor(mMaskColor);
viewFinderView.setBorderCornerRounded(mRoundedCorner);
viewFinderView.setBorderCornerRadius(mCornerRadius);
viewFinderView.setSquareViewFinder(mSquaredFinder);
viewFinderView.setViewFinderOffset(mViewFinderOffset);
return viewFinderView;
}
public void setLaserColor(int laserColor) {
mLaserColor = laserColor;
mViewFinderView.setLaserColor(mLaserColor);
mViewFinderView.setupViewFinder();
}
public void setMaskColor(int maskColor) {
mMaskColor = maskColor;
mViewFinderView.setMaskColor(mMaskColor);
mViewFinderView.setupViewFinder();
}
public void setBorderColor(int borderColor) {
mBorderColor = borderColor;
mViewFinderView.setBorderColor(mBorderColor);
mViewFinderView.setupViewFinder();
}
public void setBorderStrokeWidth(int borderStrokeWidth) {
mBorderWidth = borderStrokeWidth;
mViewFinderView.setBorderStrokeWidth(mBorderWidth);
mViewFinderView.setupViewFinder();
}
public void setBorderLineLength(int borderLineLength) {
mBorderLength = borderLineLength;
mViewFinderView.setBorderLineLength(mBorderLength);
mViewFinderView.setupViewFinder();
}
public void setLaserEnabled(boolean isLaserEnabled) {
mIsLaserEnabled = isLaserEnabled;
mViewFinderView.setLaserEnabled(mIsLaserEnabled);
mViewFinderView.setupViewFinder();
}
public void setIsBorderCornerRounded(boolean isBorderCornerRounded) {
mRoundedCorner = isBorderCornerRounded;
mViewFinderView.setBorderCornerRounded(mRoundedCorner);
mViewFinderView.setupViewFinder();
}
public void setBorderCornerRadius(int borderCornerRadius) {
mCornerRadius = borderCornerRadius;
mViewFinderView.setBorderCornerRadius(mCornerRadius);
mViewFinderView.setupViewFinder();
}
public void setSquareViewFinder(boolean isSquareViewFinder) {
mSquaredFinder = isSquareViewFinder;
mViewFinderView.setSquareViewFinder(mSquaredFinder);
mViewFinderView.setupViewFinder();
}
public void setBorderAlpha(float borderAlpha) {
mBorderAlpha = borderAlpha;
mViewFinderView.setBorderAlpha(mBorderAlpha);
mViewFinderView.setupViewFinder();
}
public void startCamera(int cameraId) {
if(mCameraHandlerThread == null) {
mCameraHandlerThread = new CameraHandlerThread(this);
}
mCameraHandlerThread.startCamera(cameraId);
}
public void setupCameraPreview(CameraWrapper cameraWrapper) {
mCameraWrapper = cameraWrapper;
if(mCameraWrapper != null) {
setupLayout(mCameraWrapper);
mViewFinderView.setupViewFinder();
if(mFlashState != null) {
setFlash(mFlashState);
}
setAutoFocus(mAutofocusState);
}
}
public void startCamera() {
startCamera(CameraUtils.getDefaultCameraId());
}
public void stopCamera() {
if(mCameraWrapper != null) {
mPreview.stopCameraPreview();
mPreview.setCamera(null, null);
mCameraWrapper.mCamera.release();
mCameraWrapper = null;
}
if(mCameraHandlerThread != null) {
mCameraHandlerThread.quit();
mCameraHandlerThread = null;
}
}
public void stopCameraPreview() {
if(mPreview != null) {
mPreview.stopCameraPreview();
}
}
protected void resumeCameraPreview() {
if(mPreview != null) {
mPreview.showCameraPreview();
}
}
public synchronized Rect getFramingRectInPreview(int previewWidth, int previewHeight) {
if (mFramingRectInPreview == null) {
Rect framingRect = mViewFinderView.getFramingRect();
int viewFinderViewWidth = mViewFinderView.getWidth();
int viewFinderViewHeight = mViewFinderView.getHeight();
if (framingRect == null || viewFinderViewWidth == 0 || viewFinderViewHeight == 0) {
return null;
}
Rect rect = new Rect(framingRect);
if(previewWidth < viewFinderViewWidth) {
rect.left = rect.left * previewWidth / viewFinderViewWidth;
rect.right = rect.right * previewWidth / viewFinderViewWidth;
}
if(previewHeight < viewFinderViewHeight) {
rect.top = rect.top * previewHeight / viewFinderViewHeight;
rect.bottom = rect.bottom * previewHeight / viewFinderViewHeight;
}
mFramingRectInPreview = rect;
}
return mFramingRectInPreview;
}
public void setFlash(boolean flag) {
mFlashState = flag;
if(mCameraWrapper != null && CameraUtils.isFlashSupported(mCameraWrapper.mCamera)) {
Camera.Parameters parameters = mCameraWrapper.mCamera.getParameters();
if(flag) {
if(parameters.getFlashMode().equals(Camera.Parameters.FLASH_MODE_TORCH)) {
return;
}
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
} else {
if(parameters.getFlashMode().equals(Camera.Parameters.FLASH_MODE_OFF)) {
return;
}
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
}
mCameraWrapper.mCamera.setParameters(parameters);
}
}
public boolean getFlash() {
if(mCameraWrapper != null && CameraUtils.isFlashSupported(mCameraWrapper.mCamera)) {
Camera.Parameters parameters = mCameraWrapper.mCamera.getParameters();
if(parameters.getFlashMode().equals(Camera.Parameters.FLASH_MODE_TORCH)) {
return true;
} else {
return false;
}
}
return false;
}
public void toggleFlash() {
if(mCameraWrapper != null && CameraUtils.isFlashSupported(mCameraWrapper.mCamera)) {
Camera.Parameters parameters = mCameraWrapper.mCamera.getParameters();
if(parameters.getFlashMode().equals(Camera.Parameters.FLASH_MODE_TORCH)) {
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
} else {
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
}
mCameraWrapper.mCamera.setParameters(parameters);
}
}
public void setAutoFocus(boolean state) {
mAutofocusState = state;
if(mPreview != null) {
mPreview.setAutoFocus(state);
}
}
public void setShouldScaleToFill(boolean shouldScaleToFill) {
mShouldScaleToFill = shouldScaleToFill;
}
public void setAspectTolerance(float aspectTolerance) {
mAspectTolerance = aspectTolerance;
}
public byte[] getRotatedData(byte[] data, Camera camera) {
Camera.Parameters parameters = camera.getParameters();
Camera.Size size = parameters.getPreviewSize();
int width = size.width;
int height = size.height;
int rotationCount = getRotationCount();
if(rotationCount == 1 || rotationCount == 3) {
for (int i = 0; i < rotationCount; i++) {
byte[] rotatedData = new byte[data.length];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++)
rotatedData[x * height + height - y - 1] = data[x + y * width];
}
data = rotatedData;
int tmp = width;
width = height;
height = tmp;
}
}
return data;
}
public int getRotationCount() {
int displayOrientation = mPreview.getDisplayOrientation();
return displayOrientation / 90;
}
}

View File

@ -0,0 +1,37 @@
package me.dm7.barcodescanner.core;
import android.hardware.Camera;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
// This code is mostly based on the top answer here: http://stackoverflow.com/questions/18149964/best-use-of-handlerthread-over-other-similar-classes
public class CameraHandlerThread extends HandlerThread {
private static final String LOG_TAG = "CameraHandlerThread";
private BarcodeScannerView mScannerView;
public CameraHandlerThread(BarcodeScannerView scannerView) {
super("CameraHandlerThread");
mScannerView = scannerView;
start();
}
public void startCamera(final int cameraId) {
Handler localHandler = new Handler(getLooper());
localHandler.post(new Runnable() {
@Override
public void run() {
final Camera camera = CameraUtils.getCameraInstance(cameraId);
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(new Runnable() {
@Override
public void run() {
mScannerView.setupCameraPreview(CameraWrapper.getWrapper(camera, cameraId));
}
});
}
});
}
}

View File

@ -0,0 +1,312 @@
package me.dm7.barcodescanner.core;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.hardware.Camera;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import java.util.List;
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = "CameraPreview";
private CameraWrapper mCameraWrapper;
private Handler mAutoFocusHandler;
private boolean mPreviewing = true;
private boolean mAutoFocus = true;
private boolean mSurfaceCreated = false;
private boolean mShouldScaleToFill = true;
private Camera.PreviewCallback mPreviewCallback;
private float mAspectTolerance = 0.1f;
public CameraPreview(Context context, CameraWrapper cameraWrapper, Camera.PreviewCallback previewCallback) {
super(context);
init(cameraWrapper, previewCallback);
}
public CameraPreview(Context context, AttributeSet attrs, CameraWrapper cameraWrapper, Camera.PreviewCallback previewCallback) {
super(context, attrs);
init(cameraWrapper, previewCallback);
}
public void init(CameraWrapper cameraWrapper, Camera.PreviewCallback previewCallback) {
setCamera(cameraWrapper, previewCallback);
mAutoFocusHandler = new Handler();
getHolder().addCallback(this);
getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void setCamera(CameraWrapper cameraWrapper, Camera.PreviewCallback previewCallback) {
mCameraWrapper = cameraWrapper;
mPreviewCallback = previewCallback;
}
public void setShouldScaleToFill(boolean scaleToFill) {
mShouldScaleToFill = scaleToFill;
}
public void setAspectTolerance(float aspectTolerance) {
mAspectTolerance = aspectTolerance;
}
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
mSurfaceCreated = true;
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i2, int i3) {
if(surfaceHolder.getSurface() == null) {
return;
}
stopCameraPreview();
showCameraPreview();
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
mSurfaceCreated = false;
stopCameraPreview();
}
public void showCameraPreview() {
if(mCameraWrapper != null) {
try {
getHolder().addCallback(this);
mPreviewing = true;
setupCameraParameters();
mCameraWrapper.mCamera.setPreviewDisplay(getHolder());
mCameraWrapper.mCamera.setDisplayOrientation(getDisplayOrientation());
mCameraWrapper.mCamera.setOneShotPreviewCallback(mPreviewCallback);
mCameraWrapper.mCamera.startPreview();
if(mAutoFocus) {
if (mSurfaceCreated) { // check if surface created before using autofocus
safeAutoFocus();
} else {
scheduleAutoFocus(); // wait 1 sec and then do check again
}
}
} catch (Exception e) {
Log.e(TAG, e.toString(), e);
}
}
}
public void safeAutoFocus() {
try {
mCameraWrapper.mCamera.autoFocus(autoFocusCB);
} catch (RuntimeException re) {
// Horrible hack to deal with autofocus errors on Sony devices
// See https://github.com/dm77/barcodescanner/issues/7 for example
scheduleAutoFocus(); // wait 1 sec and then do check again
}
}
public void stopCameraPreview() {
if(mCameraWrapper != null) {
try {
mPreviewing = false;
getHolder().removeCallback(this);
mCameraWrapper.mCamera.cancelAutoFocus();
mCameraWrapper.mCamera.setOneShotPreviewCallback(null);
mCameraWrapper.mCamera.stopPreview();
} catch(Exception e) {
Log.e(TAG, e.toString(), e);
}
}
}
public void setupCameraParameters() {
Camera.Size optimalSize = getOptimalPreviewSize();
Camera.Parameters parameters = mCameraWrapper.mCamera.getParameters();
parameters.setPreviewSize(optimalSize.width, optimalSize.height);
mCameraWrapper.mCamera.setParameters(parameters);
adjustViewSize(optimalSize);
}
private void adjustViewSize(Camera.Size cameraSize) {
Point previewSize = convertSizeToLandscapeOrientation(new Point(getWidth(), getHeight()));
float cameraRatio = ((float) cameraSize.width) / cameraSize.height;
float screenRatio = ((float) previewSize.x) / previewSize.y;
if (screenRatio > cameraRatio) {
setViewSize((int) (previewSize.y * cameraRatio), previewSize.y);
} else {
setViewSize(previewSize.x, (int) (previewSize.x / cameraRatio));
}
}
@SuppressWarnings("SuspiciousNameCombination")
private Point convertSizeToLandscapeOrientation(Point size) {
if (getDisplayOrientation() % 180 == 0) {
return size;
} else {
return new Point(size.y, size.x);
}
}
@SuppressWarnings("SuspiciousNameCombination")
private void setViewSize(int width, int height) {
ViewGroup.LayoutParams layoutParams = getLayoutParams();
int tmpWidth;
int tmpHeight;
if (getDisplayOrientation() % 180 == 0) {
tmpWidth = width;
tmpHeight = height;
} else {
tmpWidth = height;
tmpHeight = width;
}
if (mShouldScaleToFill) {
int parentWidth = ((View) getParent()).getWidth();
int parentHeight = ((View) getParent()).getHeight();
float ratioWidth = (float) parentWidth / (float) tmpWidth;
float ratioHeight = (float) parentHeight / (float) tmpHeight;
float compensation;
if (ratioWidth > ratioHeight) {
compensation = ratioWidth;
} else {
compensation = ratioHeight;
}
tmpWidth = Math.round(tmpWidth * compensation);
tmpHeight = Math.round(tmpHeight * compensation);
}
layoutParams.width = tmpWidth;
layoutParams.height = tmpHeight;
setLayoutParams(layoutParams);
}
public int getDisplayOrientation() {
if (mCameraWrapper == null) {
//If we don't have a camera set there is no orientation so return dummy value
return 0;
}
Camera.CameraInfo info = new Camera.CameraInfo();
if(mCameraWrapper.mCameraId == -1) {
Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, info);
} else {
Camera.getCameraInfo(mCameraWrapper.mCameraId, info);
}
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
int rotation = display.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;
}
return result;
}
private Camera.Size getOptimalPreviewSize() {
if(mCameraWrapper == null) {
return null;
}
List<Camera.Size> sizes = mCameraWrapper.mCamera.getParameters().getSupportedPreviewSizes();
int w = getWidth();
int h = getHeight();
if (DisplayUtils.getScreenOrientation(getContext()) == Configuration.ORIENTATION_PORTRAIT) {
int portraitWidth = h;
h = w;
w = portraitWidth;
}
double targetRatio = (double) w / h;
if (sizes == null) return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
// Try to find an size match aspect ratio and size
for (Camera.Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > mAspectTolerance) continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
// Cannot find the one match the aspect ratio, ignore the requirement
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
public void setAutoFocus(boolean state) {
if(mCameraWrapper != null && mPreviewing) {
if(state == mAutoFocus) {
return;
}
mAutoFocus = state;
if(mAutoFocus) {
if (mSurfaceCreated) { // check if surface created before using autofocus
Log.v(TAG, "Starting autofocus");
safeAutoFocus();
} else {
scheduleAutoFocus(); // wait 1 sec and then do check again
}
} else {
Log.v(TAG, "Cancelling autofocus");
mCameraWrapper.mCamera.cancelAutoFocus();
}
}
}
private Runnable doAutoFocus = new Runnable() {
public void run() {
if(mCameraWrapper != null && mPreviewing && mAutoFocus && mSurfaceCreated) {
safeAutoFocus();
}
}
};
// Mimic continuous auto-focusing
Camera.AutoFocusCallback autoFocusCB = new Camera.AutoFocusCallback() {
public void onAutoFocus(boolean success, Camera camera) {
scheduleAutoFocus();
}
};
private void scheduleAutoFocus() {
mAutoFocusHandler.postDelayed(doAutoFocus, 1000);
}
}

View File

@ -0,0 +1,63 @@
package me.dm7.barcodescanner.core;
import android.hardware.Camera;
import java.util.List;
public class CameraUtils {
/** A safe way to get an instance of the Camera object. */
public static Camera getCameraInstance() {
return getCameraInstance(getDefaultCameraId());
}
/** Favor back-facing camera by default. If none exists, fallback to whatever camera is available **/
public static int getDefaultCameraId() {
int numberOfCameras = Camera.getNumberOfCameras();
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
int defaultCameraId = -1;
for (int i = 0; i < numberOfCameras; i++) {
defaultCameraId = i;
Camera.getCameraInfo(i, cameraInfo);
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
return i;
}
}
return defaultCameraId;
}
/** A safe way to get an instance of the Camera object. */
public static Camera getCameraInstance(int cameraId) {
Camera c = null;
try {
if(cameraId == -1) {
c = Camera.open(); // attempt to get a Camera instance
} else {
c = Camera.open(cameraId); // attempt to get a Camera instance
}
}
catch (Exception e) {
// Camera is not available (in use or does not exist)
}
return c; // returns null if camera is unavailable
}
public static boolean isFlashSupported(Camera camera) {
/* Credits: Top answer at http://stackoverflow.com/a/19599365/868173 */
if (camera != null) {
Camera.Parameters parameters = camera.getParameters();
if (parameters.getFlashMode() == null) {
return false;
}
List<String> supportedFlashModes = parameters.getSupportedFlashModes();
if (supportedFlashModes == null || supportedFlashModes.isEmpty() || supportedFlashModes.size() == 1 && supportedFlashModes.get(0).equals(Camera.Parameters.FLASH_MODE_OFF)) {
return false;
}
} else {
return false;
}
return true;
}
}

View File

@ -0,0 +1,23 @@
package me.dm7.barcodescanner.core;
import android.hardware.Camera;
import androidx.annotation.NonNull;
public class CameraWrapper {
public final Camera mCamera;
public final int mCameraId;
private CameraWrapper(@NonNull Camera camera, int cameraId) {
this.mCamera = camera;
this.mCameraId = cameraId;
}
public static CameraWrapper getWrapper(Camera camera, int cameraId) {
if (camera == null) {
return null;
} else {
return new CameraWrapper(camera, cameraId);
}
}
}

View File

@ -0,0 +1,41 @@
package me.dm7.barcodescanner.core;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.view.Display;
import android.view.WindowManager;
public class DisplayUtils {
public static Point getScreenResolution(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point screenResolution = new Point();
if (android.os.Build.VERSION.SDK_INT >= 13) {
display.getSize(screenResolution);
} else {
screenResolution.set(display.getWidth(), display.getHeight());
}
return screenResolution;
}
public static int getScreenOrientation(Context context)
{
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
int orientation = Configuration.ORIENTATION_UNDEFINED;
if(display.getWidth()==display.getHeight()){
orientation = Configuration.ORIENTATION_SQUARE;
} else{
if(display.getWidth() < display.getHeight()){
orientation = Configuration.ORIENTATION_PORTRAIT;
}else {
orientation = Configuration.ORIENTATION_LANDSCAPE;
}
}
return orientation;
}
}

View File

@ -0,0 +1,53 @@
package me.dm7.barcodescanner.core;
import android.graphics.Rect;
public interface IViewFinder {
void setLaserColor(int laserColor);
void setMaskColor(int maskColor);
void setBorderColor(int borderColor);
void setBorderStrokeWidth(int borderStrokeWidth);
void setBorderLineLength(int borderLineLength);
void setLaserEnabled(boolean isLaserEnabled);
void setBorderCornerRounded(boolean isBorderCornersRounded);
void setBorderAlpha(float alpha);
void setBorderCornerRadius(int borderCornersRadius);
void setViewFinderOffset(int offset);
void setSquareViewFinder(boolean isSquareViewFinder);
/**
* Method that executes when Camera preview is starting.
* It is recommended to update framing rect here and invalidate view after that. <br/>
* For example see: {@link ViewFinderView#setupViewFinder()}
*/
void setupViewFinder();
/**
* Provides {@link Rect} that identifies area where barcode scanner can detect visual codes
* <p>Note: This rect is a area representation in absolute pixel values. <br/>
* For example: <br/>
* If View's size is 1024x800 so framing rect might be 500x400</p>
*
* @return {@link Rect} that identifies barcode scanner area
*/
Rect getFramingRect();
/**
* Width of a {@link android.view.View} that implements this interface
* <p>Note: this is already implemented in {@link android.view.View},
* so you don't need to override method and provide your implementation</p>
*
* @return width of a view
*/
int getWidth();
/**
* Height of a {@link android.view.View} that implements this interface
* <p>Note: this is already implemented in {@link android.view.View},
* so you don't need to override method and provide your implementation</p>
*
* @return height of a view
*/
int getHeight();
}

View File

@ -0,0 +1,259 @@
package me.dm7.barcodescanner.core;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
public class ViewFinderView extends View implements IViewFinder {
private static final String TAG = "ViewFinderView";
private Rect mFramingRect;
private static final float PORTRAIT_WIDTH_RATIO = 6f/8;
private static final float PORTRAIT_WIDTH_HEIGHT_RATIO = 0.75f;
private static final float LANDSCAPE_HEIGHT_RATIO = 5f/8;
private static final float LANDSCAPE_WIDTH_HEIGHT_RATIO = 1.4f;
private static final int MIN_DIMENSION_DIFF = 50;
private static final float DEFAULT_SQUARE_DIMENSION_RATIO = 5f / 8;
private static final int[] SCANNER_ALPHA = {0, 64, 128, 192, 255, 192, 128, 64};
private int scannerAlpha;
private static final int POINT_SIZE = 10;
private static final long ANIMATION_DELAY = 80l;
private final int mDefaultLaserColor = getResources().getColor(R.color.viewfinder_laser);
private final int mDefaultMaskColor = getResources().getColor(R.color.viewfinder_mask);
private final int mDefaultBorderColor = getResources().getColor(R.color.viewfinder_border);
private final int mDefaultBorderStrokeWidth = getResources().getInteger(R.integer.viewfinder_border_width);
private final int mDefaultBorderLineLength = getResources().getInteger(R.integer.viewfinder_border_length);
protected Paint mLaserPaint;
protected Paint mFinderMaskPaint;
protected Paint mBorderPaint;
protected int mBorderLineLength;
protected boolean mSquareViewFinder;
private boolean mIsLaserEnabled;
private float mBordersAlpha;
private int mViewFinderOffset = 0;
public ViewFinderView(Context context) {
super(context);
init();
}
public ViewFinderView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
init();
}
private void init() {
//set up laser paint
mLaserPaint = new Paint();
mLaserPaint.setColor(mDefaultLaserColor);
mLaserPaint.setStyle(Paint.Style.FILL);
//finder mask paint
mFinderMaskPaint = new Paint();
mFinderMaskPaint.setColor(mDefaultMaskColor);
//border paint
mBorderPaint = new Paint();
mBorderPaint.setColor(mDefaultBorderColor);
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setStrokeWidth(mDefaultBorderStrokeWidth);
mBorderPaint.setAntiAlias(true);
mBorderLineLength = mDefaultBorderLineLength;
}
@Override
public void setLaserColor(int laserColor) {
mLaserPaint.setColor(laserColor);
}
@Override
public void setMaskColor(int maskColor) {
mFinderMaskPaint.setColor(maskColor);
}
@Override
public void setBorderColor(int borderColor) {
mBorderPaint.setColor(borderColor);
}
@Override
public void setBorderStrokeWidth(int borderStrokeWidth) {
mBorderPaint.setStrokeWidth(borderStrokeWidth);
}
@Override
public void setBorderLineLength(int borderLineLength) {
mBorderLineLength = borderLineLength;
}
@Override
public void setLaserEnabled(boolean isLaserEnabled) { mIsLaserEnabled = isLaserEnabled; }
@Override
public void setBorderCornerRounded(boolean isBorderCornersRounded) {
if (isBorderCornersRounded) {
mBorderPaint.setStrokeJoin(Paint.Join.ROUND);
} else {
mBorderPaint.setStrokeJoin(Paint.Join.BEVEL);
}
}
@Override
public void setBorderAlpha(float alpha) {
int colorAlpha = (int) (255 * alpha);
mBordersAlpha = alpha;
mBorderPaint.setAlpha(colorAlpha);
}
@Override
public void setBorderCornerRadius(int borderCornersRadius) {
mBorderPaint.setPathEffect(new CornerPathEffect(borderCornersRadius));
}
@Override
public void setViewFinderOffset(int offset) {
mViewFinderOffset = offset;
}
// TODO: Need a better way to configure this. Revisit when working on 2.0
@Override
public void setSquareViewFinder(boolean set) {
mSquareViewFinder = set;
}
public void setupViewFinder() {
updateFramingRect();
invalidate();
}
public Rect getFramingRect() {
return mFramingRect;
}
@Override
public void onDraw(Canvas canvas) {
if(getFramingRect() == null) {
return;
}
drawViewFinderMask(canvas);
drawViewFinderBorder(canvas);
if (mIsLaserEnabled) {
drawLaser(canvas);
}
}
public void drawViewFinderMask(Canvas canvas) {
int width = canvas.getWidth();
int height = canvas.getHeight();
Rect framingRect = getFramingRect();
canvas.drawRect(0, 0, width, framingRect.top, mFinderMaskPaint);
canvas.drawRect(0, framingRect.top, framingRect.left, framingRect.bottom + 1, mFinderMaskPaint);
canvas.drawRect(framingRect.right + 1, framingRect.top, width, framingRect.bottom + 1, mFinderMaskPaint);
canvas.drawRect(0, framingRect.bottom + 1, width, height, mFinderMaskPaint);
}
public void drawViewFinderBorder(Canvas canvas) {
Rect framingRect = getFramingRect();
// Top-left corner
Path path = new Path();
path.moveTo(framingRect.left, framingRect.top + mBorderLineLength);
path.lineTo(framingRect.left, framingRect.top);
path.lineTo(framingRect.left + mBorderLineLength, framingRect.top);
canvas.drawPath(path, mBorderPaint);
// Top-right corner
path.moveTo(framingRect.right, framingRect.top + mBorderLineLength);
path.lineTo(framingRect.right, framingRect.top);
path.lineTo(framingRect.right - mBorderLineLength, framingRect.top);
canvas.drawPath(path, mBorderPaint);
// Bottom-right corner
path.moveTo(framingRect.right, framingRect.bottom - mBorderLineLength);
path.lineTo(framingRect.right, framingRect.bottom);
path.lineTo(framingRect.right - mBorderLineLength, framingRect.bottom);
canvas.drawPath(path, mBorderPaint);
// Bottom-left corner
path.moveTo(framingRect.left, framingRect.bottom - mBorderLineLength);
path.lineTo(framingRect.left, framingRect.bottom);
path.lineTo(framingRect.left + mBorderLineLength, framingRect.bottom);
canvas.drawPath(path, mBorderPaint);
}
public void drawLaser(Canvas canvas) {
Rect framingRect = getFramingRect();
// Draw a red "laser scanner" line through the middle to show decoding is active
mLaserPaint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
int middle = framingRect.height() / 2 + framingRect.top;
canvas.drawRect(framingRect.left + 2, middle - 1, framingRect.right - 1, middle + 2, mLaserPaint);
postInvalidateDelayed(ANIMATION_DELAY,
framingRect.left - POINT_SIZE,
framingRect.top - POINT_SIZE,
framingRect.right + POINT_SIZE,
framingRect.bottom + POINT_SIZE);
}
@Override
protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld) {
updateFramingRect();
}
public synchronized void updateFramingRect() {
Point viewResolution = new Point(getWidth(), getHeight());
int width;
int height;
int orientation = DisplayUtils.getScreenOrientation(getContext());
if(mSquareViewFinder) {
if(orientation != Configuration.ORIENTATION_PORTRAIT) {
height = (int) (getHeight() * DEFAULT_SQUARE_DIMENSION_RATIO);
width = height;
} else {
width = (int) (getWidth() * DEFAULT_SQUARE_DIMENSION_RATIO);
height = width;
}
} else {
if(orientation != Configuration.ORIENTATION_PORTRAIT) {
height = (int) (getHeight() * LANDSCAPE_HEIGHT_RATIO);
width = (int) (LANDSCAPE_WIDTH_HEIGHT_RATIO * height);
} else {
width = (int) (getWidth() * PORTRAIT_WIDTH_RATIO);
height = (int) (PORTRAIT_WIDTH_HEIGHT_RATIO * width);
}
}
if(width > getWidth()) {
width = getWidth() - MIN_DIMENSION_DIFF;
}
if(height > getHeight()) {
height = getHeight() - MIN_DIMENSION_DIFF;
}
int leftOffset = (viewResolution.x - width) / 2;
int topOffset = (viewResolution.y - height) / 2;
mFramingRect = new Rect(leftOffset + mViewFinderOffset, topOffset + mViewFinderOffset, leftOffset + width - mViewFinderOffset, topOffset + height - mViewFinderOffset);
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="viewfinder_border_width">4</integer>
<integer name="viewfinder_border_length">60</integer>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="viewfinder_border_width">5</integer>
<integer name="viewfinder_border_length">80</integer>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="viewfinder_border_width">6</integer>
<integer name="viewfinder_border_length">100</integer>
</resources>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="BarcodeScannerView">
<attr name="shouldScaleToFill" format="boolean" />
<attr name="laserEnabled" format="boolean" />
<attr name="laserColor" format="color" />
<attr name="borderColor" format="color" />
<attr name="maskColor" format="color" />
<attr name="borderWidth" format="dimension" />
<attr name="borderLength" format="dimension" />
<attr name="roundedCorner" format="boolean" />
<attr name="cornerRadius" format="dimension" />
<attr name="squaredFinder" format="boolean" />
<attr name="borderAlpha" format="float" />
<attr name="finderOffset" format="dimension" />
</declare-styleable>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="viewfinder_mask">#60000000</color>
<color name="viewfinder_laser">#ffcc0000</color>
<color name="viewfinder_border">#ffafed44</color>
</resources>

View File

@ -0,0 +1,4 @@
<resources>
<integer name="viewfinder_border_width">4</integer>
<integer name="viewfinder_border_length">60</integer>
</resources>

View File

@ -0,0 +1,29 @@
apply plugin: 'com.android.library'
android {
namespace "me.dm7.barcodescanner.zxing"
compileSdk versions.compileSdk
defaultConfig {
minSdk versions.minSdk
targetSdk versions.targetSdk
}
compileOptions {
sourceCompatibility versions.sourceCompat
targetCompatibility versions.targetCompat
}
}
dependencies {
api project(":library:external:barcodescanner:core")
// Stick to 3.3.3 because of https://github.com/zxing/zxing/issues/1170
api 'com.google.zxing:core:3.3.3'
}
afterEvaluate {
tasks.findAll { it.name.startsWith("lint") }.each {
it.enabled = false
}
}

View File

@ -0,0 +1,198 @@
package me.dm7.barcodescanner.zxing;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.hardware.Camera;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.util.Log;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import me.dm7.barcodescanner.core.BarcodeScannerView;
import me.dm7.barcodescanner.core.DisplayUtils;
public class ZXingScannerView extends BarcodeScannerView {
private static final String TAG = "ZXingScannerView";
public interface ResultHandler {
void handleResult(Result rawResult);
}
private MultiFormatReader mMultiFormatReader;
public static final List<BarcodeFormat> ALL_FORMATS = new ArrayList<>();
private List<BarcodeFormat> mFormats;
private ResultHandler mResultHandler;
static {
ALL_FORMATS.add(BarcodeFormat.AZTEC);
ALL_FORMATS.add(BarcodeFormat.CODABAR);
ALL_FORMATS.add(BarcodeFormat.CODE_39);
ALL_FORMATS.add(BarcodeFormat.CODE_93);
ALL_FORMATS.add(BarcodeFormat.CODE_128);
ALL_FORMATS.add(BarcodeFormat.DATA_MATRIX);
ALL_FORMATS.add(BarcodeFormat.EAN_8);
ALL_FORMATS.add(BarcodeFormat.EAN_13);
ALL_FORMATS.add(BarcodeFormat.ITF);
ALL_FORMATS.add(BarcodeFormat.MAXICODE);
ALL_FORMATS.add(BarcodeFormat.PDF_417);
ALL_FORMATS.add(BarcodeFormat.QR_CODE);
ALL_FORMATS.add(BarcodeFormat.RSS_14);
ALL_FORMATS.add(BarcodeFormat.RSS_EXPANDED);
ALL_FORMATS.add(BarcodeFormat.UPC_A);
ALL_FORMATS.add(BarcodeFormat.UPC_E);
ALL_FORMATS.add(BarcodeFormat.UPC_EAN_EXTENSION);
}
public ZXingScannerView(Context context) {
super(context);
initMultiFormatReader();
}
public ZXingScannerView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
initMultiFormatReader();
}
public void setFormats(List<BarcodeFormat> formats) {
mFormats = formats;
initMultiFormatReader();
}
public void setResultHandler(ResultHandler resultHandler) {
mResultHandler = resultHandler;
}
public Collection<BarcodeFormat> getFormats() {
if(mFormats == null) {
return ALL_FORMATS;
}
return mFormats;
}
private void initMultiFormatReader() {
Map<DecodeHintType,Object> hints = new EnumMap<>(DecodeHintType.class);
hints.put(DecodeHintType.POSSIBLE_FORMATS, getFormats());
mMultiFormatReader = new MultiFormatReader();
mMultiFormatReader.setHints(hints);
}
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
if(mResultHandler == null) {
return;
}
try {
Camera.Parameters parameters = camera.getParameters();
Camera.Size size = parameters.getPreviewSize();
int width = size.width;
int height = size.height;
if (DisplayUtils.getScreenOrientation(getContext()) == Configuration.ORIENTATION_PORTRAIT) {
int rotationCount = getRotationCount();
if (rotationCount == 1 || rotationCount == 3) {
int tmp = width;
width = height;
height = tmp;
}
data = getRotatedData(data, camera);
}
Result rawResult = null;
PlanarYUVLuminanceSource source = buildLuminanceSource(data, width, height);
if (source != null) {
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
try {
rawResult = mMultiFormatReader.decodeWithState(bitmap);
} catch (ReaderException re) {
// continue
} catch (NullPointerException npe) {
// This is terrible
} catch (ArrayIndexOutOfBoundsException aoe) {
} finally {
mMultiFormatReader.reset();
}
if (rawResult == null) {
LuminanceSource invertedSource = source.invert();
bitmap = new BinaryBitmap(new HybridBinarizer(invertedSource));
try {
rawResult = mMultiFormatReader.decodeWithState(bitmap);
} catch (NotFoundException e) {
// continue
} finally {
mMultiFormatReader.reset();
}
}
}
final Result finalRawResult = rawResult;
if (finalRawResult != null) {
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
// Stopping the preview can take a little long.
// So we want to set result handler to null to discard subsequent calls to
// onPreviewFrame.
ResultHandler tmpResultHandler = mResultHandler;
mResultHandler = null;
stopCameraPreview();
if (tmpResultHandler != null) {
tmpResultHandler.handleResult(finalRawResult);
}
}
});
} else {
camera.setOneShotPreviewCallback(this);
}
} catch(RuntimeException e) {
// TODO: Terrible hack. It is possible that this method is invoked after camera is released.
Log.e(TAG, e.toString(), e);
}
}
public void resumeCameraPreview(ResultHandler resultHandler) {
mResultHandler = resultHandler;
super.resumeCameraPreview();
}
public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
Rect rect = getFramingRectInPreview(width, height);
if (rect == null) {
return null;
}
// Go ahead and assume it's YUV rather than die.
PlanarYUVLuminanceSource source = null;
try {
source = new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
rect.width(), rect.height(), false);
} catch(Exception e) {
}
return source;
}
}

View File

@ -15,6 +15,8 @@ include ':library:external:textdrawable'
include ':library:external:autocomplete'
include ':library:external:realmfieldnameshelper'
include ':library:external:span'
include ':library:external:barcodescanner:core'
include ':library:external:barcodescanner:zxing'
include ':library:rustCrypto'
include ':matrix-sdk-android'

View File

@ -263,11 +263,7 @@ dependencies {
// Stick to 3.3.3 because of https://github.com/zxing/zxing/issues/1170
implementation 'com.google.zxing:core:3.3.3'
// Excludes the legacy support library annotation usages
// https://github.com/dm77/barcodescanner/blob/d036996c8a6f36a68843ffe539c834c28944b2d5/core/src/main/java/me/dm7/barcodescanner/core/CameraWrapper.java#L4
implementation ('me.dm7.barcodescanner:zxing:1.9.13') {
exclude group: 'com.android.support', module: 'support-v4'
}
implementation project(":library:external:barcodescanner:zxing")
// Emoji Keyboard
api libs.vanniktech.emojiMaterial