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