/* * Copyright (C) 2014 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.example.android.hdrviewfinder; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; import android.view.Surface; import java.util.List; /** * Simple interface for operating the camera, with major camera operations * all performed on a background handler thread. */ public class CameraOps { private static final String TAG = "CameraOps"; public static final long CAMERA_CLOSE_TIMEOUT = 2000; // ms private final CameraManager mCameraManager; private CameraDevice mCameraDevice; private CameraCaptureSession mCameraSession; private List mSurfaces; private final ConditionVariable mCloseWaiter = new ConditionVariable(); private HandlerThread mCameraThread; private Handler mCameraHandler; private final ErrorDisplayer mErrorDisplayer; private final CameraReadyListener mReadyListener; private final Handler mReadyHandler; /** * Create a new camera ops thread. * * @param errorDisplayer listener for displaying error messages * @param readyListener listener for notifying when camera is ready for requests * @param readyHandler the handler for calling readyListener methods on */ CameraOps(CameraManager manager, ErrorDisplayer errorDisplayer, CameraReadyListener readyListener, Handler readyHandler) { mCameraThread = new HandlerThread("CameraOpsThread"); mCameraThread.start(); if (manager == null || errorDisplayer == null || readyListener == null || readyHandler == null) { throw new IllegalArgumentException("Need valid displayer, listener, handler"); } mCameraManager = manager; mErrorDisplayer = errorDisplayer; mReadyListener = readyListener; mReadyHandler = readyHandler; } /** * Open the first back-facing camera listed by the camera manager. * Displays a dialog if it cannot open a camera. */ public void openCamera(final String cameraId) { mCameraHandler = new Handler(mCameraThread.getLooper()); mCameraHandler.post(new Runnable() { public void run() { if (mCameraDevice != null) { throw new IllegalStateException("Camera already open"); } try { mCameraManager.openCamera(cameraId, mCameraDeviceListener, mCameraHandler); } catch (CameraAccessException e) { String errorMessage = mErrorDisplayer.getErrorString(e); mErrorDisplayer.showErrorDialog(errorMessage); } } }); } /** * Close the camera and wait for the close callback to be called in the camera thread. * Times out after @{value CAMERA_CLOSE_TIMEOUT} ms. */ public void closeCameraAndWait() { mCloseWaiter.close(); mCameraHandler.post(mCloseCameraRunnable); boolean closed = mCloseWaiter.block(CAMERA_CLOSE_TIMEOUT); if (!closed) { Log.e(TAG, "Timeout closing camera"); } } private Runnable mCloseCameraRunnable = new Runnable() { public void run() { if (mCameraDevice != null) { mCameraDevice.close(); } mCameraDevice = null; mCameraSession = null; mSurfaces = null; } }; /** * Set the output Surfaces, and finish configuration if otherwise ready. */ public void setSurfaces(final List surfaces) { mCameraHandler.post(new Runnable() { public void run() { mSurfaces = surfaces; startCameraSession(); } }); } /** * Get a request builder for the current camera. */ public CaptureRequest.Builder createCaptureRequest(int template) throws CameraAccessException { CameraDevice device = mCameraDevice; if (device == null) { throw new IllegalStateException("Can't get requests when no camera is open"); } return device.createCaptureRequest(template); } /** * Set a repeating request. */ public void setRepeatingRequest(final CaptureRequest request, final CameraCaptureSession.CaptureCallback listener, final Handler handler) { mCameraHandler.post(new Runnable() { public void run() { try { mCameraSession.setRepeatingRequest(request, listener, handler); } catch (CameraAccessException e) { String errorMessage = mErrorDisplayer.getErrorString(e); mErrorDisplayer.showErrorDialog(errorMessage); } } }); } /** * Set a repeating request. */ public void setRepeatingBurst(final List requests, final CameraCaptureSession.CaptureCallback listener, final Handler handler) { mCameraHandler.post(new Runnable() { public void run() { try { mCameraSession.setRepeatingBurst(requests, listener, handler); } catch (CameraAccessException e) { String errorMessage = mErrorDisplayer.getErrorString(e); mErrorDisplayer.showErrorDialog(errorMessage); } } }); } /** * Configure the camera session. */ private void startCameraSession() { // Wait until both the camera device is open and the SurfaceView is ready if (mCameraDevice == null || mSurfaces == null) return; try { mCameraDevice.createCaptureSession( mSurfaces, mCameraSessionListener, mCameraHandler); } catch (CameraAccessException e) { String errorMessage = mErrorDisplayer.getErrorString(e); mErrorDisplayer.showErrorDialog(errorMessage); mCameraDevice.close(); mCameraDevice = null; } } /** * Main listener for camera session events * Invoked on mCameraThread */ private CameraCaptureSession.StateCallback mCameraSessionListener = new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { mCameraSession = session; mReadyHandler.post(new Runnable() { public void run() { // This can happen when the screen is turned off and turned back on. if (null == mCameraDevice) { return; } mReadyListener.onCameraReady(); } }); } @Override public void onConfigureFailed(CameraCaptureSession session) { mErrorDisplayer.showErrorDialog("Unable to configure the capture session"); mCameraDevice.close(); mCameraDevice = null; } }; /** * Main listener for camera device events. * Invoked on mCameraThread */ private CameraDevice.StateCallback mCameraDeviceListener = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { mCameraDevice = camera; startCameraSession(); } @Override public void onClosed(CameraDevice camera) { mCloseWaiter.open(); } @Override public void onDisconnected(CameraDevice camera) { mErrorDisplayer.showErrorDialog("The camera device has been disconnected."); camera.close(); mCameraDevice = null; } @Override public void onError(CameraDevice camera, int error) { mErrorDisplayer.showErrorDialog("The camera encountered an error:" + error); camera.close(); mCameraDevice = null; } }; /** * Simple listener for main code to know the camera is ready for requests, or failed to * start. */ public interface CameraReadyListener { public void onCameraReady(); } /** * Simple listener for displaying error messages */ public interface ErrorDisplayer { public void showErrorDialog(String errorMessage); public String getErrorString(CameraAccessException e); } }