/* * Copyright (C) The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.ekreutz.barcodescanner.camera; import android.Manifest; import android.content.Context; import android.content.res.Configuration; import android.support.annotation.RequiresPermission; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.ViewGroup; import com.google.android.gms.common.images.Size; import com.google.android.gms.vision.Detector; import java.io.IOException; public class CameraSourcePreview extends ViewGroup { private static final String TAG = "CameraSourcePreview"; public static final int FILL_MODE_COVER = 0; public static final int FILL_MODE_FIT = 1; private Context mContext; private SurfaceView mSurfaceView; private boolean mStartRequested; private boolean mSurfaceAvailable; private CameraSource mCameraSource; private int mWidth = 0, mHeight = 0; private int fillMode = FILL_MODE_COVER; public CameraSourcePreview(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; mStartRequested = false; mSurfaceAvailable = false; mSurfaceView = new SurfaceView(context); mSurfaceView.getHolder().addCallback(new SurfaceCallback()); addView(mSurfaceView); } @RequiresPermission(Manifest.permission.CAMERA) public void start(CameraSource cameraSource) throws IOException, SecurityException { if (cameraSource == null) { stop(); } mCameraSource = cameraSource; if (mCameraSource != null) { mStartRequested = true; startIfReady(); } } public void stop() { if (mCameraSource != null) { mCameraSource.stop(); } } public void release() { if (mCameraSource != null) { mCameraSource.release(); mCameraSource = null; } } @RequiresPermission(Manifest.permission.CAMERA) private void startIfReady() throws IOException, SecurityException { if (mStartRequested && mSurfaceAvailable && mCameraSource != null) { mCameraSource.start(mSurfaceView.getHolder()); mStartRequested = false; } } // Can be quite heavy, since it stops and restarts the camera @RequiresPermission(Manifest.permission.CAMERA) public void replaceBarcodeDetector(Detector<?> detector, boolean shouldResume) throws IOException, SecurityException { if (mCameraSource != null) { mCameraSource.release(); mCameraSource.setDetector(detector); if (shouldResume && mSurfaceAvailable) { start(mCameraSource); } } } // Set the camera stream fill mode public void setFillMode(int fillMode) { if (fillMode != FILL_MODE_COVER && fillMode != FILL_MODE_FIT) return; this.fillMode = fillMode; previewLayout(); } private class SurfaceCallback implements SurfaceHolder.Callback { @Override public void surfaceCreated(SurfaceHolder surface) { mSurfaceAvailable = true; try { startIfReady(); } catch (SecurityException se) { Log.e(TAG,"Do not have permission to start the camera", se); } catch (IOException e) { Log.e(TAG, "Could not start camera source.", e); } } @Override public void surfaceDestroyed(SurfaceHolder surface) { mSurfaceAvailable = false; } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { previewLayout(); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { mWidth = r - l; mHeight = b - t; previewLayout(); } /* layout the surface that we draw the camera stream on */ private void previewLayout() { if (mWidth == 0 || mHeight == 0) return; // Step 1: determine the size of the camera stream // -------------------------------- int previewWidth = 800; int previewHeight = 480; if (mCameraSource != null) { mCameraSource.setRotation(); Size size = mCameraSource.getPreviewSize(); if (size != null) { previewWidth = size.getWidth(); previewHeight = size.getHeight(); } } // Swap width and height sizes when in portrait, since it will be rotated 90 degrees if (isPortraitMode()) { int tmp = previewWidth; previewWidth = previewHeight; previewHeight = tmp; } // Step 2. Determine how to scale the stream so that it fits snugly in this view // -------------------------------- double scaleRatio = Math.min(mWidth / (double) previewWidth, mHeight / (double) previewHeight); int childLeft = (int) Math.round((mWidth - scaleRatio * previewWidth) / 2) + 1; int childRight = (int) Math.round((mWidth + scaleRatio * previewWidth) / 2) - 1; int childTop = (int) Math.round((mHeight - scaleRatio * previewHeight) / 2) + 1; int childBottom = (int) Math.round((mHeight + scaleRatio * previewHeight) / 2) - 1; // apply the layout to the surface for (int i = 0; i < getChildCount(); ++i) { getChildAt(i).layout(childLeft, childTop, childRight, childBottom); } // Step 3: Either fill this view, or barely touch the edges // -------------------------------- float r = 1.0f; if (fillMode == FILL_MODE_COVER) { r = Math.max((float) mWidth / (childRight - childLeft), (float) mHeight / (childBottom - childTop)); } else if (fillMode == FILL_MODE_FIT) { r = Math.min((float) mWidth / (childRight - childLeft), (float) mHeight / (childBottom - childTop)); } setScaleX(r); setScaleY(r); // Step 4: try starting the stream again (if needed) after our modifications // -------------------------------- try { startIfReady(); } catch (SecurityException se) { Log.e(TAG,"Do not have permission to start the camera", se); } catch (IOException e) { Log.e(TAG, "Could not start camera source.", e); } } private boolean isPortraitMode() { int orientation = mContext.getResources().getConfiguration().orientation; return orientation == Configuration.ORIENTATION_PORTRAIT; } }