AndroidCamera2AgentImpl.java revision 25ee73acd2dbd6f60deef5306994fbf3a7997936
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
17package com.android.ex.camera2.portability;
18
19import android.annotation.TargetApi;
20import android.content.Context;
21import android.graphics.ImageFormat;
22import android.graphics.Matrix;
23import android.graphics.Rect;
24import android.graphics.RectF;
25import android.graphics.SurfaceTexture;
26import android.hardware.camera2.CameraAccessException;
27import android.hardware.camera2.CameraCaptureSession;
28import android.hardware.camera2.CameraCharacteristics;
29import android.hardware.camera2.CameraDevice;
30import android.hardware.camera2.CameraManager;
31import android.hardware.camera2.CaptureFailure;
32import android.hardware.camera2.CaptureRequest;
33import android.hardware.camera2.CaptureResult;
34import android.hardware.camera2.TotalCaptureResult;
35import android.hardware.camera2.params.MeteringRectangle;
36import android.media.Image;
37import android.media.ImageReader;
38import android.media.MediaActionSound;
39import android.os.Build;
40import android.os.Handler;
41import android.os.HandlerThread;
42import android.os.Looper;
43import android.os.Message;
44import android.view.Surface;
45
46import com.android.ex.camera2.portability.debug.Log;
47import com.android.ex.camera2.utils.Camera2RequestSettingsSet;
48
49import java.nio.ByteBuffer;
50import java.util.ArrayList;
51import java.util.Arrays;
52import java.util.HashSet;
53import java.util.List;
54import java.util.Set;
55
56/**
57 * A class to implement {@link CameraAgent} of the Android camera2 framework.
58 */
59class AndroidCamera2AgentImpl extends CameraAgent {
60    private static final Log.Tag TAG = new Log.Tag("AndCam2AgntImp");
61
62    private final Camera2Handler mCameraHandler;
63    private final HandlerThread mCameraHandlerThread;
64    private final CameraStateHolder mCameraState;
65    private final DispatchThread mDispatchThread;
66    private final CameraManager mCameraManager;
67    private final MediaActionSound mNoisemaker;
68
69    /**
70     * Number of camera devices.  The length of {@code mCameraDevices} does not reveal this
71     * information because that list may contain since-invalidated indices.
72     */
73    private int mNumCameraDevices;
74
75    /**
76     * Transformation between integral camera indices and the {@link java.lang.String} indices used
77     * by the underlying API.  Note that devices may disappear because they've been disconnected or
78     * have otherwise gone offline.  Because we need to keep the meanings of whatever indices we
79     * expose stable, we cannot simply remove them in such a case; instead, we insert {@code null}s
80     * to invalidate any such indices.  Whenever new devices appear, they are appended to the end of
81     * the list, and thereby assigned the lowest index that has never yet been used.
82     */
83    private final List<String> mCameraDevices;
84
85    AndroidCamera2AgentImpl(Context context) {
86        mCameraHandlerThread = new HandlerThread("Camera2 Handler Thread");
87        mCameraHandlerThread.start();
88        mCameraHandler = new Camera2Handler(mCameraHandlerThread.getLooper());
89        mCameraState = new AndroidCamera2StateHolder();
90        mDispatchThread = new DispatchThread(mCameraHandler, mCameraHandlerThread);
91        mDispatchThread.start();
92        mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
93        mNoisemaker = new MediaActionSound();
94        mNoisemaker.load(MediaActionSound.SHUTTER_CLICK);
95
96        mNumCameraDevices = 0;
97        mCameraDevices = new ArrayList<String>();
98        updateCameraDevices();
99    }
100
101    /**
102     * Updates the camera device index assignments stored in {@link mCameraDevices}, without
103     * reappropriating any currently-assigned index.
104     * @return Whether the operation was successful
105     */
106    private boolean updateCameraDevices() {
107        try {
108            String[] currentCameraDevices = mCameraManager.getCameraIdList();
109            Set<String> currentSet = new HashSet<String>(Arrays.asList(currentCameraDevices));
110
111            // Invalidate the indices assigned to any camera devices that are no longer present
112            for (int index = 0; index < mCameraDevices.size(); ++index) {
113                if (!currentSet.contains(mCameraDevices.get(index))) {
114                    mCameraDevices.set(index, null);
115                    --mNumCameraDevices;
116                }
117            }
118
119            // Assign fresh indices to any new camera devices
120            currentSet.removeAll(mCameraDevices); // The devices we didn't know about
121            for (String device : currentCameraDevices) {
122                if (currentSet.contains(device)) {
123                    mCameraDevices.add(device);
124                    ++mNumCameraDevices;
125                }
126            }
127
128            return true;
129        } catch (CameraAccessException ex) {
130            Log.e(TAG, "Could not get device listing from camera subsystem", ex);
131            return false;
132        }
133    }
134
135    // TODO: Implement
136    @Override
137    public void setCameraDefaultExceptionCallback(CameraExceptionCallback callback,
138            Handler handler) {}
139
140    // TODO: Implement
141    @Override
142    public void recycle() {}
143
144    // TODO: Some indices may now be invalid; ensure everyone can handle that and update the docs
145    @Override
146    public CameraDeviceInfo getCameraDeviceInfo() {
147        updateCameraDevices();
148        return new AndroidCamera2DeviceInfo(mCameraManager, mCameraDevices.toArray(new String[0]),
149                mNumCameraDevices);
150    }
151
152    @Override
153    protected Handler getCameraHandler() {
154        return mCameraHandler;
155    }
156
157    @Override
158    protected DispatchThread getDispatchThread() {
159        return mDispatchThread;
160    }
161
162    private static abstract class CaptureAvailableListener
163            extends CameraCaptureSession.CaptureCallback
164            implements ImageReader.OnImageAvailableListener {};
165
166    private class Camera2Handler extends HistoryHandler {
167        // Caller-provided when leaving CAMERA_UNOPENED state:
168        private CameraOpenCallback mOpenCallback;
169        private int mCameraIndex;
170        private String mCameraId;
171
172        // Available in CAMERA_UNCONFIGURED state and above:
173        private CameraDevice mCamera;
174        private AndroidCamera2ProxyImpl mCameraProxy;
175        private Camera2RequestSettingsSet mPersistentSettings;
176        private Rect mActiveArray;
177        private boolean mLegacyDevice;
178
179        // Available in CAMERA_CONFIGURED state and above:
180        private Size mPreviewSize;
181        private Size mPhotoSize;
182
183        // Available in PREVIEW_READY state and above:
184        private SurfaceTexture mPreviewTexture;
185        private Surface mPreviewSurface;
186        private CameraCaptureSession mSession;
187        private ImageReader mCaptureReader;
188
189        // Available from the beginning of PREVIEW_ACTIVE until the first preview frame arrives:
190        private CameraStartPreviewCallback mOneshotPreviewingCallback;
191
192        // Available in FOCUS_LOCKED between AF trigger receipt and whenever the lens stops moving:
193        private CameraAFCallback mOneshotAfCallback;
194
195        // Available when taking picture between AE trigger receipt and autoexposure convergence
196        private CaptureAvailableListener mOneshotCaptureCallback;
197
198        // Available whenever setAutoFocusMoveCallback() was last invoked with a non-null argument:
199        private CameraAFMoveCallback mPassiveAfCallback;
200
201        // Gets reset on every state change
202        private int mCurrentAeState = CaptureResult.CONTROL_AE_STATE_INACTIVE;
203
204        Camera2Handler(Looper looper) {
205            super(looper);
206        }
207
208        @Override
209        public void handleMessage(final Message msg) {
210            super.handleMessage(msg);
211            try {
212                switch(msg.what) {
213                    case CameraActions.OPEN_CAMERA:
214                    case CameraActions.RECONNECT: {
215                        CameraOpenCallback openCallback = (CameraOpenCallback) msg.obj;
216                        int cameraIndex = msg.arg1;
217
218                        if (mCameraState.getState() > AndroidCamera2StateHolder.CAMERA_UNOPENED) {
219                            openCallback.onDeviceOpenedAlready(cameraIndex,
220                                    generateHistoryString(cameraIndex));
221                            break;
222                        }
223
224                        mOpenCallback = openCallback;
225                        mCameraIndex = cameraIndex;
226                        mCameraId = mCameraDevices.get(mCameraIndex);
227                        Log.i(TAG, String.format("Opening camera index %d (id %s) with camera2 API",
228                                cameraIndex, mCameraId));
229
230                        if (mCameraId == null) {
231                            mOpenCallback.onCameraDisabled(msg.arg1);
232                            break;
233                        }
234                        mCameraManager.openCamera(mCameraId, mCameraDeviceStateCallback, this);
235
236                        break;
237                    }
238
239                    case CameraActions.RELEASE: {
240                        if (mCameraState.getState() == AndroidCamera2StateHolder.CAMERA_UNOPENED) {
241                            Log.w(TAG, "Ignoring release at inappropriate time");
242                            break;
243                        }
244
245                        if (mSession != null) {
246                            closePreviewSession();
247                            mSession = null;
248                        }
249                        if (mCamera != null) {
250                            mCamera.close();
251                            mCamera = null;
252                        }
253                        mCameraProxy = null;
254                        mPersistentSettings = null;
255                        mActiveArray = null;
256                        if (mPreviewSurface != null) {
257                            mPreviewSurface.release();
258                            mPreviewSurface = null;
259                        }
260                        mPreviewTexture = null;
261                        if (mCaptureReader != null) {
262                            mCaptureReader.close();
263                            mCaptureReader = null;
264                        }
265                        mPreviewSize = null;
266                        mPhotoSize = null;
267                        mCameraIndex = 0;
268                        mCameraId = null;
269                        changeState(AndroidCamera2StateHolder.CAMERA_UNOPENED);
270                        break;
271                    }
272
273                    /*case CameraActions.UNLOCK: {
274                        break;
275                    }
276
277                    case CameraActions.LOCK: {
278                        break;
279                    }*/
280
281                    case CameraActions.SET_PREVIEW_TEXTURE_ASYNC: {
282                        setPreviewTexture((SurfaceTexture) msg.obj);
283                        break;
284                    }
285
286                    case CameraActions.START_PREVIEW_ASYNC: {
287                        if (mCameraState.getState() !=
288                                        AndroidCamera2StateHolder.CAMERA_PREVIEW_READY) {
289                            // TODO: Provide better feedback here?
290                            Log.w(TAG, "Refusing to start preview at inappropriate time");
291                            break;
292                        }
293
294                        mOneshotPreviewingCallback = (CameraStartPreviewCallback) msg.obj;
295                        changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE);
296                        try {
297                            mSession.setRepeatingRequest(
298                                    mPersistentSettings.createRequest(mCamera,
299                                            CameraDevice.TEMPLATE_PREVIEW, mPreviewSurface),
300                                    /*listener*/mCameraResultStateCallback, /*handler*/this);
301                        } catch(CameraAccessException ex) {
302                            Log.w(TAG, "Unable to start preview", ex);
303                            changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY);
304                        }
305                        break;
306                    }
307
308                    // FIXME: We need to tear down the CameraCaptureSession here
309                    // (and unlock the CameraSettings object from our
310                    // CameraProxy) so that the preview/photo sizes can be
311                    // changed again while no preview is running.
312                    case CameraActions.STOP_PREVIEW: {
313                        if (mCameraState.getState() <
314                                        AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) {
315                            Log.w(TAG, "Refusing to stop preview at inappropriate time");
316                            break;
317                        }
318
319                        mSession.stopRepeating();
320                        changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY);
321                        break;
322                    }
323
324                    /*case CameraActions.SET_PREVIEW_CALLBACK_WITH_BUFFER: {
325                        break;
326                    }
327
328                    case CameraActions.ADD_CALLBACK_BUFFER: {
329                        break;
330                    }
331
332                    case CameraActions.SET_PREVIEW_DISPLAY_ASYNC: {
333                        break;
334                    }
335
336                    case CameraActions.SET_PREVIEW_CALLBACK: {
337                        break;
338                    }
339
340                    case CameraActions.SET_ONE_SHOT_PREVIEW_CALLBACK: {
341                        break;
342                    }
343
344                    case CameraActions.SET_PARAMETERS: {
345                        break;
346                    }
347
348                    case CameraActions.GET_PARAMETERS: {
349                        break;
350                    }
351
352                    case CameraActions.REFRESH_PARAMETERS: {
353                        break;
354                    }*/
355
356                    case CameraActions.APPLY_SETTINGS: {
357                        AndroidCamera2Settings settings = (AndroidCamera2Settings) msg.obj;
358                        applyToRequest(settings);
359                        break;
360                    }
361
362                    case CameraActions.AUTO_FOCUS: {
363                        // We only support locking the focus while a preview is being displayed.
364                        // However, it can be requested multiple times in succession; the effect of
365                        // the subsequent invocations is determined by the focus mode defined in the
366                        // provided CameraSettings object. In passive (CONTINUOUS_*) mode, the
367                        // duplicate requests are no-ops and leave the lens locked at its current
368                        // position, but in active (AUTO) mode, they perform another scan and lock
369                        // once that is finished. In any manual focus mode, this call is a no-op,
370                        // and most notably, this is the only case where the callback isn't invoked.
371                        if (mCameraState.getState() <
372                                        AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) {
373                            Log.w(TAG, "Ignoring attempt to autofocus without preview");
374                            break;
375                        }
376
377                        // The earliest we can reliably tell whether the autofocus has locked in
378                        // response to our latest request is when our one-time capture progresses.
379                        // However, it will probably take longer than that, so once that happens,
380                        // just start checking the repeating preview requests as they complete.
381                        final CameraAFCallback callback = (CameraAFCallback) msg.obj;
382                        CameraCaptureSession.CaptureCallback deferredCallbackSetter =
383                                new CameraCaptureSession.CaptureCallback() {
384                            private boolean mAlreadyDispatched = false;
385
386                            @Override
387                            public void onCaptureProgressed(CameraCaptureSession session,
388                                                            CaptureRequest request,
389                                                            CaptureResult result) {
390                                checkAfState(result);
391                            }
392
393                            @Override
394                            public void onCaptureCompleted(CameraCaptureSession session,
395                                                           CaptureRequest request,
396                                                           TotalCaptureResult result) {
397                                checkAfState(result);
398                            }
399
400                            private void checkAfState(CaptureResult result) {
401                                if (result.get(CaptureResult.CONTROL_AF_STATE) != null &&
402                                        !mAlreadyDispatched) {
403                                    // Now our mCameraResultStateCallback will invoke the callback
404                                    // the first time it finds the focus motor to be locked.
405                                    mAlreadyDispatched = true;
406                                    mOneshotAfCallback = callback;
407                                    // This is an optimization: check the AF state of this frame
408                                    // instead of simply waiting for the next.
409                                    mCameraResultStateCallback.monitorControlStates(result);
410                                }
411                            }
412
413                            @Override
414                            public void onCaptureFailed(CameraCaptureSession session,
415                                                        CaptureRequest request,
416                                                        CaptureFailure failure) {
417                                Log.e(TAG, "Focusing failed with reason " + failure.getReason());
418                                callback.onAutoFocus(false, mCameraProxy);
419                            }};
420
421                        // Send a one-time capture to trigger the camera driver to lock focus.
422                        changeState(AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED);
423                        Camera2RequestSettingsSet trigger =
424                                new Camera2RequestSettingsSet(mPersistentSettings);
425                        trigger.set(CaptureRequest.CONTROL_AF_TRIGGER,
426                                CaptureRequest.CONTROL_AF_TRIGGER_START);
427                        try {
428                            mSession.capture(
429                                    trigger.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW,
430                                            mPreviewSurface),
431                                    /*listener*/deferredCallbackSetter, /*handler*/ this);
432                        } catch(CameraAccessException ex) {
433                            Log.e(TAG, "Unable to lock autofocus", ex);
434                            changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE);
435                        }
436                        break;
437                    }
438
439                    case CameraActions.CANCEL_AUTO_FOCUS: {
440                        // Why would you want to unlock the lens if it isn't already locked?
441                        if (mCameraState.getState() <
442                                AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) {
443                            Log.w(TAG, "Ignoring attempt to release focus lock without preview");
444                            break;
445                        }
446
447                        // Send a one-time capture to trigger the camera driver to resume scanning.
448                        changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE);
449                        Camera2RequestSettingsSet cancel =
450                                new Camera2RequestSettingsSet(mPersistentSettings);
451                        cancel.set(CaptureRequest.CONTROL_AF_TRIGGER,
452                                CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
453                        try {
454                            mSession.capture(
455                                    cancel.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW,
456                                            mPreviewSurface),
457                                    /*listener*/null, /*handler*/this);
458                        } catch(CameraAccessException ex) {
459                            Log.e(TAG, "Unable to cancel autofocus", ex);
460                            changeState(AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED);
461                        }
462                        break;
463                    }
464
465                    case CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK: {
466                        mPassiveAfCallback = (CameraAFMoveCallback) msg.obj;
467                        break;
468                    }
469
470                    /*case CameraActions.SET_ZOOM_CHANGE_LISTENER: {
471                        break;
472                    }
473
474                    case CameraActions.SET_FACE_DETECTION_LISTENER: {
475                        break;
476                    }
477
478                    case CameraActions.START_FACE_DETECTION: {
479                        break;
480                    }
481
482                    case CameraActions.STOP_FACE_DETECTION: {
483                        break;
484                    }
485
486                    case CameraActions.SET_ERROR_CALLBACK: {
487                        break;
488                    }
489
490                    case CameraActions.ENABLE_SHUTTER_SOUND: {
491                        break;
492                    }*/
493
494                    case CameraActions.SET_DISPLAY_ORIENTATION: {
495                        // Only set the JPEG capture orientation if requested to do so; otherwise,
496                        // capture in the sensor's physical orientation. (e.g., JPEG rotation is
497                        // necessary in auto-rotate mode.
498                        mPersistentSettings.set(CaptureRequest.JPEG_ORIENTATION, msg.arg2 > 0 ?
499                                mCameraProxy.getCharacteristics().getJpegOrientation(msg.arg1) : 0);
500                        break;
501                    }
502
503                    case CameraActions.SET_JPEG_ORIENTATION: {
504                        mPersistentSettings.set(CaptureRequest.JPEG_ORIENTATION, msg.arg1);
505                        break;
506                    }
507
508                    case CameraActions.CAPTURE_PHOTO: {
509                        if (mCameraState.getState() <
510                                        AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) {
511                            Log.e(TAG, "Photos may only be taken when a preview is active");
512                            break;
513                        }
514                        if (mCameraState.getState() !=
515                                AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED) {
516                            Log.w(TAG, "Taking a (likely blurry) photo without the lens locked");
517                        }
518
519                        final CaptureAvailableListener listener =
520                                (CaptureAvailableListener) msg.obj;
521                        if (mLegacyDevice ||
522                                (mCurrentAeState == CaptureResult.CONTROL_AE_STATE_CONVERGED &&
523                                !mPersistentSettings.matches(CaptureRequest.CONTROL_AE_MODE,
524                                        CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH) &&
525                                !mPersistentSettings.matches(CaptureRequest.FLASH_MODE,
526                                        CaptureRequest.FLASH_MODE_SINGLE)))
527                                {
528                            // Legacy devices don't support the precapture state keys and instead
529                            // perform autoexposure convergence automatically upon capture.
530
531                            // On other devices, as long as it has already converged, it determined
532                            // that flash was not required, and we're not going to invalidate the
533                            // current exposure levels by forcing the force on, we can save
534                            // significant capture time by not forcing a recalculation.
535                            Log.i(TAG, "Skipping pre-capture autoexposure convergence");
536                            mCaptureReader.setOnImageAvailableListener(listener, /*handler*/this);
537                            try {
538                                mSession.capture(
539                                        mPersistentSettings.createRequest(mCamera,
540                                                CameraDevice.TEMPLATE_STILL_CAPTURE,
541                                                mCaptureReader.getSurface()),
542                                        listener, /*handler*/this);
543                            } catch (CameraAccessException ex) {
544                                Log.e(TAG, "Unable to initiate immediate capture", ex);
545                            }
546                        } else {
547                            // We need to let AE converge before capturing. Once our one-time
548                            // trigger capture has made it into the pipeline, we'll start checking
549                            // for the completion of that convergence, capturing when that happens.
550                            Log.i(TAG, "Forcing pre-capture autoexposure convergence");
551                            CameraCaptureSession.CaptureCallback deferredCallbackSetter =
552                                    new CameraCaptureSession.CaptureCallback() {
553                                private boolean mAlreadyDispatched = false;
554
555                                @Override
556                                public void onCaptureProgressed(CameraCaptureSession session,
557                                                                CaptureRequest request,
558                                                                CaptureResult result) {
559                                    checkAeState(result);
560                                }
561
562                                @Override
563                                public void onCaptureCompleted(CameraCaptureSession session,
564                                                               CaptureRequest request,
565                                                               TotalCaptureResult result) {
566                                    checkAeState(result);
567                                }
568
569                                private void checkAeState(CaptureResult result) {
570                                    if (result.get(CaptureResult.CONTROL_AE_STATE) != null &&
571                                            !mAlreadyDispatched) {
572                                        // Now our mCameraResultStateCallback will invoke the
573                                        // callback once the autoexposure routine has converged.
574                                        mAlreadyDispatched = true;
575                                        mOneshotCaptureCallback = listener;
576                                        // This is an optimization: check the AE state of this frame
577                                        // instead of simply waiting for the next.
578                                        mCameraResultStateCallback.monitorControlStates(result);
579                                    }
580                                }
581
582                                @Override
583                                public void onCaptureFailed(CameraCaptureSession session,
584                                                            CaptureRequest request,
585                                                            CaptureFailure failure) {
586                                    Log.e(TAG, "Autoexposure and capture failed with reason " +
587                                            failure.getReason());
588                                    // TODO: Make an error callback?
589                                }};
590
591                            // Set a one-time capture to trigger the camera driver's autoexposure:
592                            Camera2RequestSettingsSet expose =
593                                    new Camera2RequestSettingsSet(mPersistentSettings);
594                            expose.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
595                                    CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
596                            try {
597                                mSession.capture(
598                                        expose.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW,
599                                                mPreviewSurface),
600                                        /*listener*/deferredCallbackSetter, /*handler*/this);
601                            } catch (CameraAccessException ex) {
602                                Log.e(TAG, "Unable to run autoexposure and perform capture", ex);
603                            }
604                        }
605                        break;
606                    }
607
608                    default: {
609                        // TODO: Rephrase once everything has been implemented
610                        throw new RuntimeException("Unimplemented CameraProxy message=" + msg.what);
611                    }
612                }
613            } catch (final Exception ex) {
614                if (msg.what != CameraActions.RELEASE && mCamera != null) {
615                    // TODO: Handle this better
616                    mCamera.close();
617                    mCamera = null;
618                } else if (mCamera == null) {
619                    if (msg.what == CameraActions.OPEN_CAMERA) {
620                        if (mOpenCallback != null) {
621                            mOpenCallback.onDeviceOpenFailure(mCameraIndex,
622                                    generateHistoryString(mCameraIndex));
623                        }
624                    } else {
625                        Log.w(TAG, "Cannot handle message " + msg.what + ", mCamera is null");
626                    }
627                    return;
628                }
629
630                if (ex instanceof RuntimeException) {
631                    post(new Runnable() {
632                        @Override
633                        public void run() {
634                            sCameraExceptionCallback.onCameraException((RuntimeException) ex);
635                        }});
636                }
637            }
638        }
639
640        public CameraSettings buildSettings(AndroidCamera2Capabilities caps) {
641            try {
642                return new AndroidCamera2Settings(mCamera, CameraDevice.TEMPLATE_PREVIEW,
643                        mActiveArray, mPreviewSize, mPhotoSize);
644            } catch (CameraAccessException ex) {
645                Log.e(TAG, "Unable to query camera device to build settings representation");
646                return null;
647            }
648        }
649
650        /**
651         * Simply propagates settings from provided {@link CameraSettings}
652         * object to our {@link CaptureRequest.Builder} for use in captures.
653         * <p>Most conversions to match the API 2 formats are performed by
654         * {@link AndroidCamera2Capabilities.IntegralStringifier}; otherwise
655         * any final adjustments are done here before updating the builder.</p>
656         *
657         * @param settings The new/updated settings
658         */
659        private void applyToRequest(AndroidCamera2Settings settings) {
660            // TODO: If invoked when in PREVIEW_READY state, a new preview size will not take effect
661
662            mPersistentSettings.union(settings.getRequestSettings());
663            mPreviewSize = settings.getCurrentPreviewSize();
664            mPhotoSize = settings.getCurrentPhotoSize();
665
666            if (mCameraState.getState() >= AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) {
667                // If we're already previewing, reflect most settings immediately
668                try {
669                    mSession.setRepeatingRequest(
670                            mPersistentSettings.createRequest(mCamera,
671                                    CameraDevice.TEMPLATE_PREVIEW, mPreviewSurface),
672                            /*listener*/mCameraResultStateCallback, /*handler*/this);
673                } catch (CameraAccessException ex) {
674                    Log.e(TAG, "Failed to apply updated request settings", ex);
675                }
676            } else if (mCameraState.getState() < AndroidCamera2StateHolder.CAMERA_PREVIEW_READY) {
677                // If we're already ready to preview, this doesn't regress our state
678                changeState(AndroidCamera2StateHolder.CAMERA_CONFIGURED);
679            }
680        }
681
682        private void setPreviewTexture(SurfaceTexture surfaceTexture) {
683            // TODO: Must be called after providing a .*Settings populated with sizes
684            // TODO: We don't technically offer a selection of sizes tailored to SurfaceTextures!
685
686            // TODO: Handle this error condition with a callback or exception
687            if (mCameraState.getState() < AndroidCamera2StateHolder.CAMERA_CONFIGURED) {
688                Log.w(TAG, "Ignoring texture setting at inappropriate time");
689                return;
690            }
691
692            // Avoid initializing another capture session unless we absolutely have to
693            if (surfaceTexture == mPreviewTexture) {
694                Log.i(TAG, "Optimizing out redundant preview texture setting");
695                return;
696            }
697
698            if (mSession != null) {
699                closePreviewSession();
700            }
701
702            mPreviewTexture = surfaceTexture;
703            surfaceTexture.setDefaultBufferSize(mPreviewSize.width(), mPreviewSize.height());
704
705            if (mPreviewSurface != null) {
706                mPreviewSurface.release();
707            }
708            mPreviewSurface = new Surface(surfaceTexture);
709
710            if (mCaptureReader != null) {
711                mCaptureReader.close();
712            }
713            mCaptureReader = ImageReader.newInstance(
714                    mPhotoSize.width(), mPhotoSize.height(), ImageFormat.JPEG, 1);
715
716            try {
717                mCamera.createCaptureSession(
718                        Arrays.asList(mPreviewSurface, mCaptureReader.getSurface()),
719                        mCameraPreviewStateCallback, this);
720            } catch (CameraAccessException ex) {
721                Log.e(TAG, "Failed to create camera capture session", ex);
722            }
723        }
724
725        private void closePreviewSession() {
726            try {
727                mSession.abortCaptures();
728                mSession = null;
729            } catch (CameraAccessException ex) {
730                Log.e(TAG, "Failed to close existing camera capture session", ex);
731            }
732            changeState(AndroidCamera2StateHolder.CAMERA_CONFIGURED);
733        }
734
735        private void changeState(int newState) {
736            if (mCameraState.getState() != newState) {
737                mCameraState.setState(newState);
738                if (newState < AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) {
739                    mCurrentAeState = CaptureResult.CONTROL_AE_STATE_INACTIVE;
740                    mCameraResultStateCallback.resetState();
741                }
742            }
743        }
744
745        // This callback monitors our connection to and disconnection from camera devices.
746        private CameraDevice.StateCallback mCameraDeviceStateCallback =
747                new CameraDevice.StateCallback() {
748            @Override
749            public void onOpened(CameraDevice camera) {
750                mCamera = camera;
751                if (mOpenCallback != null) {
752                    try {
753                        CameraCharacteristics props =
754                                mCameraManager.getCameraCharacteristics(mCameraId);
755                        mCameraProxy = new AndroidCamera2ProxyImpl(mCameraIndex, mCamera,
756                                    getCameraDeviceInfo().getCharacteristics(mCameraIndex), props);
757                        mPersistentSettings = new Camera2RequestSettingsSet();
758                        mActiveArray =
759                                props.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
760                        mLegacyDevice =
761                                props.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
762                                        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
763                        changeState(AndroidCamera2StateHolder.CAMERA_UNCONFIGURED);
764                        mOpenCallback.onCameraOpened(mCameraProxy);
765                    } catch (CameraAccessException ex) {
766                        mOpenCallback.onDeviceOpenFailure(mCameraIndex,
767                                generateHistoryString(mCameraIndex));
768                    }
769                }
770            }
771
772            @Override
773            public void onDisconnected(CameraDevice camera) {
774                Log.w(TAG, "Camera device '" + mCameraIndex + "' was disconnected");
775            }
776
777            @Override
778            public void onError(CameraDevice camera, int error) {
779                Log.e(TAG, "Camera device '" + mCameraIndex + "' encountered error code '" +
780                        error + '\'');
781                if (mOpenCallback != null) {
782                    mOpenCallback.onDeviceOpenFailure(mCameraIndex,
783                            generateHistoryString(mCameraIndex));
784                }
785            }};
786
787        // This callback monitors our camera session (i.e. our transition into and out of preview).
788        private CameraCaptureSession.StateCallback mCameraPreviewStateCallback =
789                new CameraCaptureSession.StateCallback() {
790            @Override
791            public void onConfigured(CameraCaptureSession session) {
792                mSession = session;
793                changeState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY);
794            }
795
796            @Override
797            public void onConfigureFailed(CameraCaptureSession session) {
798                // TODO: Invoke a callback
799                Log.e(TAG, "Failed to configure the camera for capture");
800            }
801
802            @Override
803            public void onActive(CameraCaptureSession session) {
804                if (mOneshotPreviewingCallback != null) {
805                    // The session is up and processing preview requests. Inform the caller.
806                    mOneshotPreviewingCallback.onPreviewStarted();
807                    mOneshotPreviewingCallback = null;
808                }
809            }};
810
811        private abstract class CameraResultStateCallback
812                extends CameraCaptureSession.CaptureCallback {
813            public abstract void monitorControlStates(CaptureResult result);
814
815            public abstract void resetState();
816        }
817
818        // This callback monitors requested captures and notifies any relevant callbacks.
819        private CameraResultStateCallback mCameraResultStateCallback =
820                new CameraResultStateCallback() {
821            private int mLastAfState = -1;
822            private long mLastAfFrameNumber = -1;
823            private long mLastAeFrameNumber = -1;
824
825            @Override
826            public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
827                                            CaptureResult result) {
828                monitorControlStates(result);
829            }
830
831            @Override
832            public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
833                                           TotalCaptureResult result) {
834                monitorControlStates(result);
835            }
836
837            @Override
838            public void monitorControlStates(CaptureResult result) {
839                Integer afStateMaybe = result.get(CaptureResult.CONTROL_AF_STATE);
840                if (afStateMaybe != null) {
841                    int afState = afStateMaybe;
842                    // Since we handle both partial and total results for multiple frames here, we
843                    // might get the final callbacks for an earlier frame after receiving one or
844                    // more that correspond to the next one. To prevent our data from oscillating,
845                    // we never consider AF states that are older than the last one we've seen.
846                    if (result.getFrameNumber() > mLastAfFrameNumber) {
847                        boolean afStateChanged = afState != mLastAfState;
848                        mLastAfState = afState;
849                        mLastAfFrameNumber = result.getFrameNumber();
850
851                        switch (afState) {
852                            case CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN:
853                            case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED:
854                            case CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED: {
855                                if (afStateChanged && mPassiveAfCallback != null) {
856                                    // A CameraAFMoveCallback is attached. If we just started to
857                                    // scan, the motor is moving; otherwise, it has settled.
858                                    mPassiveAfCallback.onAutoFocusMoving(
859                                            afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN,
860                                            mCameraProxy);
861                                }
862                                break;
863                            }
864
865                            case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED:
866                            case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED: {
867                                // This check must be made regardless of whether the focus state has
868                                // changed recently to avoid infinite waiting during autoFocus()
869                                // when the algorithm has already either converged or failed to.
870                                if (mOneshotAfCallback != null) {
871                                    // A call to autoFocus() was just made to request a focus lock.
872                                    // Notify the caller that the lens is now indefinitely fixed,
873                                    // and report whether the image we're stuck with is in focus.
874                                    mOneshotAfCallback.onAutoFocus(
875                                            afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED,
876                                            mCameraProxy);
877                                    mOneshotAfCallback = null;
878                                }
879                                break;
880                            }
881                        }
882                    }
883                }
884
885                Integer aeStateMaybe = result.get(CaptureResult.CONTROL_AE_STATE);
886                if (aeStateMaybe != null) {
887                    int aeState = aeStateMaybe;
888                    // Since we handle both partial and total results for multiple frames here, we
889                    // might get the final callbacks for an earlier frame after receiving one or
890                    // more that correspond to the next one. To prevent our data from oscillating,
891                    // we never consider AE states that are older than the last one we've seen.
892                    if (result.getFrameNumber() > mLastAeFrameNumber) {
893                        mCurrentAeState = aeStateMaybe;
894                        mLastAeFrameNumber = result.getFrameNumber();
895
896                        switch (aeState) {
897                            case CaptureResult.CONTROL_AE_STATE_CONVERGED:
898                            case CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED:
899                            case CaptureResult.CONTROL_AE_STATE_LOCKED: {
900                                // This check must be made regardless of whether the exposure state
901                                // has changed recently to avoid infinite waiting during
902                                // takePicture() when the algorithm has already converged.
903                                if (mOneshotCaptureCallback != null) {
904                                    // A call to takePicture() was just made, and autoexposure
905                                    // converged so it's time to initiate the capture!
906                                    mCaptureReader.setOnImageAvailableListener(
907                                            /*listener*/mOneshotCaptureCallback,
908                                            /*handler*/Camera2Handler.this);
909                                    try {
910                                        mSession.capture(
911                                                mPersistentSettings.createRequest(mCamera,
912                                                        CameraDevice.TEMPLATE_STILL_CAPTURE,
913                                                        mCaptureReader.getSurface()),
914                                                /*callback*/mOneshotCaptureCallback,
915                                                /*handler*/Camera2Handler.this);
916                                    } catch (CameraAccessException ex) {
917                                        Log.e(TAG, "Unable to initiate capture", ex);
918                                    } finally {
919                                        mOneshotCaptureCallback = null;
920                                    }
921                                }
922                                break;
923                            }
924                        }
925                    }
926                }
927            }
928
929            @Override
930            public void resetState() {
931                mLastAfState = -1;
932                mLastAfFrameNumber = -1;
933                mLastAeFrameNumber = -1;
934            }
935
936            @Override
937            public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request,
938                                        CaptureFailure failure) {
939                Log.e(TAG, "Capture attempt failed with reason " + failure.getReason());
940            }};
941    }
942
943    private class AndroidCamera2ProxyImpl extends CameraAgent.CameraProxy {
944        private final int mCameraIndex;
945        private final CameraDevice mCamera;
946        private final CameraDeviceInfo.Characteristics mCharacteristics;
947        private final AndroidCamera2Capabilities mCapabilities;
948        private CameraSettings mLastSettings;
949        private boolean mShutterSoundEnabled;
950
951        public AndroidCamera2ProxyImpl(int cameraIndex, CameraDevice camera,
952                CameraDeviceInfo.Characteristics characteristics,
953                CameraCharacteristics properties) {
954            mCameraIndex = cameraIndex;
955            mCamera = camera;
956            mCharacteristics = characteristics;
957            mCapabilities = new AndroidCamera2Capabilities(properties);
958            mLastSettings = null;
959            mShutterSoundEnabled = true;
960        }
961
962        // TODO: Implement
963        @Override
964        public android.hardware.Camera getCamera() { return null; }
965
966        @Override
967        public int getCameraId() {
968            return mCameraIndex;
969        }
970
971        @Override
972        public CameraDeviceInfo.Characteristics getCharacteristics() {
973            return mCharacteristics;
974        }
975
976        @Override
977        public CameraCapabilities getCapabilities() {
978            return mCapabilities;
979        }
980
981        private AndroidCamera2Capabilities getSpecializedCapabilities() {
982            return mCapabilities;
983        }
984
985        // FIXME: Unlock the sizes in stopPreview(), as per the corresponding
986        // explanation on the STOP_PREVIEW case in the handler.
987        @Override
988        public void setPreviewTexture(SurfaceTexture surfaceTexture) {
989            // Once the Surface has been selected, we configure the session and
990            // are no longer able to change the sizes.
991            getSettings().setSizesLocked(true);
992            super.setPreviewTexture(surfaceTexture);
993        }
994
995        // FIXME: Unlock the sizes in stopPreview(), as per the corresponding
996        // explanation on the STOP_PREVIEW case in the handler.
997        @Override
998        public void setPreviewTextureSync(SurfaceTexture surfaceTexture) {
999            // Once the Surface has been selected, we configure the session and
1000            // are no longer able to change the sizes.
1001            getSettings().setSizesLocked(true);
1002            super.setPreviewTexture(surfaceTexture);
1003        }
1004
1005        // TODO: Implement
1006        @Override
1007        public void setPreviewDataCallback(Handler handler, CameraPreviewDataCallback cb) {}
1008
1009        // TODO: Implement
1010        @Override
1011        public void setOneShotPreviewCallback(Handler handler, CameraPreviewDataCallback cb) {}
1012
1013        // TODO: Implement
1014        @Override
1015        public void setPreviewDataCallbackWithBuffer(Handler handler, CameraPreviewDataCallback cb)
1016                {}
1017
1018        // TODO: Implement
1019        public void addCallbackBuffer(final byte[] callbackBuffer) {}
1020
1021        @Override
1022        public void autoFocus(final Handler handler, final CameraAFCallback cb) {
1023            mDispatchThread.runJob(new Runnable() {
1024                @Override
1025                public void run() {
1026                    CameraAFCallback cbForward = null;
1027                    if (cb != null) {
1028                        cbForward = new CameraAFCallback() {
1029                            @Override
1030                            public void onAutoFocus(final boolean focused,
1031                                                    final CameraProxy camera) {
1032                                handler.post(new Runnable() {
1033                                    @Override
1034                                    public void run() {
1035                                        cb.onAutoFocus(focused, camera);
1036                                    }});
1037                            }};
1038                    }
1039
1040                    mCameraState.waitForStates(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE |
1041                            AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED);
1042                    mCameraHandler.obtainMessage(CameraActions.AUTO_FOCUS, cbForward)
1043                            .sendToTarget();
1044                }});
1045        }
1046
1047        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1048        @Override
1049        public void setAutoFocusMoveCallback(final Handler handler, final CameraAFMoveCallback cb) {
1050            mDispatchThread.runJob(new Runnable() {
1051                @Override
1052                public void run() {
1053                    CameraAFMoveCallback cbForward = null;
1054                    if (cb != null) {
1055                        cbForward = new CameraAFMoveCallback() {
1056                            @Override
1057                            public void onAutoFocusMoving(final boolean moving,
1058                                                          final CameraProxy camera) {
1059                                handler.post(new Runnable() {
1060                                    @Override
1061                                    public void run() {
1062                                        cb.onAutoFocusMoving(moving, camera);
1063                                    }});
1064                                }};
1065                    }
1066
1067                    mCameraHandler.obtainMessage(CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK,
1068                            cbForward).sendToTarget();
1069                }});
1070        }
1071
1072        @Override
1073        public void takePicture(final Handler handler,
1074                                final CameraShutterCallback shutter,
1075                                CameraPictureCallback raw,
1076                                CameraPictureCallback postview,
1077                                final CameraPictureCallback jpeg) {
1078            // TODO: We never call raw or postview
1079            final CaptureAvailableListener picListener =
1080                    new CaptureAvailableListener() {
1081                @Override
1082                public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
1083                                             long timestamp) {
1084                    if (shutter != null) {
1085                        handler.post(new Runnable() {
1086                            @Override
1087                            public void run() {
1088                                if (mShutterSoundEnabled) {
1089                                    mNoisemaker.play(MediaActionSound.SHUTTER_CLICK);
1090                                }
1091                                shutter.onShutter(AndroidCamera2ProxyImpl.this);
1092                            }});
1093                    }
1094                }
1095
1096                @Override
1097                public void onImageAvailable(ImageReader reader) {
1098                    try (Image image = reader.acquireNextImage()) {
1099                        if (jpeg != null) {
1100                            ByteBuffer buffer = image.getPlanes()[0].getBuffer();
1101                            final byte[] pixels = new byte[buffer.remaining()];
1102                            buffer.get(pixels);
1103                            handler.post(new Runnable() {
1104                                @Override
1105                                public void run() {
1106                                    jpeg.onPictureTaken(pixels, AndroidCamera2ProxyImpl.this);
1107                                }});
1108                        }
1109                    }
1110                }};
1111            mDispatchThread.runJob(new Runnable() {
1112                @Override
1113                public void run() {
1114                    // Wait until PREVIEW_ACTIVE or better
1115                    mCameraState.waitForStates(
1116                            ~(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE - 1));
1117                    mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO, picListener)
1118                            .sendToTarget();
1119                }});
1120        }
1121
1122        // TODO: Implement
1123        @Override
1124        public void setZoomChangeListener(android.hardware.Camera.OnZoomChangeListener listener) {}
1125
1126        // TODO: Implement
1127        @Override
1128        public void setFaceDetectionCallback(Handler handler, CameraFaceDetectionCallback callback)
1129                {}
1130
1131        // TODO: Remove this method override once we handle this message
1132        @Override
1133        public void startFaceDetection() {}
1134
1135        // TODO: Remove this method override once we handle this message
1136        @Override
1137        public void stopFaceDetection() {}
1138
1139        // TODO: Implement
1140        @Override
1141        public void setErrorCallback(Handler handler, CameraErrorCallback cb) {}
1142
1143        // TODO: Implement
1144        @Override
1145        public void setParameters(android.hardware.Camera.Parameters params) {}
1146
1147        // TODO: Implement
1148        @Override
1149        public android.hardware.Camera.Parameters getParameters() { return null; }
1150
1151        @Override
1152        public CameraSettings getSettings() {
1153            if (mLastSettings == null) {
1154                mLastSettings = mCameraHandler.buildSettings(mCapabilities);
1155            }
1156            return mLastSettings;
1157        }
1158
1159        @Override
1160        public boolean applySettings(CameraSettings settings) {
1161            if (settings == null) {
1162                Log.w(TAG, "null parameters in applySettings()");
1163                return false;
1164            }
1165            if (!(settings instanceof AndroidCamera2Settings)) {
1166                Log.e(TAG, "Provided settings not compatible with the backing framework API");
1167                return false;
1168            }
1169
1170            // Wait for any state that isn't OPENED
1171            if (applySettingsHelper(settings, ~AndroidCamera2StateHolder.CAMERA_UNOPENED)) {
1172                mLastSettings = settings;
1173                return true;
1174            }
1175            return false;
1176        }
1177
1178        @Override
1179        public void enableShutterSound(boolean enable) {
1180            mShutterSoundEnabled = enable;
1181        }
1182
1183        // TODO: Implement
1184        @Override
1185        public String dumpDeviceSettings() { return null; }
1186
1187        @Override
1188        public Handler getCameraHandler() {
1189            return AndroidCamera2AgentImpl.this.getCameraHandler();
1190        }
1191
1192        @Override
1193        public DispatchThread getDispatchThread() {
1194            return AndroidCamera2AgentImpl.this.getDispatchThread();
1195        }
1196
1197        @Override
1198        public CameraStateHolder getCameraState() {
1199            return mCameraState;
1200        }
1201    }
1202
1203    /** A linear state machine: each state entails all the states below it. */
1204    private static class AndroidCamera2StateHolder extends CameraStateHolder {
1205        // Usage flow: openCamera() -> applySettings() -> setPreviewTexture() -> startPreview() ->
1206        //             autoFocus() -> takePicture()
1207        // States are mutually exclusive, but must be separate bits so that they can be used with
1208        // the StateHolder#waitForStates() and StateHolder#waitToAvoidStates() methods.
1209        // Do not set the state to be a combination of these values!
1210        /* Camera states */
1211        /** No camera device is opened. */
1212        public static final int CAMERA_UNOPENED = 1 << 0;
1213        /** A camera is opened, but no settings have been provided. */
1214        public static final int CAMERA_UNCONFIGURED = 1 << 1;
1215        /** The open camera has been configured by providing it with settings. */
1216        public static final int CAMERA_CONFIGURED = 1 << 2;
1217        /** A capture session is ready to stream a preview, but still has no repeating request. */
1218        public static final int CAMERA_PREVIEW_READY = 1 << 3;
1219        /** A preview is currently being streamed. */
1220        public static final int CAMERA_PREVIEW_ACTIVE = 1 << 4;
1221        /** The lens is locked on a particular region. */
1222        public static final int CAMERA_FOCUS_LOCKED = 1 << 5;
1223
1224        public AndroidCamera2StateHolder() {
1225            this(CAMERA_UNOPENED);
1226        }
1227
1228        public AndroidCamera2StateHolder(int state) {
1229            super(state);
1230        }
1231    }
1232
1233    private static class AndroidCamera2DeviceInfo implements CameraDeviceInfo {
1234        private final CameraManager mCameraManager;
1235        private final String[] mCameraIds;
1236        private final int mNumberOfCameras;
1237        private final int mFirstBackCameraId;
1238        private final int mFirstFrontCameraId;
1239
1240        public AndroidCamera2DeviceInfo(CameraManager cameraManager,
1241                                        String[] cameraIds, int numberOfCameras) {
1242            mCameraManager = cameraManager;
1243            mCameraIds = cameraIds;
1244            mNumberOfCameras = numberOfCameras;
1245
1246            int firstBackId = NO_DEVICE;
1247            int firstFrontId = NO_DEVICE;
1248            for (int id = 0; id < cameraIds.length; ++id) {
1249                try {
1250                    int lensDirection = cameraManager.getCameraCharacteristics(cameraIds[id])
1251                            .get(CameraCharacteristics.LENS_FACING);
1252                    if (firstBackId == NO_DEVICE &&
1253                            lensDirection == CameraCharacteristics.LENS_FACING_BACK) {
1254                        firstBackId = id;
1255                    }
1256                    if (firstFrontId == NO_DEVICE &&
1257                            lensDirection == CameraCharacteristics.LENS_FACING_FRONT) {
1258                        firstFrontId = id;
1259                    }
1260                } catch (CameraAccessException ex) {
1261                    Log.w(TAG, "Couldn't get characteristics of camera '" + id + "'", ex);
1262                }
1263            }
1264            mFirstBackCameraId = firstBackId;
1265            mFirstFrontCameraId = firstFrontId;
1266        }
1267
1268        @Override
1269        public Characteristics getCharacteristics(int cameraId) {
1270            String actualId = mCameraIds[cameraId];
1271            try {
1272                CameraCharacteristics info = mCameraManager.getCameraCharacteristics(actualId);
1273                return new AndroidCharacteristics2(info);
1274            } catch (CameraAccessException ex) {
1275                return null;
1276            }
1277        }
1278
1279        @Override
1280        public int getNumberOfCameras() {
1281            return mNumberOfCameras;
1282        }
1283
1284        @Override
1285        public int getFirstBackCameraId() {
1286            return mFirstBackCameraId;
1287        }
1288
1289        @Override
1290        public int getFirstFrontCameraId() {
1291            return mFirstFrontCameraId;
1292        }
1293
1294        private static class AndroidCharacteristics2 extends Characteristics {
1295            private CameraCharacteristics mCameraInfo;
1296
1297            AndroidCharacteristics2(CameraCharacteristics cameraInfo) {
1298                mCameraInfo = cameraInfo;
1299            }
1300
1301            @Override
1302            public boolean isFacingBack() {
1303                return mCameraInfo.get(CameraCharacteristics.LENS_FACING)
1304                        .equals(CameraCharacteristics.LENS_FACING_BACK);
1305            }
1306
1307            @Override
1308            public boolean isFacingFront() {
1309                return mCameraInfo.get(CameraCharacteristics.LENS_FACING)
1310                        .equals(CameraCharacteristics.LENS_FACING_FRONT);
1311            }
1312
1313            @Override
1314            public int getSensorOrientation() {
1315                return mCameraInfo.get(CameraCharacteristics.SENSOR_ORIENTATION);
1316            }
1317
1318            @Override
1319            public Matrix getPreviewTransform(int currentDisplayOrientation,
1320                                              RectF surfaceDimensions,
1321                                              RectF desiredBounds) {
1322                if (!orientationIsValid(currentDisplayOrientation)) {
1323                    return new Matrix();
1324                }
1325
1326                // The system transparently transforms the image to fill the surface
1327                // when the device is in its natural orientation. We rotate the
1328                // coordinates of the rectangle's corners to be relative to the
1329                // original image, instead of to the current screen orientation.
1330                float[] surfacePolygon = rotate(convertRectToPoly(surfaceDimensions),
1331                        2 * currentDisplayOrientation / 90);
1332                float[] desiredPolygon = convertRectToPoly(desiredBounds);
1333
1334                Matrix transform = new Matrix();
1335                // Use polygons instead of rectangles so that rotation will be
1336                // calculated, since that is not done by the new camera API.
1337                transform.setPolyToPoly(surfacePolygon, 0, desiredPolygon, 0, 4);
1338                return transform;
1339            }
1340
1341            @Override
1342            public boolean canDisableShutterSound() {
1343                return true;
1344            }
1345
1346            private static float[] convertRectToPoly(RectF rf) {
1347                return new float[] {rf.left, rf.top, rf.right, rf.top,
1348                        rf.right, rf.bottom, rf.left, rf.bottom};
1349            }
1350
1351            private static float[] rotate(float[] arr, int times) {
1352                if (times < 0) {
1353                    times = times % arr.length + arr.length;
1354                }
1355
1356                float[] res = new float[arr.length];
1357                for (int offset = 0; offset < arr.length; ++offset) {
1358                    res[offset] = arr[(times + offset) % arr.length];
1359                }
1360                return res;
1361            }
1362        }
1363    }
1364
1365    private static final CameraExceptionCallback sCameraExceptionCallback =
1366            new CameraExceptionCallback() {
1367                @Override
1368                public synchronized void onCameraException(RuntimeException e) {
1369                    throw e;
1370                }
1371            };
1372}
1373