|
|
@@ -0,0 +1,885 @@
|
|
|
+package com.journeyapps.barcodescanner;
|
|
|
+
|
|
|
+import android.annotation.SuppressLint;
|
|
|
+import android.annotation.TargetApi;
|
|
|
+import android.content.Context;
|
|
|
+import android.content.res.TypedArray;
|
|
|
+import android.graphics.Color;
|
|
|
+import android.graphics.Matrix;
|
|
|
+import android.graphics.Rect;
|
|
|
+import android.graphics.SurfaceTexture;
|
|
|
+import android.os.Build;
|
|
|
+import android.os.Bundle;
|
|
|
+import android.os.Handler;
|
|
|
+import android.os.Message;
|
|
|
+import android.os.Parcelable;
|
|
|
+import android.util.AttributeSet;
|
|
|
+import android.util.Log;
|
|
|
+import android.view.SurfaceHolder;
|
|
|
+import android.view.SurfaceView;
|
|
|
+import android.view.TextureView;
|
|
|
+import android.view.ViewGroup;
|
|
|
+import android.view.WindowManager;
|
|
|
+
|
|
|
+import com.journeyapps.barcodescanner.R;
|
|
|
+import com.journeyapps.barcodescanner.camera.CameraInstance;
|
|
|
+import com.journeyapps.barcodescanner.camera.CameraParametersCallback;
|
|
|
+import com.journeyapps.barcodescanner.camera.CameraSettings;
|
|
|
+import com.journeyapps.barcodescanner.camera.CameraSurface;
|
|
|
+import com.journeyapps.barcodescanner.camera.CenterCropStrategy;
|
|
|
+import com.journeyapps.barcodescanner.camera.FitCenterStrategy;
|
|
|
+import com.journeyapps.barcodescanner.camera.DisplayConfiguration;
|
|
|
+import com.journeyapps.barcodescanner.camera.FitXYStrategy;
|
|
|
+import com.journeyapps.barcodescanner.camera.PreviewScalingStrategy;
|
|
|
+
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+/**
|
|
|
+ * CameraPreview is a view that handles displaying of a camera preview on a SurfaceView. It is
|
|
|
+ * intended to be used as a base for realtime processing of camera images, e.g. barcode decoding
|
|
|
+ * or OCR, although none of this happens in CameraPreview itself.
|
|
|
+ *
|
|
|
+ * The camera is managed on a separate thread, using CameraInstance.
|
|
|
+ *
|
|
|
+ * Two methods MUST be called on CameraPreview to manage its state:
|
|
|
+ * 1. resume() - initialize the camera and start the preview. Call from the Activity's onResume().
|
|
|
+ * 2. pause() - stop the preview and release any resources. Call from the Activity's onPause().
|
|
|
+ *
|
|
|
+ * Startup sequence:
|
|
|
+ *
|
|
|
+ * 1. Create SurfaceView.
|
|
|
+ * 2. open camera.
|
|
|
+ * 2. layout this container, to get size
|
|
|
+ * 3. set display config, according to the container size
|
|
|
+ * 4. configure()
|
|
|
+ * 5. wait for preview size to be ready
|
|
|
+ * 6. set surface size according to preview size
|
|
|
+ * 7. set surface and start preview
|
|
|
+ */
|
|
|
+public class CameraPreview extends ViewGroup {
|
|
|
+ public interface StateListener {
|
|
|
+ /**
|
|
|
+ * Preview and frame sizes are determined.
|
|
|
+ */
|
|
|
+ void previewSized();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Preview has started.
|
|
|
+ */
|
|
|
+ void previewStarted();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Preview has stopped.
|
|
|
+ */
|
|
|
+ void previewStopped();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The camera has errored, and cannot display a preview.
|
|
|
+ *
|
|
|
+ * @param error the error
|
|
|
+ */
|
|
|
+ void cameraError(Exception error);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The camera has been closed.
|
|
|
+ */
|
|
|
+ void cameraClosed();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static final String TAG = CameraPreview.class.getSimpleName();
|
|
|
+
|
|
|
+ private CameraInstance cameraInstance;
|
|
|
+
|
|
|
+ private WindowManager windowManager;
|
|
|
+
|
|
|
+ private Handler stateHandler;
|
|
|
+
|
|
|
+ private boolean useTextureView = false;
|
|
|
+
|
|
|
+ private SurfaceView surfaceView;
|
|
|
+ private TextureView textureView;
|
|
|
+
|
|
|
+ private boolean previewActive = false;
|
|
|
+
|
|
|
+ private RotationListener rotationListener;
|
|
|
+ private int openedOrientation = -1;
|
|
|
+
|
|
|
+ // Delay after rotation change is detected before we reorientate ourselves.
|
|
|
+ // This is to avoid double-reinitialization when the Activity is destroyed and recreated.
|
|
|
+ private static final int ROTATION_LISTENER_DELAY_MS = 250;
|
|
|
+
|
|
|
+ private List<StateListener> stateListeners = new ArrayList<>();
|
|
|
+
|
|
|
+ private DisplayConfiguration displayConfiguration;
|
|
|
+ private CameraSettings cameraSettings = new CameraSettings();
|
|
|
+
|
|
|
+ // Size of this container, non-null after layout is performed
|
|
|
+ private Size containerSize;
|
|
|
+
|
|
|
+ // Size of the preview resolution
|
|
|
+ private Size previewSize;
|
|
|
+
|
|
|
+ // Rect placing the preview surface
|
|
|
+ private Rect surfaceRect;
|
|
|
+
|
|
|
+ // Size of the current surface. non-null if the surface is ready
|
|
|
+ private Size currentSurfaceSize;
|
|
|
+
|
|
|
+ // Framing rectangle relative to this view
|
|
|
+ private Rect framingRect = null;
|
|
|
+
|
|
|
+ // Framing rectangle relative to the preview resolution
|
|
|
+ private Rect previewFramingRect = null;
|
|
|
+
|
|
|
+ // Size of the framing rectangle. If null, defaults to using a margin percentage.
|
|
|
+ private Size framingRectSize = null;
|
|
|
+
|
|
|
+ // Fraction of the width / heigth to use as a margin. This fraction is used on each size, so
|
|
|
+ // must be smaller than 0.5;
|
|
|
+ private double marginFraction = 0.1d;
|
|
|
+
|
|
|
+ private PreviewScalingStrategy previewScalingStrategy = null;
|
|
|
+
|
|
|
+ private boolean torchOn = false;
|
|
|
+
|
|
|
+ @TargetApi(14)
|
|
|
+ private TextureView.SurfaceTextureListener surfaceTextureListener() {
|
|
|
+ // Cannot initialize automatically, since we may be API < 14
|
|
|
+ return new TextureView.SurfaceTextureListener() {
|
|
|
+ @Override
|
|
|
+ public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
|
|
|
+ onSurfaceTextureSizeChanged(surface, width, height);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
|
|
|
+ currentSurfaceSize = new Size(width, height);
|
|
|
+ startPreviewIfReady();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
|
|
|
+
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private final SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void surfaceCreated(SurfaceHolder holder) {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void surfaceDestroyed(SurfaceHolder holder) {
|
|
|
+ currentSurfaceSize = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
|
|
+ if (holder == null) {
|
|
|
+ Log.e(TAG, "*** WARNING *** surfaceChanged() gave us a null surface!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ currentSurfaceSize = new Size(width, height);
|
|
|
+ startPreviewIfReady();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ private final Handler.Callback stateCallback = new Handler.Callback() {
|
|
|
+ @Override
|
|
|
+ public boolean handleMessage(Message message) {
|
|
|
+ if (message.what == R.id.zxing_prewiew_size_ready) {
|
|
|
+ // At this point, we have the camera preview size, and should have containerSize and
|
|
|
+ // surfaceRect.
|
|
|
+ previewSized((Size) message.obj);
|
|
|
+ return true;
|
|
|
+ } else if (message.what == R.id.zxing_camera_error) {
|
|
|
+ Exception error = (Exception) message.obj;
|
|
|
+
|
|
|
+ if (isActive()) {
|
|
|
+ // This check prevents multiple errors from begin passed through.
|
|
|
+ pause();
|
|
|
+ fireState.cameraError(error);
|
|
|
+ }
|
|
|
+ } else if (message.what == R.id.zxing_camera_closed) {
|
|
|
+ fireState.cameraClosed();
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ private RotationCallback rotationCallback = new RotationCallback() {
|
|
|
+ @Override
|
|
|
+ public void onRotationChanged(int rotation) {
|
|
|
+ // Make sure this is run on the main thread.
|
|
|
+ stateHandler.postDelayed(() -> rotationChanged(), ROTATION_LISTENER_DELAY_MS);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ public CameraPreview(Context context) {
|
|
|
+ super(context);
|
|
|
+ initialize(context, null, 0, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ public CameraPreview(Context context, AttributeSet attrs) {
|
|
|
+ super(context, attrs);
|
|
|
+ initialize(context, attrs, 0, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ public CameraPreview(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
|
+ super(context, attrs, defStyleAttr);
|
|
|
+ initialize(context, attrs, defStyleAttr, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void initialize(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
|
|
+ if (getBackground() == null) {
|
|
|
+ // Default to SurfaceView colour, so that there are less changes.
|
|
|
+ setBackgroundColor(Color.BLACK);
|
|
|
+ }
|
|
|
+
|
|
|
+ initializeAttributes(attrs);
|
|
|
+
|
|
|
+ windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
|
|
+
|
|
|
+ stateHandler = new Handler(stateCallback);
|
|
|
+
|
|
|
+ rotationListener = new RotationListener();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void onAttachedToWindow() {
|
|
|
+ super.onAttachedToWindow();
|
|
|
+
|
|
|
+ setupSurfaceView();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Initialize from XML attributes.
|
|
|
+ *
|
|
|
+ * @param attrs the attributes
|
|
|
+ */
|
|
|
+ protected void initializeAttributes(AttributeSet attrs) {
|
|
|
+ TypedArray styledAttributes = getContext().obtainStyledAttributes(attrs, R.styleable.zxing_camera_preview);
|
|
|
+
|
|
|
+ int framingRectWidth = (int) styledAttributes.getDimension(R.styleable.zxing_camera_preview_zxing_framing_rect_width, -1);
|
|
|
+ int framingRectHeight = (int) styledAttributes.getDimension(R.styleable.zxing_camera_preview_zxing_framing_rect_height, -1);
|
|
|
+
|
|
|
+ if (framingRectWidth > 0 && framingRectHeight > 0) {
|
|
|
+ this.framingRectSize = new Size(framingRectWidth, framingRectHeight);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.useTextureView = styledAttributes.getBoolean(R.styleable.zxing_camera_preview_zxing_use_texture_view, true);
|
|
|
+
|
|
|
+ // See zxing_attrs.xml for the enum values
|
|
|
+ int scalingStrategyNumber = styledAttributes.getInteger(R.styleable.zxing_camera_preview_zxing_preview_scaling_strategy, -1);
|
|
|
+ if (scalingStrategyNumber == 1) {
|
|
|
+ previewScalingStrategy = new CenterCropStrategy();
|
|
|
+ } else if (scalingStrategyNumber == 2) {
|
|
|
+ previewScalingStrategy = new FitCenterStrategy();
|
|
|
+ } else if (scalingStrategyNumber == 3) {
|
|
|
+ previewScalingStrategy = new FitXYStrategy();
|
|
|
+ }
|
|
|
+
|
|
|
+ styledAttributes.recycle();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void rotationChanged() {
|
|
|
+ // Confirm that it did actually change
|
|
|
+ if (isActive() && getDisplayRotation() != openedOrientation) {
|
|
|
+ pause();
|
|
|
+ resume();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void setupSurfaceView() {
|
|
|
+ if (useTextureView) {
|
|
|
+ textureView = new TextureView(getContext());
|
|
|
+ textureView.setSurfaceTextureListener(surfaceTextureListener());
|
|
|
+ addView(textureView);
|
|
|
+ } else {
|
|
|
+ surfaceView = new SurfaceView(getContext());
|
|
|
+ surfaceView.getHolder().addCallback(surfaceCallback);
|
|
|
+ addView(surfaceView);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Add a listener to be notified of changes to the preview state, as well as camera errors.
|
|
|
+ *
|
|
|
+ * @param listener the listener
|
|
|
+ */
|
|
|
+ public void addStateListener(StateListener listener) {
|
|
|
+ stateListeners.add(listener);
|
|
|
+ }
|
|
|
+
|
|
|
+ private final StateListener fireState = new StateListener() {
|
|
|
+ @Override
|
|
|
+ public void previewSized() {
|
|
|
+ for (StateListener listener : stateListeners) {
|
|
|
+ listener.previewSized();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void previewStarted() {
|
|
|
+ for (StateListener listener : stateListeners) {
|
|
|
+ listener.previewStarted();
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void previewStopped() {
|
|
|
+ for (StateListener listener : stateListeners) {
|
|
|
+ listener.previewStopped();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void cameraError(Exception error) {
|
|
|
+ for (StateListener listener : stateListeners) {
|
|
|
+ listener.cameraError(error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void cameraClosed() {
|
|
|
+ for (StateListener listener : stateListeners) {
|
|
|
+ listener.cameraClosed();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ private void calculateFrames() {
|
|
|
+ if (containerSize == null || previewSize == null || displayConfiguration == null) {
|
|
|
+ previewFramingRect = null;
|
|
|
+ framingRect = null;
|
|
|
+ surfaceRect = null;
|
|
|
+ throw new IllegalStateException("containerSize or previewSize is not set yet");
|
|
|
+ }
|
|
|
+
|
|
|
+ int previewWidth = previewSize.width;
|
|
|
+ int previewHeight = previewSize.height;
|
|
|
+
|
|
|
+ int width = containerSize.width;
|
|
|
+ int height = containerSize.height;
|
|
|
+
|
|
|
+ Rect scaledPreview = displayConfiguration.scalePreview(previewSize);
|
|
|
+ if (scaledPreview.width() <= 0 || scaledPreview.height() <= 0) {
|
|
|
+ // Something is not ready yet - we can't start the preview.
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ surfaceRect = scaledPreview;
|
|
|
+
|
|
|
+ Rect container = new Rect(0, 0, width, height);
|
|
|
+ framingRect = calculateFramingRect(container, surfaceRect);
|
|
|
+ Rect frameInPreview = new Rect(framingRect);
|
|
|
+ frameInPreview.offset(-surfaceRect.left, -surfaceRect.top);
|
|
|
+
|
|
|
+ previewFramingRect = new Rect(frameInPreview.left * previewWidth / surfaceRect.width(),
|
|
|
+ frameInPreview.top * previewHeight / surfaceRect.height(),
|
|
|
+ frameInPreview.right * previewWidth / surfaceRect.width(),
|
|
|
+ frameInPreview.bottom * previewHeight / surfaceRect.height());
|
|
|
+
|
|
|
+ if (previewFramingRect == null || previewFramingRect.width() <= 0 || previewFramingRect.height() <= 0) {
|
|
|
+ previewFramingRect = null;
|
|
|
+ framingRect = null;
|
|
|
+ Log.w(TAG, "Preview frame is too small");
|
|
|
+ } else {
|
|
|
+ fireState.previewSized();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Call this on the main thread, while the preview is running.
|
|
|
+ *
|
|
|
+ * @param on true to turn on the torch
|
|
|
+ */
|
|
|
+ public void setTorch(boolean on) {
|
|
|
+ torchOn = on;
|
|
|
+ if (cameraInstance != null) {
|
|
|
+ cameraInstance.setTorch(on);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Changes the settings for Camera.
|
|
|
+ * Must be called after {@link #resume()}.
|
|
|
+ *
|
|
|
+ * @param callback {@link CameraParametersCallback}
|
|
|
+ */
|
|
|
+ public void changeCameraParameters(CameraParametersCallback callback) {
|
|
|
+ if (cameraInstance != null) {
|
|
|
+ cameraInstance.changeCameraParameters(callback);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void containerSized(Size containerSize) {
|
|
|
+ this.containerSize = containerSize;
|
|
|
+ if (cameraInstance != null) {
|
|
|
+ if (cameraInstance.getDisplayConfiguration() == null) {
|
|
|
+ displayConfiguration = new DisplayConfiguration(getDisplayRotation(), containerSize);
|
|
|
+ displayConfiguration.setPreviewScalingStrategy(getPreviewScalingStrategy());
|
|
|
+ cameraInstance.setDisplayConfiguration(displayConfiguration);
|
|
|
+ cameraInstance.configureCamera();
|
|
|
+ if (torchOn) {
|
|
|
+ cameraInstance.setTorch(torchOn);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Override the preview scaling strategy.
|
|
|
+ *
|
|
|
+ * @param previewScalingStrategy null for the default
|
|
|
+ */
|
|
|
+ public void setPreviewScalingStrategy(PreviewScalingStrategy previewScalingStrategy) {
|
|
|
+ this.previewScalingStrategy = previewScalingStrategy;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Override this to specify a different preview scaling strategy.
|
|
|
+ */
|
|
|
+ public PreviewScalingStrategy getPreviewScalingStrategy() {
|
|
|
+ if (previewScalingStrategy != null) {
|
|
|
+ return previewScalingStrategy;
|
|
|
+ }
|
|
|
+
|
|
|
+ // If we are using SurfaceTexture, it is safe to use centerCrop.
|
|
|
+ // For SurfaceView, it's better to use fitCenter, otherwise the preview may overlap to
|
|
|
+ // other views.
|
|
|
+ if (textureView != null) {
|
|
|
+ return new CenterCropStrategy();
|
|
|
+ } else {
|
|
|
+ return new FitCenterStrategy();
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private void previewSized(Size size) {
|
|
|
+ this.previewSize = size;
|
|
|
+ if (containerSize != null) {
|
|
|
+ calculateFrames();
|
|
|
+ requestLayout();
|
|
|
+ startPreviewIfReady();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Calculate transformation for the TextureView.
|
|
|
+ *
|
|
|
+ * An identity matrix would cause the preview to be scaled up/down to fill the TextureView.
|
|
|
+ *
|
|
|
+ * @param textureSize the size of the textureView
|
|
|
+ * @param previewSize the camera preview resolution
|
|
|
+ * @return the transform matrix for the TextureView
|
|
|
+ */
|
|
|
+ protected Matrix calculateTextureTransform(Size textureSize, Size previewSize) {
|
|
|
+ float ratioTexture = (float) textureSize.width / (float) textureSize.height;
|
|
|
+ float ratioPreview = (float) previewSize.width / (float) previewSize.height;
|
|
|
+
|
|
|
+ float scaleX;
|
|
|
+ float scaleY;
|
|
|
+
|
|
|
+ // We scale so that either width or height fits exactly in the TextureView, and the other
|
|
|
+ // is bigger (cropped).
|
|
|
+ if (ratioTexture < ratioPreview) {
|
|
|
+ scaleX = ratioPreview / ratioTexture;
|
|
|
+ scaleY = 1;
|
|
|
+ } else {
|
|
|
+ scaleX = 1;
|
|
|
+ scaleY = ratioTexture / ratioPreview;
|
|
|
+ }
|
|
|
+
|
|
|
+ Matrix matrix = new Matrix();
|
|
|
+
|
|
|
+ matrix.setScale(scaleX, scaleY);
|
|
|
+
|
|
|
+ // Center the preview
|
|
|
+ float scaledWidth = textureSize.width * scaleX;
|
|
|
+ float scaledHeight = textureSize.height * scaleY;
|
|
|
+ float dx = (textureSize.width - scaledWidth) / 2;
|
|
|
+ float dy = (textureSize.height - scaledHeight) / 2;
|
|
|
+
|
|
|
+ // Perform the translation on the scaled preview
|
|
|
+ matrix.postTranslate(dx, dy);
|
|
|
+
|
|
|
+ return matrix;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void startPreviewIfReady() {
|
|
|
+ if (currentSurfaceSize != null && previewSize != null && surfaceRect != null) {
|
|
|
+ if (surfaceView != null && currentSurfaceSize.equals(new Size(surfaceRect.width(), surfaceRect.height()))) {
|
|
|
+ startCameraPreview(new CameraSurface(surfaceView.getHolder()));
|
|
|
+ } else if (textureView != null && textureView.getSurfaceTexture() != null) {
|
|
|
+ if (previewSize != null) {
|
|
|
+ Matrix transform = calculateTextureTransform(new Size(textureView.getWidth(), textureView.getHeight()), previewSize);
|
|
|
+ textureView.setTransform(transform);
|
|
|
+ }
|
|
|
+
|
|
|
+ startCameraPreview(new CameraSurface(textureView.getSurfaceTexture()));
|
|
|
+ } else {
|
|
|
+ // Surface is not the correct size yet
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressLint("DrawAllocation")
|
|
|
+ @Override
|
|
|
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
|
|
+ containerSized(new Size(r - l, b - t));
|
|
|
+
|
|
|
+ if (surfaceView != null) {
|
|
|
+ if (surfaceRect == null) {
|
|
|
+ // Match the container, to reduce the risk of issues. The preview should never be drawn
|
|
|
+ // while the surface has this size.
|
|
|
+ surfaceView.layout(0, 0, getWidth(), getHeight());
|
|
|
+ } else {
|
|
|
+ surfaceView.layout(surfaceRect.left, surfaceRect.top, surfaceRect.right, surfaceRect.bottom);
|
|
|
+ }
|
|
|
+ } else if (textureView != null) {
|
|
|
+ textureView.layout(0, 0, getWidth(), getHeight());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The framing rectangle, relative to this view. Use to draw the rectangle.
|
|
|
+ *
|
|
|
+ * Will never be null while the preview is active.
|
|
|
+ *
|
|
|
+ * @return the framing rect, or null
|
|
|
+ * @see #isPreviewActive()
|
|
|
+ */
|
|
|
+ public Rect getFramingRect() {
|
|
|
+ return framingRect;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The framing rect, relative to the camera preview resolution.
|
|
|
+ *
|
|
|
+ * Will never be null while the preview is active.
|
|
|
+ *
|
|
|
+ * @return the preview rect, or null
|
|
|
+ * @see #isPreviewActive()
|
|
|
+ */
|
|
|
+ public Rect getPreviewFramingRect() {
|
|
|
+ return previewFramingRect;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Size getPreviewSize() {
|
|
|
+ return previewSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @return the CameraSettings currently in use
|
|
|
+ */
|
|
|
+ public CameraSettings getCameraSettings() {
|
|
|
+ return cameraSettings;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Set the CameraSettings. Use this to select a different camera, change exposure and torch
|
|
|
+ * settings, and some other options.
|
|
|
+ *
|
|
|
+ * This has no effect if the camera is already open.
|
|
|
+ *
|
|
|
+ * @param cameraSettings the new settings
|
|
|
+ */
|
|
|
+ public void setCameraSettings(CameraSettings cameraSettings) {
|
|
|
+ this.cameraSettings = cameraSettings;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Start the camera preview and decoding. Typically this should be called from the Activity's
|
|
|
+ * onResume() method.
|
|
|
+ *
|
|
|
+ * Call from UI thread only.
|
|
|
+ */
|
|
|
+ public void resume() {
|
|
|
+ // This must be safe to call multiple times
|
|
|
+ Util.validateMainThread();
|
|
|
+ Log.d(TAG, "resume()");
|
|
|
+
|
|
|
+ // initCamera() does nothing if called twice, but does log a warning
|
|
|
+ initCamera();
|
|
|
+
|
|
|
+ if (currentSurfaceSize != null) {
|
|
|
+ // The activity was paused but not stopped, so the surface still exists. Therefore
|
|
|
+ // surfaceCreated() won't be called, so init the camera here.
|
|
|
+ startPreviewIfReady();
|
|
|
+ } else if (surfaceView != null) {
|
|
|
+ // Install the callback and wait for surfaceCreated() to init the camera.
|
|
|
+ surfaceView.getHolder().addCallback(surfaceCallback);
|
|
|
+ } else if (textureView != null) {
|
|
|
+ if (textureView.isAvailable()) {
|
|
|
+ surfaceTextureListener().onSurfaceTextureAvailable(textureView.getSurfaceTexture(), textureView.getWidth(), textureView.getHeight());
|
|
|
+ } else {
|
|
|
+ textureView.setSurfaceTextureListener(surfaceTextureListener());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // To trigger surfaceSized again
|
|
|
+ requestLayout();
|
|
|
+ rotationListener.listen(getContext(), rotationCallback);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Pause scanning and the camera preview. Typically this should be called from the Activity's
|
|
|
+ * onPause() method.
|
|
|
+ *
|
|
|
+ * Call from UI thread only.
|
|
|
+ */
|
|
|
+ public void pause() {
|
|
|
+ // This must be safe to call multiple times.
|
|
|
+ Util.validateMainThread();
|
|
|
+ Log.d(TAG, "pause()");
|
|
|
+
|
|
|
+ openedOrientation = -1;
|
|
|
+ if (cameraInstance != null) {
|
|
|
+ cameraInstance.close();
|
|
|
+ cameraInstance = null;
|
|
|
+ previewActive = false;
|
|
|
+ } else {
|
|
|
+ stateHandler.sendEmptyMessage(R.id.zxing_camera_closed);
|
|
|
+ }
|
|
|
+ if (currentSurfaceSize == null && surfaceView != null) {
|
|
|
+ SurfaceHolder surfaceHolder = surfaceView.getHolder();
|
|
|
+ surfaceHolder.removeCallback(surfaceCallback);
|
|
|
+ }
|
|
|
+ if (currentSurfaceSize == null && textureView != null) {
|
|
|
+ textureView.setSurfaceTextureListener(null);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.containerSize = null;
|
|
|
+ this.previewSize = null;
|
|
|
+ this.previewFramingRect = null;
|
|
|
+ rotationListener.stop();
|
|
|
+
|
|
|
+ fireState.previewStopped();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Pause scanning and preview; waiting for the Camera to be closed.
|
|
|
+ *
|
|
|
+ * This blocks the main thread.
|
|
|
+ */
|
|
|
+ public void pauseAndWait() {
|
|
|
+ CameraInstance instance = getCameraInstance();
|
|
|
+ pause();
|
|
|
+ long startTime = System.nanoTime();
|
|
|
+ while(instance != null && !instance.isCameraClosed()) {
|
|
|
+ if (System.nanoTime() - startTime > 2000000000) {
|
|
|
+ // Don't wait for longer than 2 seconds
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ Thread.sleep(1);
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public Size getFramingRectSize() {
|
|
|
+ return framingRectSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Set an exact size for the framing rectangle. It will be centered in the view.
|
|
|
+ *
|
|
|
+ * @param framingRectSize the size
|
|
|
+ */
|
|
|
+ public void setFramingRectSize(Size framingRectSize) {
|
|
|
+ this.framingRectSize = framingRectSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ public double getMarginFraction() {
|
|
|
+ return marginFraction;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The the fraction of the width/height of view to be used as a margin for the framing rect.
|
|
|
+ * This is ignored if framingRectSize is specified.
|
|
|
+ *
|
|
|
+ * @param marginFraction the fraction
|
|
|
+ */
|
|
|
+ public void setMarginFraction(double marginFraction) {
|
|
|
+ if (marginFraction >= 0.5d) {
|
|
|
+ throw new IllegalArgumentException("The margin fraction must be less than 0.5");
|
|
|
+ }
|
|
|
+ this.marginFraction = marginFraction;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isUseTextureView() {
|
|
|
+ return useTextureView;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Set to true to use TextureView instead of SurfaceView.
|
|
|
+ *
|
|
|
+ * Will only have an effect on API >= 14.
|
|
|
+ *
|
|
|
+ * @param useTextureView true to use TextureView.
|
|
|
+ */
|
|
|
+ public void setUseTextureView(boolean useTextureView) {
|
|
|
+ this.useTextureView = useTextureView;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Considered active if between resume() and pause().
|
|
|
+ *
|
|
|
+ * @return true if active
|
|
|
+ */
|
|
|
+ protected boolean isActive() {
|
|
|
+ return cameraInstance != null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int getDisplayRotation() {
|
|
|
+ return windowManager.getDefaultDisplay().getRotation();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void initCamera() {
|
|
|
+ if (cameraInstance != null) {
|
|
|
+ Log.w(TAG, "initCamera called twice");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ cameraInstance = createCameraInstance();
|
|
|
+
|
|
|
+ cameraInstance.setReadyHandler(stateHandler);
|
|
|
+ cameraInstance.open();
|
|
|
+
|
|
|
+ // Keep track of the orientation we opened at, so that we don't reopen the camera if we
|
|
|
+ // don't need to.
|
|
|
+ openedOrientation = getDisplayRotation();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Create a new CameraInstance.
|
|
|
+ *
|
|
|
+ * Override to use a custom CameraInstance.
|
|
|
+ *
|
|
|
+ * @return a new CameraInstance
|
|
|
+ */
|
|
|
+ protected CameraInstance createCameraInstance() {
|
|
|
+ CameraInstance cameraInstance = new CameraInstance(getContext());
|
|
|
+ cameraInstance.setCameraSettings(cameraSettings);
|
|
|
+ return cameraInstance;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void startCameraPreview(CameraSurface surface) {
|
|
|
+ if (!previewActive && cameraInstance != null) {
|
|
|
+ Log.i(TAG, "Starting preview");
|
|
|
+ cameraInstance.setSurface(surface);
|
|
|
+ cameraInstance.startPreview();
|
|
|
+ previewActive = true;
|
|
|
+
|
|
|
+ previewStarted();
|
|
|
+ fireState.previewStarted();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Called when the preview is started. Override this to start decoding work.
|
|
|
+ */
|
|
|
+ protected void previewStarted() {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get the current CameraInstance. This may be null, and may change when
|
|
|
+ * pausing / resuming the preview.
|
|
|
+ *
|
|
|
+ * While the preview is active, getCameraInstance() will never be null.
|
|
|
+ *
|
|
|
+ * @return the current CameraInstance
|
|
|
+ * @see #isPreviewActive()
|
|
|
+ */
|
|
|
+ public CameraInstance getCameraInstance() {
|
|
|
+ return cameraInstance;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The preview typically starts being active a while after calling resume(), and stops
|
|
|
+ * when calling pause().
|
|
|
+ *
|
|
|
+ * @return true if the preview is active
|
|
|
+ */
|
|
|
+ public boolean isPreviewActive() {
|
|
|
+ return previewActive;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Calculate framing rectangle, relative to the preview frame.
|
|
|
+ *
|
|
|
+ * Note that the SurfaceView may be larger than the container.
|
|
|
+ *
|
|
|
+ * Override this for more control over the framing rect calculations.
|
|
|
+ *
|
|
|
+ * @param container this container, with left = top = 0
|
|
|
+ * @param surface the SurfaceView, relative to this container
|
|
|
+ * @return the framing rect, relative to this container
|
|
|
+ */
|
|
|
+ protected Rect calculateFramingRect(Rect container, Rect surface) {
|
|
|
+ // intersection is the part of the container that is used for the preview
|
|
|
+ Rect intersection = new Rect(container);
|
|
|
+ boolean intersects = intersection.intersect(surface);
|
|
|
+
|
|
|
+ if (framingRectSize != null) {
|
|
|
+ // Specific size is specified. Make sure it's not larger than the container or surface.
|
|
|
+ int horizontalMargin = Math.max(0, (intersection.width() - framingRectSize.width) / 2);
|
|
|
+ int verticalMargin = Math.max(0, (intersection.height() - framingRectSize.height) / 2);
|
|
|
+ intersection.inset(horizontalMargin, verticalMargin);
|
|
|
+ return intersection;
|
|
|
+ }
|
|
|
+ // margin as 10% (default) of the smaller of width, height
|
|
|
+ int margin = (int)Math.min(intersection.width() * marginFraction, intersection.height() * marginFraction);
|
|
|
+ intersection.inset(margin, margin);
|
|
|
+ if (intersection.height() > intersection.width()) {
|
|
|
+ // We don't want a frame that is taller than wide.
|
|
|
+ intersection.inset(0, (intersection.height() - intersection.width()) / 2);
|
|
|
+ }
|
|
|
+ return intersection;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected Parcelable onSaveInstanceState() {
|
|
|
+ Parcelable superState = super.onSaveInstanceState();
|
|
|
+
|
|
|
+ Bundle myState = new Bundle();
|
|
|
+ myState.putParcelable("super", superState);
|
|
|
+ myState.putBoolean("torch", torchOn);
|
|
|
+ return myState;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void onRestoreInstanceState(Parcelable state) {
|
|
|
+ if (!(state instanceof Bundle)) {
|
|
|
+ super.onRestoreInstanceState(state);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ Bundle myState = (Bundle)state;
|
|
|
+ Parcelable superState = myState.getParcelable("super");
|
|
|
+ super.onRestoreInstanceState(superState);
|
|
|
+ boolean torch = myState.getBoolean("torch");
|
|
|
+ setTorch(torch);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ *
|
|
|
+ * @return true if the camera has been closed in a background thread.
|
|
|
+ */
|
|
|
+ public boolean isCameraClosed() {
|
|
|
+ return cameraInstance == null || cameraInstance.isCameraClosed();
|
|
|
+ }
|
|
|
+}
|