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