/* Copyright 2012 Nik Cain nik@showmehills.com This file is part of ShowMeHills. ShowMeHills is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ShowMeHills is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ShowMeHills. If not, see <http://www.gnu.org/licenses/>. */ package com.showmehills; import android.content.Context; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.StreamConfigurationMap; import android.os.Handler; import android.util.AttributeSet; import android.util.Log; import android.util.Size; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.support.annotation.NonNull; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; public class CameraPreviewSurface extends SurfaceView { Context ctx; private String mCameraId; CameraManager mCameraManager; private boolean mGotSecondCallback; Handler mBackgroundHandler; CameraDevice mCamera; static final String TAG = "showmehills"; SurfaceHolder previewHolder; ShowMeHillsActivity smh; SurfaceView mSurfaceView; CameraCaptureSession mCaptureSession; Size previewSize; boolean paused = false; static class CompareSizesByArea implements Comparator<Size> { @Override public int compare(Size lhs, Size rhs) { // We cast here to ensure the multiplications won't overflow return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight()); } } static Size chooseBigEnoughSize(Size[] choices, int width, int height) { // Collect the supported resolutions that are at least as big as the preview Surface List<Size> bigEnough = new ArrayList<>(); for (Size option : choices) { if (option.getWidth() >= width && option.getHeight() >= height) { bigEnough.add(option); Log.d(TAG, "size " + option); } } // Pick the smallest of those, assuming we found any if (bigEnough.size() > 0) { return Collections.min(bigEnough, new CompareSizesByArea()); } else { Log.e(TAG, "Couldn't find any suitable preview size"); return choices[0]; } } final CameraCaptureSession.StateCallback mCaptureSessionListener = new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { Log.i(TAG, "Finished configuring camera outputs"); mCaptureSession = session; SurfaceHolder holder = mSurfaceView.getHolder(); if (holder != null) { try { // Build a request for preview footage CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); requestBuilder.addTarget(holder.getSurface()); CaptureRequest previewRequest = requestBuilder.build(); // Start displaying preview images try { session.setRepeatingRequest(previewRequest, /*listener*/null, /*handler*/null); } catch (CameraAccessException ex) { Log.e(TAG, "Failed to make repeating preview request", ex); } } catch (CameraAccessException ex) { Log.e(TAG, "Failed to build preview request", ex); } } else { Log.e(TAG, "Holder didn't exist when trying to formulate preview request"); } } @Override public void onClosed(@NonNull CameraCaptureSession session) { mCaptureSession = null; } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { Log.e(TAG, "Configuration error on device '" + mCamera.getId()); } }; final CameraDevice.StateCallback mCameraStateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice camera) { Log.i(TAG, "Successfully opened camera"); mCamera = camera; try { List<Surface> outputs = Arrays.asList( mSurfaceView.getHolder().getSurface());//, mCaptureBuffer.getSurface()); camera.createCaptureSession(outputs, mCaptureSessionListener, mBackgroundHandler); } catch (CameraAccessException ex) { Log.e(TAG, "Failed to create a capture session", ex); } catch (SecurityException se) { Log.e(TAG, "security exception", se); } // Control flow continues in mCaptureSessionListener.onConfigured() } @Override public void onDisconnected(@NonNull CameraDevice camera) { Log.e(TAG, "Camera was disconnected"); } @Override public void onError(@NonNull CameraDevice camera, int error) { Log.e(TAG, "State error on device '" + camera.getId() + "': code " + error); } }; SurfaceHolder.Callback surfaceHolderListener = new SurfaceHolder.Callback() { public void surfaceCreated(SurfaceHolder holder) { mCameraId = null; mGotSecondCallback = false; } public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { Log.d(TAG, "surface changed"); if (paused) { Log.d(TAG, "paused, do nothing"); return; } if (mCameraId == null) { Log.d(TAG, "null camera, setting new one up"); // Find the device's back-facing camera and set the destination buffer sizes try { mGotSecondCallback = false; mCameraManager = (CameraManager) ctx.getSystemService(Context.CAMERA_SERVICE); for (String cameraId : mCameraManager.getCameraIdList()) { CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(cameraId); if (cameraCharacteristics.get(cameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK) { Log.i(TAG, "Found a back-facing camera"); StreamConfigurationMap info = cameraCharacteristics .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); Log.i(TAG, "SurfaceView size: " + getWidth() + 'x' + getHeight()); if (previewSize == null) { previewSize = new Size(getWidth(), getHeight()); } Size optimalSize = chooseBigEnoughSize( info.getOutputSizes(SurfaceHolder.class), previewSize.getWidth(), previewSize.getHeight()); // Set the SurfaceHolder to use the camera's largest supported size Log.i(TAG, "Preview size: " + optimalSize); SurfaceHolder surfaceHolder = getHolder(); surfaceHolder.setFixedSize(optimalSize.getWidth(), optimalSize.getHeight()); mCameraId = cameraId; } } } catch (CameraAccessException ex) { Log.e(TAG, "Unable to list cameras", ex); } catch (SecurityException sx) { Log.e(TAG, "permission failure", sx); } Log.e(TAG, "Didn't find any back-facing cameras"); // This is the second time the method is being invoked: our size change is complete } else { if (!mGotSecondCallback) { Log.d(TAG, "2nd callback"); if (mCamera != null) { Log.e(TAG, "Aborting camera open because it hadn't been closed"); return; } // Open the camera device try { Log.d(TAG, "opening camera"); mCameraManager.openCamera(mCameraId, mCameraStateCallback, mBackgroundHandler); } catch (CameraAccessException ex) { Log.e(TAG, "Failed to configure output surface", ex); } catch (SecurityException sx) { Log.e(TAG, "permission failure", sx); } mGotSecondCallback = true; // Control flow continues in mCameraStateCallback.onOpened() } } smh.scrheight = h; smh.scrwidth = w; } public void surfaceDestroyed(SurfaceHolder arg0) { } }; public void onResume() { paused = false; } public void onPause() { Log.d(TAG, "pausing surface"); paused = true; SurfaceHolder surfaceHolder = getHolder(); surfaceHolder.setFixedSize(10,10); if (mCaptureSession != null) { mCaptureSession.close(); mCaptureSession = null; } if (mCamera != null) { mCamera.close(); Log.d(TAG, "closed camera"); } mCamera = null; mCameraId = null; mGotSecondCallback = false; } public CameraPreviewSurface(Context ct) { super(ct); ctx = ct; mSurfaceView = this; } public void init(ShowMeHillsActivity myapp) { smh = myapp; previewHolder = this.getHolder(); previewHolder.addCallback(surfaceHolderListener); } public CameraPreviewSurface(Context ct, ShowMeHillsActivity myapp) { super(ct); ctx = ct; mSurfaceView = this; init(myapp); } public CameraPreviewSurface(Context context, AttributeSet attrs) { super(context, attrs); ctx = context; mSurfaceView = this; } public CameraPreviewSurface(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); ctx = context; mSurfaceView = this; } }