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