Merge pull request #413 from plougsgaard/android-barcode-scanner-rebased

Android Barcode Scanner
This commit is contained in:
Zack Story 2016-09-16 22:50:52 -07:00 committed by GitHub
commit 1747ec4b0a
7 changed files with 258 additions and 7 deletions

View File

@ -32,4 +32,5 @@ repositories {
dependencies {
compile "com.facebook.react:react-native:0.19.+"
compile "com.google.zxing:core:3.2.1"
}

View File

@ -17,6 +17,8 @@ public class RCTCamera {
private final HashMap<Integer, CameraInfoWrapper> _cameraInfos;
private final HashMap<Integer, Integer> _cameraTypeToIndex;
private final Map<Number, Camera> _cameras;
private boolean _barcodeScannerEnabled = false;
private List<String> _barCodeTypes = null;
private int _orientation = -1;
private int _actualDeviceOrientation = 0;
private int _adjustedDeviceOrientation = 0;
@ -134,6 +136,22 @@ public class RCTCamera {
adjustPreviewLayout(RCTCameraModule.RCT_CAMERA_TYPE_BACK);
}
public boolean isBarcodeScannerEnabled() {
return _barcodeScannerEnabled;
}
public void setBarcodeScannerEnabled(boolean barcodeScannerEnabled) {
_barcodeScannerEnabled = barcodeScannerEnabled;
}
public List<String> getBarCodeTypes() {
return _barCodeTypes;
}
public void setBarCodeTypes(List<String> barCodeTypes) {
_barCodeTypes = barCodeTypes;
}
public int getActualDeviceOrientation() {
return _actualDeviceOrientation;
}

View File

@ -79,7 +79,7 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
public static final int MEDIA_TYPE_IMAGE = 1;
public static final int MEDIA_TYPE_VIDEO = 2;
private final ReactApplicationContext _reactContext;
private static ReactApplicationContext _reactContext;
private RCTSensorOrientationChecker _sensorOrientationChecker;
private MediaRecorder mMediaRecorder = new MediaRecorder();
@ -96,6 +96,10 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
_reactContext.addLifecycleEventListener(this);
}
public static ReactApplicationContext getReactContextSingleton() {
return _reactContext;
}
public void onInfo(MediaRecorder mr, int what, int extra) {
if ( what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED ||
what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {

View File

@ -11,6 +11,8 @@ import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.View;
import java.util.List;
public class RCTCameraView extends ViewGroup {
private final OrientationEventListener _orientationListener;
private final Context _context;
@ -105,6 +107,14 @@ public class RCTCameraView extends ViewGroup {
}
}
public void setBarcodeScannerEnabled(boolean barcodeScannerEnabled) {
RCTCamera.getInstance().setBarcodeScannerEnabled(barcodeScannerEnabled);
}
public void setBarCodeTypes(List<String> types) {
RCTCamera.getInstance().setBarCodeTypes(types);
}
private boolean setActualDeviceOrientation(Context context) {
int actualDeviceOrientation = getDeviceOrientation(context);
if (_actualDeviceOrientation != actualDeviceOrientation) {

View File

@ -8,20 +8,43 @@ import android.content.Context;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.view.TextureView;
import android.os.AsyncTask;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import java.util.List;
import java.util.EnumMap;
import java.util.EnumSet;
class RCTCameraViewFinder extends TextureView implements TextureView.SurfaceTextureListener {
import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
class RCTCameraViewFinder extends TextureView implements TextureView.SurfaceTextureListener, Camera.PreviewCallback {
private int _cameraType;
private SurfaceTexture _surfaceTexture;
private boolean _isStarting;
private boolean _isStopping;
private Camera _camera;
// concurrency lock for barcode scanner to avoid flooding the runtime
public static volatile boolean barcodeScannerTaskLock = false;
// reader instance for the barcode scanner
private final MultiFormatReader _multiFormatReader = new MultiFormatReader();
public RCTCameraViewFinder(Context context, int type) {
super(context);
this.setSurfaceTextureListener(this);
this._cameraType = type;
this.initBarcodeReader(RCTCamera.getInstance().getBarCodeTypes());
}
@Override
@ -112,6 +135,8 @@ class RCTCameraViewFinder extends TextureView implements TextureView.SurfaceText
_camera.setParameters(parameters);
_camera.setPreviewTexture(_surfaceTexture);
_camera.startPreview();
// send previews to `onPreviewFrame`
_camera.setPreviewCallback(this);
} catch (NullPointerException e) {
e.printStackTrace();
} catch (Exception e) {
@ -129,6 +154,7 @@ class RCTCameraViewFinder extends TextureView implements TextureView.SurfaceText
try {
if (_camera != null) {
_camera.stopPreview();
// stop sending previews to `onPreviewFrame`
_camera.setPreviewCallback(null);
RCTCamera.getInstance().releaseCameraInstance(_cameraType);
_camera = null;
@ -141,4 +167,144 @@ class RCTCameraViewFinder extends TextureView implements TextureView.SurfaceText
}
}
}
/**
* Parse barcodes as BarcodeFormat constants.
*
* Supports all iOS codes except [code138, code39mod43, itf14]
*
* Additionally supports [codabar, code128, maxicode, rss14, rssexpanded, upca, upceanextension]
*/
private BarcodeFormat parseBarCodeString(String c) {
if ("aztec".equals(c)) {
return BarcodeFormat.AZTEC;
} else if ("ean13".equals(c)) {
return BarcodeFormat.EAN_13;
} else if ("ean8".equals(c)) {
return BarcodeFormat.EAN_8;
} else if ("ean8".equals(c)) {
return BarcodeFormat.EAN_8;
} else if ("qr".equals(c)) {
return BarcodeFormat.QR_CODE;
} else if ("ean8".equals(c)) {
return BarcodeFormat.EAN_8;
} else if ("pdf417".equals(c)) {
return BarcodeFormat.PDF_417;
} else if ("upce".equals(c)) {
return BarcodeFormat.UPC_E;
} else if ("datamatrix".equals(c)) {
return BarcodeFormat.DATA_MATRIX;
} else if ("code39".equals(c)) {
return BarcodeFormat.CODE_39;
} else if ("code93".equals(c)) {
return BarcodeFormat.CODE_93;
} else if ("interleaved2of5".equals(c)) {
return BarcodeFormat.ITF;
} else if ("codabar".equals(c)) {
return BarcodeFormat.CODABAR;
} else if ("code128".equals(c)) {
return BarcodeFormat.CODE_128;
} else if ("maxicode".equals(c)) {
return BarcodeFormat.MAXICODE;
} else if ("rss14".equals(c)) {
return BarcodeFormat.RSS_14;
} else if ("rssexpanded".equals(c)) {
return BarcodeFormat.RSS_EXPANDED;
} else if ("upca".equals(c)) {
return BarcodeFormat.UPC_A;
} else if ("upceanextension".equals(c)) {
return BarcodeFormat.UPC_EAN_EXTENSION;
} else {
android.util.Log.v("RCTCamera", "Unsupported code.. [" + c + "]");
return null;
}
}
/**
* Initialize the barcode decoder.
*/
private void initBarcodeReader(List<String> barCodeTypes) {
EnumMap<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
EnumSet<BarcodeFormat> decodeFormats = EnumSet.noneOf(BarcodeFormat.class);
if (barCodeTypes != null) {
for (String code : barCodeTypes) {
BarcodeFormat format = parseBarCodeString(code);
if (format != null) {
decodeFormats.add(format);
}
}
}
hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
_multiFormatReader.setHints(hints);
}
/**
* Spawn a barcode reader task if
* - the barcode scanner is enabled (has a onBarCodeRead function)
* - one isn't already running
*
* See {Camera.PreviewCallback}
*/
public void onPreviewFrame(byte[] data, Camera camera) {
if (RCTCamera.getInstance().isBarcodeScannerEnabled() && !RCTCameraViewFinder.barcodeScannerTaskLock) {
RCTCameraViewFinder.barcodeScannerTaskLock = true;
new ReaderAsyncTask(camera, data).execute();
}
}
private class ReaderAsyncTask extends AsyncTask<Void, Void, Void> {
private byte[] imageData;
private final Camera camera;
ReaderAsyncTask(Camera camera, byte[] imageData) {
this.camera = camera;
this.imageData = imageData;
}
@Override
protected Void doInBackground(Void... ignored) {
if (isCancelled()) {
return null;
}
Camera.Size size = camera.getParameters().getPreviewSize();
int width = size.width;
int height = size.height;
// rotate for zxing if orientation is portrait
if (RCTCamera.getInstance().getActualDeviceOrientation() == 0) {
byte[] rotated = new byte[imageData.length];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
rotated[x * height + height - y - 1] = imageData[x + y * width];
}
}
width = size.height;
height = size.width;
imageData = rotated;
}
try {
PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(imageData, width, height, 0, 0, width, height, false);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
Result result = _multiFormatReader.decodeWithState(bitmap);
ReactContext reactContext = RCTCameraModule.getReactContextSingleton();
WritableMap event = Arguments.createMap();
event.putString("data", result.getText());
event.putString("type", result.getBarcodeFormat().toString());
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("CameraBarCodeReadAndroid", event);
} catch (Throwable t) {
// meh
} finally {
_multiFormatReader.reset();
RCTCameraViewFinder.barcodeScannerTaskLock = false;
return null;
}
}
}
}

View File

@ -1,9 +1,14 @@
package com.lwansbrough.RCTCamera;
import android.support.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.*;
import com.facebook.react.uimanager.annotations.ReactProp;
import java.util.List;
import java.util.ArrayList;
public class RCTCameraViewManager extends ViewGroupManager<RCTCameraView> {
private static final String REACT_CLASS = "RCTCamera";
@ -61,4 +66,21 @@ public class RCTCameraViewManager extends ViewGroupManager<RCTCameraView> {
public void setCaptureAudio(RCTCameraView view, boolean captureAudio) {
// TODO - implement video mode
}
@ReactProp(name = "barcodeScannerEnabled")
public void setBarcodeScannerEnabled(RCTCameraView view, boolean barcodeScannerEnabled) {
view.setBarcodeScannerEnabled(barcodeScannerEnabled);
}
@ReactProp(name = "barCodeTypes")
public void setBarCodeTypes(RCTCameraView view, ReadableArray barCodeTypes) {
if (barCodeTypes == null) {
return;
}
List<String> result = new ArrayList<String>(barCodeTypes.size());
for (int i = 0; i < barCodeTypes.size(); i++) {
result.add(barCodeTypes.getString(i));
}
view.setBarCodeTypes(result);
}
}

View File

@ -1,6 +1,7 @@
import React, { Component, PropTypes } from 'react';
import {
NativeAppEventEmitter,
DeviceEventEmitter, // android
NativeAppEventEmitter, // ios
NativeModules,
Platform,
StyleSheet,
@ -46,6 +47,8 @@ function convertNativeProps(props) {
newProps.barCodeTypes = [];
}
newProps.barcodeScannerEnabled = typeof props.onBarCodeRead === 'function'
return newProps;
}
@ -89,6 +92,7 @@ export default class Camera extends Component {
]),
keepAwake: PropTypes.bool,
onBarCodeRead: PropTypes.func,
barcodeScannerEnabled: PropTypes.bool,
onFocusChanged: PropTypes.func,
onZoomChanged: PropTypes.func,
mirrorImage: PropTypes.bool,
@ -141,9 +145,9 @@ export default class Camera extends Component {
}
async componentWillMount() {
this.cameraBarCodeReadListener = NativeAppEventEmitter.addListener('CameraBarCodeRead', this._onBarCodeRead);
this._addOnBarCodeReadListener()
let { captureMode } = convertNativeProps({captureMode: this.props.captureMode})
let { captureMode } = convertNativeProps({ captureMode: this.props.captureMode })
let hasVideoAndAudio = this.props.captureAudio && captureMode === Camera.constants.CaptureMode.video
let check = hasVideoAndAudio ? Camera.checkDeviceAuthorizationStatus : Camera.checkVideoAuthorizationStatus;
@ -154,13 +158,37 @@ export default class Camera extends Component {
}
componentWillUnmount() {
this.cameraBarCodeReadListener.remove();
this._removeOnBarCodeReadListener()
if (this.state.isRecording) {
this.stopCapture();
}
}
componentWillReceiveProps(newProps) {
const { onBarCodeRead } = this.props
if (onBarCodeRead && !newProps.onBarCodeRead) {
this._addOnBarCodeReadListener(newProps)
}
}
_addOnBarCodeReadListener(props) {
const { onBarCodeRead } = props || this.props
this._removeOnBarCodeReadListener()
if (onBarCodeRead) {
this.cameraBarCodeReadListener = Platform.select({
ios: NativeAppEventEmitter.addListener('CameraBarCodeRead', this._onBarCodeRead),
android: DeviceEventEmitter.addListener('CameraBarCodeReadAndroid', this._onBarCodeRead)
})
}
}
_removeOnBarCodeReadListener() {
const listener = this.cameraBarCodeReadListener
if (listener) {
listener.remove()
}
}
render() {
const style = [styles.base, this.props.style];
const nativeProps = convertNativeProps(this.props);
@ -169,7 +197,9 @@ export default class Camera extends Component {
}
_onBarCodeRead = (data) => {
if (this.props.onBarCodeRead) this.props.onBarCodeRead(data)
if (this.props.onBarCodeRead) {
this.props.onBarCodeRead(data)
}
};
capture(options) {