1b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher/*
2b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher * Copyright (C) 2014 The Android Open Source Project
3b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher *
4b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher * Licensed under the Apache License, Version 2.0 (the "License");
5b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher * you may not use this file except in compliance with the License.
6b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher * You may obtain a copy of the License at
7b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher *
8b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher *      http://www.apache.org/licenses/LICENSE-2.0
9b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher *
10b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher * Unless required by applicable law or agreed to in writing, software
11b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher * distributed under the License is distributed on an "AS IS" BASIS,
12b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher * See the License for the specific language governing permissions and
14b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher * limitations under the License.
15b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher */
16b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
17b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherpackage com.example.android.camera2.cameratoo;
18b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
19b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.app.Activity;
20b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.graphics.ImageFormat;
21b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.hardware.camera2.CameraAccessException;
22b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.hardware.camera2.CameraCharacteristics;
23b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.hardware.camera2.CameraCaptureSession;
24b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.hardware.camera2.CameraDevice;
25b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.hardware.camera2.CameraManager;
26b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.hardware.camera2.CaptureFailure;
27b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.hardware.camera2.CaptureRequest;
28b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.hardware.camera2.TotalCaptureResult;
29b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.hardware.camera2.params.StreamConfigurationMap;
30b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.media.Image;
31b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.media.ImageReader;
32b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.os.Bundle;
33b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.os.Environment;
34b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.os.Handler;
35b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.os.HandlerThread;
36b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.os.Looper;
37b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.util.Size;
38b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.util.Log;
39b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.view.Surface;
40b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.view.SurfaceHolder;
41b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.view.SurfaceView;
42b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport android.view.View;
43b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
44b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport java.io.File;
45b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport java.io.FileNotFoundException;
46b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport java.io.FileOutputStream;
47b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport java.io.IOException;
48b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport java.nio.ByteBuffer;
49b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport java.util.ArrayList;
50b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport java.util.Arrays;
51b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport java.util.Collections;
52b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport java.util.Comparator;
53b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherimport java.util.List;
54b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
55b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher/**
56b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher * A basic demonstration of how to write a point-and-shoot camera app against the new
57b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher * android.hardware.camera2 API.
58b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher */
59b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucherpublic class CameraTooActivity extends Activity {
60b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /** Output files will be saved as /sdcard/Pictures/cameratoo*.jpg */
61b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    static final String CAPTURE_FILENAME_PREFIX = "cameratoo";
62b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /** Tag to distinguish log prints. */
63b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    static final String TAG = "CameraToo";
64b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
65b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /** An additional thread for running tasks that shouldn't block the UI. */
66b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    HandlerThread mBackgroundThread;
67b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /** Handler for running tasks in the background. */
68b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    Handler mBackgroundHandler;
69b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /** Handler for running tasks on the UI thread. */
70b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    Handler mForegroundHandler;
71b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /** View for displaying the camera preview. */
72b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    SurfaceView mSurfaceView;
73b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /** Used to retrieve the captured image when the user takes a snapshot. */
74b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    ImageReader mCaptureBuffer;
75b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /** Handle to the Android camera services. */
76b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    CameraManager mCameraManager;
77b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /** The specific camera device that we're using. */
78b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    CameraDevice mCamera;
79b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /** Our image capture session. */
80b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    CameraCaptureSession mCaptureSession;
81b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
82b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /**
83b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose
84b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * width and height are at least as large as the respective requested values.
85b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * @param choices The list of sizes that the camera supports for the intended output class
86b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * @param width The minimum desired width
87b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * @param height The minimum desired height
88b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * @return The optimal {@code Size}, or an arbitrary one if none were big enough
89b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     */
90b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    static Size chooseBigEnoughSize(Size[] choices, int width, int height) {
91b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        // Collect the supported resolutions that are at least as big as the preview Surface
92b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        List<Size> bigEnough = new ArrayList<Size>();
93b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        for (Size option : choices) {
94b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            if (option.getWidth() >= width && option.getHeight() >= height) {
95b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                bigEnough.add(option);
96b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            }
97b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        }
98b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
99b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        // Pick the smallest of those, assuming we found any
100b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        if (bigEnough.size() > 0) {
101b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            return Collections.min(bigEnough, new CompareSizesByArea());
102b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        } else {
103b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            Log.e(TAG, "Couldn't find any suitable preview size");
104b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            return choices[0];
105b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        }
106b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    }
107b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
108b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /**
109b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * Compares two {@code Size}s based on their areas.
110b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     */
111b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    static class CompareSizesByArea implements Comparator<Size> {
112b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        @Override
113b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        public int compare(Size lhs, Size rhs) {
114b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            // We cast here to ensure the multiplications won't overflow
115b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
116b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    (long) rhs.getWidth() * rhs.getHeight());
117b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        }
118b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    }
119b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
120b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /**
121b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * Called when our {@code Activity} gains focus. <p>Starts initializing the camera.</p>
122b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     */
123b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    @Override
124b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    protected void onResume() {
125b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        super.onResume();
126b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
127b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        // Start a background thread to manage camera requests
128b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        mBackgroundThread = new HandlerThread("background");
129b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        mBackgroundThread.start();
130b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
131b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        mForegroundHandler = new Handler(getMainLooper());
132b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
133b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
134b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
135b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        // Inflate the SurfaceView, set it as the main layout, and attach a listener
136b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        View layout = getLayoutInflater().inflate(R.layout.mainactivity, null);
137b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        mSurfaceView = (SurfaceView) layout.findViewById(R.id.mainSurfaceView);
138b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
139b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        setContentView(mSurfaceView);
140b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
141b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        // Control flow continues in mSurfaceHolderCallback.surfaceChanged()
142b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    }
143b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
144b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /**
145b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * Called when our {@code Activity} loses focus. <p>Tears everything back down.</p>
146b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     */
147b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    @Override
148b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    protected void onPause() {
149b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        super.onPause();
150b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
151b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        try {
152b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            // Ensure SurfaceHolderCallback#surfaceChanged() will run again if the user returns
153b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            mSurfaceView.getHolder().setFixedSize(/*width*/0, /*height*/0);
154b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
155b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            // Cancel any stale preview jobs
156b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            if (mCaptureSession != null) {
157b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                mCaptureSession.close();
158b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                mCaptureSession = null;
159b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            }
160b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        } finally {
161b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            if (mCamera != null) {
162b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                mCamera.close();
163b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                mCamera = null;
164b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            }
165b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        }
166b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
167b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        // Finish processing posted messages, then join on the handling thread
168b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        mBackgroundThread.quitSafely();
169b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        try {
170b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            mBackgroundThread.join();
171b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        } catch (InterruptedException ex) {
172b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            Log.e(TAG, "Background worker thread was interrupted while joined", ex);
173b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        }
174b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
175b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        // Close the ImageReader now that the background thread has stopped
176b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        if (mCaptureBuffer != null) mCaptureBuffer.close();
177b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    }
178b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
179b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /**
180b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * Called when the user clicks on our {@code SurfaceView}, which has ID {@code mainSurfaceView}
181b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * as defined in the {@code mainactivity.xml} layout file. <p>Captures a full-resolution image
182b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * and saves it to permanent storage.</p>
183b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     */
184b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    public void onClickOnSurfaceView(View v) {
185b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        if (mCaptureSession != null) {
186b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            try {
187b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                CaptureRequest.Builder requester =
188b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                        mCamera.createCaptureRequest(mCamera.TEMPLATE_STILL_CAPTURE);
189b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                requester.addTarget(mCaptureBuffer.getSurface());
190b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                try {
191b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    // This handler can be null because we aren't actually attaching any callback
192b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    mCaptureSession.capture(requester.build(), /*listener*/null, /*handler*/null);
193b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                } catch (CameraAccessException ex) {
194b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    Log.e(TAG, "Failed to file actual capture request", ex);
195b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                }
196b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            } catch (CameraAccessException ex) {
197b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                Log.e(TAG, "Failed to build actual capture request", ex);
198b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            }
199b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        } else {
200b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            Log.e(TAG, "User attempted to perform a capture outside our session");
201b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        }
202b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
203b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        // Control flow continues in mImageCaptureListener.onImageAvailable()
204b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    }
205b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
206b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /**
207b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * Callbacks invoked upon state changes in our {@code SurfaceView}.
208b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     */
209b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
210b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        /** The camera device to use, or null if we haven't yet set a fixed surface size. */
211b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        private String mCameraId;
212b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
213b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        /** Whether we received a change callback after setting our fixed surface size. */
214b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        private boolean mGotSecondCallback;
215b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
216b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        @Override
217b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        public void surfaceCreated(SurfaceHolder holder) {
218b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            // This is called every time the surface returns to the foreground
219b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            Log.i(TAG, "Surface created");
220b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            mCameraId = null;
221b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            mGotSecondCallback = false;
222b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        }
223b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
224b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        @Override
225b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        public void surfaceDestroyed(SurfaceHolder holder) {
226b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            Log.i(TAG, "Surface destroyed");
227b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            holder.removeCallback(this);
228b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            // We don't stop receiving callbacks forever because onResume() will reattach us
229b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        }
230b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
231b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        @Override
232b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
233b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            // On the first invocation, width and height were automatically set to the view's size
234b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            if (mCameraId == null) {
235b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                // Find the device's back-facing camera and set the destination buffer sizes
236b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                try {
237b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    for (String cameraId : mCameraManager.getCameraIdList()) {
238b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                        CameraCharacteristics cameraCharacteristics =
239b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                                mCameraManager.getCameraCharacteristics(cameraId);
240b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                        if (cameraCharacteristics.get(cameraCharacteristics.LENS_FACING) ==
241b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                                CameraCharacteristics.LENS_FACING_BACK) {
242b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            Log.i(TAG, "Found a back-facing camera");
243b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            StreamConfigurationMap info = cameraCharacteristics
244b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                                    .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
245b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
246b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            // Bigger is better when it comes to saving our image
247b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            Size largestSize = Collections.max(
248b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                                    Arrays.asList(info.getOutputSizes(ImageFormat.JPEG)),
249b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                                    new CompareSizesByArea());
250b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
251b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            // Prepare an ImageReader in case the user wants to capture images
252b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            Log.i(TAG, "Capture size: " + largestSize);
253b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            mCaptureBuffer = ImageReader.newInstance(largestSize.getWidth(),
254b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                                    largestSize.getHeight(), ImageFormat.JPEG, /*maxImages*/2);
255b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            mCaptureBuffer.setOnImageAvailableListener(
256b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                                    mImageCaptureListener, mBackgroundHandler);
257b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
258b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            // Danger, W.R.! Attempting to use too large a preview size could
259b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            // exceed the camera bus' bandwidth limitation, resulting in
260b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            // gorgeous previews but the storage of garbage capture data.
261b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            Log.i(TAG, "SurfaceView size: " +
262b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                                    mSurfaceView.getWidth() + 'x' + mSurfaceView.getHeight());
263b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            Size optimalSize = chooseBigEnoughSize(
264b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                                    info.getOutputSizes(SurfaceHolder.class), width, height);
265b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
266b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            // Set the SurfaceHolder to use the camera's largest supported size
267b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            Log.i(TAG, "Preview size: " + optimalSize);
268b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            SurfaceHolder surfaceHolder = mSurfaceView.getHolder();
269b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            surfaceHolder.setFixedSize(optimalSize.getWidth(),
270b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                                    optimalSize.getHeight());
271b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
272b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            mCameraId = cameraId;
273b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            return;
274b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
275b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            // Control flow continues with this method one more time
276b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            // (since we just changed our own size)
277b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                        }
278b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    }
279b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                } catch (CameraAccessException ex) {
280b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    Log.e(TAG, "Unable to list cameras", ex);
281b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                }
282b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
283b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                Log.e(TAG, "Didn't find any back-facing cameras");
284b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            // This is the second time the method is being invoked: our size change is complete
285b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            } else if (!mGotSecondCallback) {
286b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                if (mCamera != null) {
287b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    Log.e(TAG, "Aborting camera open because it hadn't been closed");
288b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    return;
289b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                }
290b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
291b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                // Open the camera device
292b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                try {
293fd887436bd111e4d2c7307578a51b5070025b7f2Eino-Ville Talvala                    mCameraManager.openCamera(mCameraId, mCameraStateCallback,
294b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            mBackgroundHandler);
295b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                } catch (CameraAccessException ex) {
296b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    Log.e(TAG, "Failed to configure output surface", ex);
297b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                }
298b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                mGotSecondCallback = true;
299b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
300fd887436bd111e4d2c7307578a51b5070025b7f2Eino-Ville Talvala                // Control flow continues in mCameraStateCallback.onOpened()
301b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            }
302b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        }};
303b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
304b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /**
305b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * Calledbacks invoked upon state changes in our {@code CameraDevice}. <p>These are run on
306b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * {@code mBackgroundThread}.</p>
307b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     */
308fd887436bd111e4d2c7307578a51b5070025b7f2Eino-Ville Talvala    final CameraDevice.StateCallback mCameraStateCallback =
309fd887436bd111e4d2c7307578a51b5070025b7f2Eino-Ville Talvala            new CameraDevice.StateCallback() {
310b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        @Override
311b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        public void onOpened(CameraDevice camera) {
312b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            Log.i(TAG, "Successfully opened camera");
313b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            mCamera = camera;
314b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            try {
315b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                List<Surface> outputs = Arrays.asList(
316b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                        mSurfaceView.getHolder().getSurface(), mCaptureBuffer.getSurface());
317b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                camera.createCaptureSession(outputs, mCaptureSessionListener,
318b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                        mBackgroundHandler);
319b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            } catch (CameraAccessException ex) {
320b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                Log.e(TAG, "Failed to create a capture session", ex);
321b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            }
322b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
323b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            // Control flow continues in mCaptureSessionListener.onConfigured()
324b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        }
325b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
326b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        @Override
327b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        public void onDisconnected(CameraDevice camera) {
328b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            Log.e(TAG, "Camera was disconnected");
329b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        }
330b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
331b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        @Override
332b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        public void onError(CameraDevice camera, int error) {
333b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            Log.e(TAG, "State error on device '" + camera.getId() + "': code " + error);
334b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        }};
335b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
336b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /**
337b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * Callbacks invoked upon state changes in our {@code CameraCaptureSession}. <p>These are run on
338b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * {@code mBackgroundThread}.</p>
339b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     */
340fd887436bd111e4d2c7307578a51b5070025b7f2Eino-Ville Talvala    final CameraCaptureSession.StateCallback mCaptureSessionListener =
341fd887436bd111e4d2c7307578a51b5070025b7f2Eino-Ville Talvala            new CameraCaptureSession.StateCallback() {
342b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        @Override
343b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        public void onConfigured(CameraCaptureSession session) {
344b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            Log.i(TAG, "Finished configuring camera outputs");
345b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            mCaptureSession = session;
346b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
347b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            SurfaceHolder holder = mSurfaceView.getHolder();
348b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            if (holder != null) {
349b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                try {
350b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    // Build a request for preview footage
351b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    CaptureRequest.Builder requestBuilder =
352b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            mCamera.createCaptureRequest(mCamera.TEMPLATE_PREVIEW);
353b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    requestBuilder.addTarget(holder.getSurface());
354b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    CaptureRequest previewRequest = requestBuilder.build();
355b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
356b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    // Start displaying preview images
357b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    try {
358b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                        session.setRepeatingRequest(previewRequest, /*listener*/null,
359b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                                /*handler*/null);
360b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    } catch (CameraAccessException ex) {
361b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                        Log.e(TAG, "Failed to make repeating preview request", ex);
362b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    }
363b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                } catch (CameraAccessException ex) {
364b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    Log.e(TAG, "Failed to build preview request", ex);
365b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                }
366b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            }
367b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            else {
368b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                Log.e(TAG, "Holder didn't exist when trying to formulate preview request");
369b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            }
370b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        }
371b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
372b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        @Override
373b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        public void onClosed(CameraCaptureSession session) {
374b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            mCaptureSession = null;
375b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        }
376b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
377b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        @Override
378b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        public void onConfigureFailed(CameraCaptureSession session) {
379b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            Log.e(TAG, "Configuration error on device '" + mCamera.getId());
380b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        }};
381b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
382b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /**
383b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * Callback invoked when we've received a JPEG image from the camera.
384b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     */
385b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    final ImageReader.OnImageAvailableListener mImageCaptureListener =
386b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            new ImageReader.OnImageAvailableListener() {
387b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        @Override
388b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        public void onImageAvailable(ImageReader reader) {
389b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            // Save the image once we get a chance
390b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            mBackgroundHandler.post(new CapturedImageSaver(reader.acquireNextImage()));
391b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
392b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            // Control flow continues in CapturedImageSaver#run()
393b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        }};
394b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
395b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    /**
396b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * Deferred processor responsible for saving snapshots to disk. <p>This is run on
397b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     * {@code mBackgroundThread}.</p>
398b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher     */
399b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    static class CapturedImageSaver implements Runnable {
400b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        /** The image to save. */
401b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        private Image mCapture;
402b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
403b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        public CapturedImageSaver(Image capture) {
404b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            mCapture = capture;
405b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        }
406b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
407b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        @Override
408b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        public void run() {
409b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            try {
410b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                // Choose an unused filename under the Pictures/ directory
411b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                File file = File.createTempFile(CAPTURE_FILENAME_PREFIX, ".jpg",
412b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                        Environment.getExternalStoragePublicDirectory(
413b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                                Environment.DIRECTORY_PICTURES));
414b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                try (FileOutputStream ostream = new FileOutputStream(file)) {
415b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    Log.i(TAG, "Retrieved image is" +
416b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            (mCapture.getFormat() == ImageFormat.JPEG ? "" : "n't") + " a JPEG");
417b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    ByteBuffer buffer = mCapture.getPlanes()[0].getBuffer();
418b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    Log.i(TAG, "Captured image size: " +
419b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                            mCapture.getWidth() + 'x' + mCapture.getHeight());
420b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher
421b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    // Write the image out to the chosen file
422b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    byte[] jpeg = new byte[buffer.remaining()];
423b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    buffer.get(jpeg);
424b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    ostream.write(jpeg);
425b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                } catch (FileNotFoundException ex) {
426b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    Log.e(TAG, "Unable to open output file for writing", ex);
427b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                } catch (IOException ex) {
428b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                    Log.e(TAG, "Failed to write the image to the output file", ex);
429b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                }
430b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            } catch (IOException ex) {
431b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                Log.e(TAG, "Unable to create a new output file", ex);
432b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            } finally {
433b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher                mCapture.close();
434b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher            }
435b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher        }
436b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher    }
437b84fdffad40226ca642680c7d1eec7c7799f07aaSol Boucher}
438