1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.hdrviewfinder; 18 19 import android.hardware.camera2.CameraAccessException; 20 import android.hardware.camera2.CameraCaptureSession; 21 import android.hardware.camera2.CameraDevice; 22 import android.hardware.camera2.CameraManager; 23 import android.hardware.camera2.CaptureRequest; 24 import android.os.ConditionVariable; 25 import android.os.Handler; 26 import android.os.HandlerThread; 27 import android.util.Log; 28 import android.view.Surface; 29 30 import java.util.List; 31 32 /** 33 * Simple interface for operating the camera, with major camera operations 34 * all performed on a background handler thread. 35 */ 36 public class CameraOps { 37 38 private static final String TAG = "CameraOps"; 39 40 public static final long CAMERA_CLOSE_TIMEOUT = 2000; // ms 41 42 private final CameraManager mCameraManager; 43 private CameraDevice mCameraDevice; 44 private CameraCaptureSession mCameraSession; 45 private List<Surface> mSurfaces; 46 47 private final ConditionVariable mCloseWaiter = new ConditionVariable(); 48 49 private HandlerThread mCameraThread; 50 private Handler mCameraHandler; 51 52 private final ErrorDisplayer mErrorDisplayer; 53 54 private final CameraReadyListener mReadyListener; 55 private final Handler mReadyHandler; 56 57 /** 58 * Create a new camera ops thread. 59 * 60 * @param errorDisplayer listener for displaying error messages 61 * @param readyListener listener for notifying when camera is ready for requests 62 * @param readyHandler the handler for calling readyListener methods on 63 */ CameraOps(CameraManager manager, ErrorDisplayer errorDisplayer, CameraReadyListener readyListener, Handler readyHandler)64 CameraOps(CameraManager manager, ErrorDisplayer errorDisplayer, 65 CameraReadyListener readyListener, Handler readyHandler) { 66 mCameraThread = new HandlerThread("CameraOpsThread"); 67 mCameraThread.start(); 68 69 if (manager == null || errorDisplayer == null || 70 readyListener == null || readyHandler == null) { 71 throw new IllegalArgumentException("Need valid displayer, listener, handler"); 72 } 73 74 mCameraManager = manager; 75 mErrorDisplayer = errorDisplayer; 76 mReadyListener = readyListener; 77 mReadyHandler = readyHandler; 78 } 79 80 /** 81 * Open the first back-facing camera listed by the camera manager. 82 * Displays a dialog if it cannot open a camera. 83 */ openCamera(final String cameraId)84 public void openCamera(final String cameraId) { 85 mCameraHandler = new Handler(mCameraThread.getLooper()); 86 87 mCameraHandler.post(new Runnable() { 88 public void run() { 89 if (mCameraDevice != null) { 90 throw new IllegalStateException("Camera already open"); 91 } 92 try { 93 mCameraManager.openCamera(cameraId, mCameraDeviceListener, mCameraHandler); 94 } catch (CameraAccessException e) { 95 String errorMessage = mErrorDisplayer.getErrorString(e); 96 mErrorDisplayer.showErrorDialog(errorMessage); 97 } 98 } 99 }); 100 } 101 102 /** 103 * Close the camera and wait for the close callback to be called in the camera thread. 104 * Times out after @{value CAMERA_CLOSE_TIMEOUT} ms. 105 */ closeCameraAndWait()106 public void closeCameraAndWait() { 107 mCloseWaiter.close(); 108 mCameraHandler.post(mCloseCameraRunnable); 109 boolean closed = mCloseWaiter.block(CAMERA_CLOSE_TIMEOUT); 110 if (!closed) { 111 Log.e(TAG, "Timeout closing camera"); 112 } 113 } 114 115 private Runnable mCloseCameraRunnable = new Runnable() { 116 public void run() { 117 if (mCameraDevice != null) { 118 mCameraDevice.close(); 119 } 120 mCameraDevice = null; 121 mCameraSession = null; 122 mSurfaces = null; 123 } 124 }; 125 126 /** 127 * Set the output Surfaces, and finish configuration if otherwise ready. 128 */ setSurfaces(final List<Surface> surfaces)129 public void setSurfaces(final List<Surface> surfaces) { 130 mCameraHandler.post(new Runnable() { 131 public void run() { 132 mSurfaces = surfaces; 133 startCameraSession(); 134 } 135 }); 136 } 137 138 /** 139 * Get a request builder for the current camera. 140 */ createCaptureRequest(int template)141 public CaptureRequest.Builder createCaptureRequest(int template) throws CameraAccessException { 142 CameraDevice device = mCameraDevice; 143 if (device == null) { 144 throw new IllegalStateException("Can't get requests when no camera is open"); 145 } 146 return device.createCaptureRequest(template); 147 } 148 149 /** 150 * Set a repeating request. 151 */ setRepeatingRequest(final CaptureRequest request, final CameraCaptureSession.CaptureCallback listener, final Handler handler)152 public void setRepeatingRequest(final CaptureRequest request, 153 final CameraCaptureSession.CaptureCallback listener, 154 final Handler handler) { 155 mCameraHandler.post(new Runnable() { 156 public void run() { 157 try { 158 mCameraSession.setRepeatingRequest(request, listener, handler); 159 } catch (CameraAccessException e) { 160 String errorMessage = mErrorDisplayer.getErrorString(e); 161 mErrorDisplayer.showErrorDialog(errorMessage); 162 } 163 } 164 }); 165 } 166 167 /** 168 * Set a repeating request. 169 */ setRepeatingBurst(final List<CaptureRequest> requests, final CameraCaptureSession.CaptureCallback listener, final Handler handler)170 public void setRepeatingBurst(final List<CaptureRequest> requests, 171 final CameraCaptureSession.CaptureCallback listener, 172 final Handler handler) { 173 mCameraHandler.post(new Runnable() { 174 public void run() { 175 try { 176 mCameraSession.setRepeatingBurst(requests, listener, handler); 177 } catch (CameraAccessException e) { 178 String errorMessage = mErrorDisplayer.getErrorString(e); 179 mErrorDisplayer.showErrorDialog(errorMessage); 180 } 181 } 182 }); 183 } 184 185 /** 186 * Configure the camera session. 187 */ startCameraSession()188 private void startCameraSession() { 189 // Wait until both the camera device is open and the SurfaceView is ready 190 if (mCameraDevice == null || mSurfaces == null) return; 191 192 try { 193 mCameraDevice.createCaptureSession( 194 mSurfaces, mCameraSessionListener, mCameraHandler); 195 } catch (CameraAccessException e) { 196 String errorMessage = mErrorDisplayer.getErrorString(e); 197 mErrorDisplayer.showErrorDialog(errorMessage); 198 mCameraDevice.close(); 199 mCameraDevice = null; 200 } 201 } 202 203 /** 204 * Main listener for camera session events 205 * Invoked on mCameraThread 206 */ 207 private CameraCaptureSession.StateCallback mCameraSessionListener = 208 new CameraCaptureSession.StateCallback() { 209 210 @Override 211 public void onConfigured(CameraCaptureSession session) { 212 mCameraSession = session; 213 mReadyHandler.post(new Runnable() { 214 public void run() { 215 // This can happen when the screen is turned off and turned back on. 216 if (null == mCameraDevice) { 217 return; 218 } 219 220 mReadyListener.onCameraReady(); 221 } 222 }); 223 224 } 225 226 @Override 227 public void onConfigureFailed(CameraCaptureSession session) { 228 mErrorDisplayer.showErrorDialog("Unable to configure the capture session"); 229 mCameraDevice.close(); 230 mCameraDevice = null; 231 } 232 }; 233 234 /** 235 * Main listener for camera device events. 236 * Invoked on mCameraThread 237 */ 238 private CameraDevice.StateCallback mCameraDeviceListener = new CameraDevice.StateCallback() { 239 240 @Override 241 public void onOpened(CameraDevice camera) { 242 mCameraDevice = camera; 243 startCameraSession(); 244 } 245 246 @Override 247 public void onClosed(CameraDevice camera) { 248 mCloseWaiter.open(); 249 } 250 251 @Override 252 public void onDisconnected(CameraDevice camera) { 253 mErrorDisplayer.showErrorDialog("The camera device has been disconnected."); 254 camera.close(); 255 mCameraDevice = null; 256 } 257 258 @Override 259 public void onError(CameraDevice camera, int error) { 260 mErrorDisplayer.showErrorDialog("The camera encountered an error:" + error); 261 camera.close(); 262 mCameraDevice = null; 263 } 264 265 }; 266 267 /** 268 * Simple listener for main code to know the camera is ready for requests, or failed to 269 * start. 270 */ 271 public interface CameraReadyListener { onCameraReady()272 public void onCameraReady(); 273 } 274 275 /** 276 * Simple listener for displaying error messages 277 */ 278 public interface ErrorDisplayer { showErrorDialog(String errorMessage)279 public void showErrorDialog(String errorMessage); 280 getErrorString(CameraAccessException e)281 public String getErrorString(CameraAccessException e); 282 } 283 284 } 285