From e9f9decf00e6a8d80fc3dd1e6c5e2030871e3e16 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 23 Jun 2023 18:58:42 +0200 Subject: [PATCH] Import source from https://github.com/dm77/barcodescanner --- dependencies_groups.gradle | 1 - .../external/barcodescanner/core/build.gradle | 26 ++ .../core/BarcodeScannerView.java | 339 ++++++++++++++++++ .../core/CameraHandlerThread.java | 37 ++ .../barcodescanner/core/CameraPreview.java | 312 ++++++++++++++++ .../dm7/barcodescanner/core/CameraUtils.java | 63 ++++ .../barcodescanner/core/CameraWrapper.java | 23 ++ .../dm7/barcodescanner/core/DisplayUtils.java | 41 +++ .../dm7/barcodescanner/core/IViewFinder.java | 53 +++ .../barcodescanner/core/ViewFinderView.java | 259 +++++++++++++ .../core/src/main/res/values-hdpi/strings.xml | 5 + .../src/main/res/values-xhdpi/strings.xml | 5 + .../src/main/res/values-xxhdpi/strings.xml | 5 + .../core/src/main/res/values/attrs.xml | 17 + .../core/src/main/res/values/colors.xml | 6 + .../core/src/main/res/values/strings.xml | 4 + .../barcodescanner/zxing/build.gradle | 29 ++ .../zxing/ZXingScannerView.java | 198 ++++++++++ settings.gradle | 2 + vector/build.gradle | 6 +- 20 files changed, 1425 insertions(+), 6 deletions(-) create mode 100644 library/external/barcodescanner/core/build.gradle create mode 100644 library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/BarcodeScannerView.java create mode 100644 library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraHandlerThread.java create mode 100644 library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraPreview.java create mode 100644 library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraUtils.java create mode 100644 library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraWrapper.java create mode 100644 library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/DisplayUtils.java create mode 100644 library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/IViewFinder.java create mode 100644 library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/ViewFinderView.java create mode 100644 library/external/barcodescanner/core/src/main/res/values-hdpi/strings.xml create mode 100644 library/external/barcodescanner/core/src/main/res/values-xhdpi/strings.xml create mode 100644 library/external/barcodescanner/core/src/main/res/values-xxhdpi/strings.xml create mode 100644 library/external/barcodescanner/core/src/main/res/values/attrs.xml create mode 100644 library/external/barcodescanner/core/src/main/res/values/colors.xml create mode 100644 library/external/barcodescanner/core/src/main/res/values/strings.xml create mode 100644 library/external/barcodescanner/zxing/build.gradle create mode 100644 library/external/barcodescanner/zxing/src/main/java/me/dm7/barcodescanner/zxing/ZXingScannerView.java diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 5505e34b25..9e5d1e4072 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -239,7 +239,6 @@ ext.groups = [ regex: [ ], group: [ - 'me.dm7.barcodescanner', ] ] ] diff --git a/library/external/barcodescanner/core/build.gradle b/library/external/barcodescanner/core/build.gradle new file mode 100644 index 0000000000..583b435dd7 --- /dev/null +++ b/library/external/barcodescanner/core/build.gradle @@ -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 + } +} diff --git a/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/BarcodeScannerView.java b/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/BarcodeScannerView.java new file mode 100644 index 0000000000..ecfba76d89 --- /dev/null +++ b/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/BarcodeScannerView.java @@ -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"); + } + } + + /** + *

Method that creates view that represents visual appearance of a barcode scanner

+ *

Override it to provide your own view for visual appearance of a barcode scanner

+ * + * @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; + } +} + diff --git a/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraHandlerThread.java b/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraHandlerThread.java new file mode 100644 index 0000000000..2d4bcee7bf --- /dev/null +++ b/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraHandlerThread.java @@ -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)); + } + }); + } + }); + } +} diff --git a/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraPreview.java b/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraPreview.java new file mode 100644 index 0000000000..b066e25b2c --- /dev/null +++ b/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraPreview.java @@ -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 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); + } +} diff --git a/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraUtils.java b/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraUtils.java new file mode 100644 index 0000000000..599bd5fa48 --- /dev/null +++ b/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraUtils.java @@ -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 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; + } +} \ No newline at end of file diff --git a/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraWrapper.java b/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraWrapper.java new file mode 100644 index 0000000000..49759ae0f9 --- /dev/null +++ b/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraWrapper.java @@ -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); + } + } +} diff --git a/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/DisplayUtils.java b/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/DisplayUtils.java new file mode 100644 index 0000000000..6c47312221 --- /dev/null +++ b/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/DisplayUtils.java @@ -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; + } + +} diff --git a/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/IViewFinder.java b/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/IViewFinder.java new file mode 100644 index 0000000000..a6caaa4cb6 --- /dev/null +++ b/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/IViewFinder.java @@ -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.
+ * For example see: {@link ViewFinderView#setupViewFinder()} + */ + void setupViewFinder(); + + /** + * Provides {@link Rect} that identifies area where barcode scanner can detect visual codes + *

Note: This rect is a area representation in absolute pixel values.
+ * For example:
+ * If View's size is 1024x800 so framing rect might be 500x400

+ * + * @return {@link Rect} that identifies barcode scanner area + */ + Rect getFramingRect(); + + /** + * Width of a {@link android.view.View} that implements this interface + *

Note: this is already implemented in {@link android.view.View}, + * so you don't need to override method and provide your implementation

+ * + * @return width of a view + */ + int getWidth(); + + /** + * Height of a {@link android.view.View} that implements this interface + *

Note: this is already implemented in {@link android.view.View}, + * so you don't need to override method and provide your implementation

+ * + * @return height of a view + */ + int getHeight(); +} diff --git a/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/ViewFinderView.java b/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/ViewFinderView.java new file mode 100644 index 0000000000..307a8a42b4 --- /dev/null +++ b/library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/ViewFinderView.java @@ -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); + } +} + diff --git a/library/external/barcodescanner/core/src/main/res/values-hdpi/strings.xml b/library/external/barcodescanner/core/src/main/res/values-hdpi/strings.xml new file mode 100644 index 0000000000..d5979caaf8 --- /dev/null +++ b/library/external/barcodescanner/core/src/main/res/values-hdpi/strings.xml @@ -0,0 +1,5 @@ + + + 4 + 60 + \ No newline at end of file diff --git a/library/external/barcodescanner/core/src/main/res/values-xhdpi/strings.xml b/library/external/barcodescanner/core/src/main/res/values-xhdpi/strings.xml new file mode 100644 index 0000000000..7f57b00a3a --- /dev/null +++ b/library/external/barcodescanner/core/src/main/res/values-xhdpi/strings.xml @@ -0,0 +1,5 @@ + + + 5 + 80 + \ No newline at end of file diff --git a/library/external/barcodescanner/core/src/main/res/values-xxhdpi/strings.xml b/library/external/barcodescanner/core/src/main/res/values-xxhdpi/strings.xml new file mode 100644 index 0000000000..d69e2a8b0f --- /dev/null +++ b/library/external/barcodescanner/core/src/main/res/values-xxhdpi/strings.xml @@ -0,0 +1,5 @@ + + + 6 + 100 + \ No newline at end of file diff --git a/library/external/barcodescanner/core/src/main/res/values/attrs.xml b/library/external/barcodescanner/core/src/main/res/values/attrs.xml new file mode 100644 index 0000000000..8e7485ebb3 --- /dev/null +++ b/library/external/barcodescanner/core/src/main/res/values/attrs.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/library/external/barcodescanner/core/src/main/res/values/colors.xml b/library/external/barcodescanner/core/src/main/res/values/colors.xml new file mode 100644 index 0000000000..58c019a6a9 --- /dev/null +++ b/library/external/barcodescanner/core/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #60000000 + #ffcc0000 + #ffafed44 + \ No newline at end of file diff --git a/library/external/barcodescanner/core/src/main/res/values/strings.xml b/library/external/barcodescanner/core/src/main/res/values/strings.xml new file mode 100644 index 0000000000..9cfbb9e1c4 --- /dev/null +++ b/library/external/barcodescanner/core/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + 4 + 60 + diff --git a/library/external/barcodescanner/zxing/build.gradle b/library/external/barcodescanner/zxing/build.gradle new file mode 100644 index 0000000000..e84d0f5bb9 --- /dev/null +++ b/library/external/barcodescanner/zxing/build.gradle @@ -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 + } +} diff --git a/library/external/barcodescanner/zxing/src/main/java/me/dm7/barcodescanner/zxing/ZXingScannerView.java b/library/external/barcodescanner/zxing/src/main/java/me/dm7/barcodescanner/zxing/ZXingScannerView.java new file mode 100644 index 0000000000..d1717ba5be --- /dev/null +++ b/library/external/barcodescanner/zxing/src/main/java/me/dm7/barcodescanner/zxing/ZXingScannerView.java @@ -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 ALL_FORMATS = new ArrayList<>(); + private List 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 formats) { + mFormats = formats; + initMultiFormatReader(); + } + + public void setResultHandler(ResultHandler resultHandler) { + mResultHandler = resultHandler; + } + + public Collection getFormats() { + if(mFormats == null) { + return ALL_FORMATS; + } + return mFormats; + } + + private void initMultiFormatReader() { + Map 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; + } +} diff --git a/settings.gradle b/settings.gradle index bccfa7913a..a0b9ce65ed 100644 --- a/settings.gradle +++ b/settings.gradle @@ -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' diff --git a/vector/build.gradle b/vector/build.gradle index e45db64548..eef4e333cf 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -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