AndroidCamera2AgentImpl.java revision 7e0d39bf7b6e0f0df606e3f6c15f673f70fed3f7
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.SurfaceTexture;
22import android.hardware.camera2.CameraAccessException;
23import android.hardware.camera2.CameraCaptureSession;
24import android.hardware.camera2.CameraCharacteristics;
25import android.hardware.camera2.CameraDevice;
26import android.hardware.camera2.CameraManager;
27import android.hardware.camera2.CaptureRequest;
28import android.os.Build;
29import android.os.Handler;
30import android.os.HandlerThread;
31import android.os.Looper;
32import android.os.Message;
33import android.view.Surface;
34
35import com.android.ex.camera2.portability.debug.Log;
36
37import java.util.ArrayList;
38import java.util.Arrays;
39import java.util.HashSet;
40import java.util.List;
41import java.util.Set;
42
43/**
44 * A class to implement {@link CameraAgent} of the Android camera2 framework.
45 */
46class AndroidCamera2AgentImpl extends CameraAgent {
47    private static final Log.Tag TAG = new Log.Tag("AndCam2AgntImp");
48
49    private final Camera2Handler mCameraHandler;
50    private final HandlerThread mCameraHandlerThread;
51    private final CameraStateHolder mCameraState;
52    private final DispatchThread mDispatchThread;
53    private final CameraManager mCameraManager;
54
55    /**
56     * Number of camera devices.  The length of {@code mCameraDevices} does not reveal this
57     * information because that list may contain since-invalidated indices.
58     */
59    private int mNumCameraDevices;
60
61    /**
62     * Transformation between integral camera indices and the {@link java.lang.String} indices used
63     * by the underlying API.  Note that devices may disappear because they've been disconnected or
64     * have otherwise gone offline.  Because we need to keep the meanings of whatever indices we
65     * expose stable, we cannot simply remove them in such a case; instead, we insert {@code null}s
66     * to invalidate any such indices.  Whenever new devices appear, they are appended to the end of
67     * the list, and thereby assigned the lowest index that has never yet been used.
68     */
69    private final List<String> mCameraDevices;
70
71    AndroidCamera2AgentImpl(Context context) {
72        mCameraHandlerThread = new HandlerThread("Camera2 Handler Thread");
73        mCameraHandlerThread.start();
74        mCameraHandler = new Camera2Handler(mCameraHandlerThread.getLooper());
75        mCameraState = new AndroidCamera2StateHolder();
76        mDispatchThread = new DispatchThread(mCameraHandler, mCameraHandlerThread);
77        mDispatchThread.start();
78        mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
79
80        mNumCameraDevices = 0;
81        mCameraDevices = new ArrayList<String>();
82        updateCameraDevices();
83    }
84
85    /**
86     * Updates the camera device index assignments stored in {@link mCameraDevices}, without
87     * reappropriating any currently-assigned index.
88     * @return Whether the operation was successful
89     */
90    private boolean updateCameraDevices() {
91        try {
92            String[] currentCameraDevices = mCameraManager.getCameraIdList();
93            Set<String> currentSet = new HashSet<String>(Arrays.asList(currentCameraDevices));
94
95            // Invalidate the indices assigned to any camera devices that are no longer present
96            for (int index = 0; index < mCameraDevices.size(); ++index) {
97                if (!currentSet.contains(mCameraDevices.get(index))) {
98                    mCameraDevices.set(index, null);
99                    --mNumCameraDevices;
100                }
101            }
102
103            // Assign fresh indices to any new camera devices
104            currentSet.removeAll(mCameraDevices); // The devices we didn't know about
105            for (String device : currentCameraDevices) {
106                if (currentSet.contains(device)) {
107                    mCameraDevices.add(device);
108                    ++mNumCameraDevices;
109                }
110            }
111
112            return true;
113        } catch (CameraAccessException ex) {
114            Log.e(TAG, "Could not get device listing from camera subsystem", ex);
115            return false;
116        }
117    }
118
119    // TODO: Implement
120    @Override
121    public void setCameraDefaultExceptionCallback(CameraExceptionCallback callback,
122            Handler handler) {}
123
124    // TODO: Implement
125    @Override
126    public void recycle() {}
127
128    // TODO: Some indices may now be invalid; ensure everyone can handle that and update the docs
129    @Override
130    public CameraDeviceInfo getCameraDeviceInfo() {
131        updateCameraDevices();
132        return new AndroidCamera2DeviceInfo(mCameraManager, mCameraDevices.toArray(new String[0]),
133                mNumCameraDevices);
134    }
135
136    @Override
137    protected Handler getCameraHandler() {
138        return mCameraHandler;
139    }
140
141    @Override
142    protected DispatchThread getDispatchThread() {
143        return mDispatchThread;
144    }
145
146    private class Camera2Handler extends HistoryHandler {
147        private CameraOpenCallback mOpenCallback;
148        private int mCameraIndex;
149        private String mCameraId;
150        private CameraDevice mCamera;
151        private AndroidCamera2ProxyImpl mCameraProxy;
152        private CaptureRequest.Builder mPreviewRequestBuilder;
153        private Size mPreviewSize;
154        private Size mPhotoSize;
155        private SurfaceTexture mPreviewTexture;
156        private Surface mPreviewSurface;
157        private CameraCaptureSession mPreviewSession;
158
159        Camera2Handler(Looper looper) {
160            super(looper);
161        }
162
163        @Override
164        public void handleMessage(final Message msg) {
165            super.handleMessage(msg);
166            try {
167                switch(msg.what) {
168                    case CameraActions.OPEN_CAMERA:
169                    case CameraActions.RECONNECT: {
170                        CameraOpenCallback openCallback = (CameraOpenCallback) msg.obj;
171                        int cameraIndex = msg.arg1;
172
173                        if (mCameraState.getState() != AndroidCamera2StateHolder.CAMERA_UNOPENED) {
174                            openCallback.onDeviceOpenedAlready(cameraIndex,
175                                    generateHistoryString(cameraIndex));
176                            break;
177                        }
178
179                        mOpenCallback = openCallback;
180                        mCameraIndex = cameraIndex;
181                        mCameraId = mCameraDevices.get(mCameraIndex);
182
183                        if (mCameraId == null) {
184                            mOpenCallback.onCameraDisabled(msg.arg1);
185                            break;
186                        }
187                        mCameraManager.openCamera(mCameraId, mCameraDeviceStateListener, this);
188
189                        break;
190                    }
191
192                    case CameraActions.RELEASE: {
193                        if (mCameraState.getState() == AndroidCamera2StateHolder.CAMERA_UNOPENED) {
194                            Log.e(TAG, "Ignoring release at inappropriate time");
195                        }
196
197                        if (mCameraState.getState() ==
198                                AndroidCamera2StateHolder.CAMERA_PREVIEW_READY) {
199                            closePreviewSession();
200                        }
201                        if (mCamera != null) {
202                            mCamera.close();
203                            mCamera = null;
204                        }
205                        mCameraProxy = null;
206                        mPreviewRequestBuilder = null;
207                        if (mPreviewSurface != null) {
208                            mPreviewSurface.release();
209                            mPreviewSurface = null;
210                        }
211                        mPreviewTexture = null;
212                        mPreviewSize = null;
213                        mPhotoSize = null;
214                        mCameraIndex = 0;
215                        mCameraId = null;
216                        mCameraState.setState(AndroidCamera2StateHolder.CAMERA_UNOPENED);
217                        break;
218                    }
219
220                    /*case CameraActions.UNLOCK: {
221                        break;
222                    }
223
224                    case CameraActions.LOCK: {
225                        break;
226                    }*/
227
228                    case CameraActions.SET_PREVIEW_TEXTURE_ASYNC: {
229                        setPreviewTexture((SurfaceTexture) msg.obj);
230                        break;
231                    }
232
233                    case CameraActions.START_PREVIEW_ASYNC: {
234                        if (mCameraState.getState() !=
235                                        AndroidCamera2StateHolder.CAMERA_PREVIEW_READY ||
236                                mPreviewSession == null) {
237                            // TODO: Provide better feedback here?
238                            Log.e(TAG, "Refusing to start preview at inappropriate time");
239                            break;
240                        }
241
242                        final CameraStartPreviewCallbackForward cbForward =
243                                (CameraStartPreviewCallbackForward) msg.obj;
244                        CameraCaptureSession.CaptureListener cbDispatcher = null;
245                        if (cbForward != null) {
246                            cbDispatcher = new CameraCaptureSession.CaptureListener() {
247                                @Override
248                                public void onCaptureStarted(CameraCaptureSession session,
249                                                             CaptureRequest request,
250                                                             long timestamp) {
251                                    cbForward.onPreviewStarted();
252                                }};
253                        }
254                        mPreviewSession.setRepeatingRequest(mPreviewRequestBuilder.build(),
255                                cbDispatcher/*listener*/, this/*handler*/);
256                        mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE);
257                        break;
258                    }
259
260                    case CameraActions.STOP_PREVIEW: {
261                        if (mCameraState.getState() !=
262                                        AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE ||
263                                mPreviewSession == null) {
264                            Log.e(TAG, "Refusing to stop preview at inappropriate time");
265                        }
266
267                        mPreviewSession.stopRepeating();
268                        mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY);
269                        break;
270                    }
271
272                    /*case CameraActions.SET_PREVIEW_CALLBACK_WITH_BUFFER: {
273                        break;
274                    }
275
276                    case CameraActions.ADD_CALLBACK_BUFFER: {
277                        break;
278                    }
279
280                    case CameraActions.SET_PREVIEW_DISPLAY_ASYNC: {
281                        break;
282                    }
283
284                    case CameraActions.SET_PREVIEW_CALLBACK: {
285                        break;
286                    }
287
288                    case CameraActions.SET_ONE_SHOT_PREVIEW_CALLBACK: {
289                        break;
290                    }
291
292                    case CameraActions.SET_PARAMETERS: {
293                        break;
294                    }
295
296                    case CameraActions.GET_PARAMETERS: {
297                        break;
298                    }
299
300                    case CameraActions.REFRESH_PARAMETERS: {
301                        break;
302                    }*/
303
304                    case CameraActions.APPLY_SETTINGS: {
305                        CameraSettings settings = (CameraSettings) msg.obj;
306                        applyToRequest(settings);
307                        break;
308                    }
309
310                    /*case CameraActions.AUTO_FOCUS: {
311                        break;
312                    }
313
314                    case CameraActions.CANCEL_AUTO_FOCUS: {
315                        break;
316                    }
317
318                    case CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK: {
319                        break;
320                    }
321
322                    case CameraActions.SET_ZOOM_CHANGE_LISTENER: {
323                        break;
324                    }
325
326                    case CameraActions.SET_FACE_DETECTION_LISTENER: {
327                        break;
328                    }
329
330                    case CameraActions.START_FACE_DETECTION: {
331                        break;
332                    }
333
334                    case CameraActions.STOP_FACE_DETECTION: {
335                        break;
336                    }
337
338                    case CameraActions.SET_ERROR_CALLBACK: {
339                        break;
340                    }
341
342                    case CameraActions.ENABLE_SHUTTER_SOUND: {
343                        break;
344                    }
345
346                    case CameraActions.SET_DISPLAY_ORIENTATION: {
347                        break;
348                    }
349
350                    case CameraActions.CAPTURE_PHOTO: {
351                        break;
352                    }*/
353
354                    default: {
355                        // TODO: Rephrase once everything has been implemented
356                        throw new RuntimeException("Unimplemented CameraProxy message=" + msg.what);
357                    }
358                }
359            } catch (final Exception ex) {
360                if (msg.what != CameraActions.RELEASE && mCamera != null) {
361                    mCamera.close();
362                    mCamera = null;
363                } else if (mCamera == null) {
364                    if (msg.what == CameraActions.OPEN_CAMERA) {
365                        if (mOpenCallback != null) {
366                            mOpenCallback.onDeviceOpenFailure(mCameraIndex,
367                                    generateHistoryString(mCameraIndex));
368                        }
369                    } else {
370                        Log.w(TAG, "Cannot handle message " + msg.what + ", mCamera is null");
371                    }
372                    return;
373                }
374
375                if (ex instanceof RuntimeException) {
376                    post(new Runnable() {
377                        @Override
378                        public void run() {
379                            sCameraExceptionCallback.onCameraException((RuntimeException) ex);
380                        }});
381                }
382            }
383        }
384
385        public CameraSettings buildSettings(AndroidCamera2Capabilities caps) {
386            return new AndroidCamera2Settings(caps, mPreviewRequestBuilder, mPreviewSize,
387                    mPhotoSize);
388        }
389
390        // TODO: Finish implementation to add support for all settings
391        private void applyToRequest(CameraSettings settings) {
392            // TODO: If invoked when in PREVIEW_READY state, a new preview size will not take effect
393            AndroidCamera2Capabilities.IntegralStringifier intifier =
394                    mCameraProxy.getSpecializedCapabilities().getIntegralStringifier();
395            mPreviewSize = settings.getCurrentPreviewSize();
396            mPhotoSize = settings.getCurrentPhotoSize();
397            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
398                    intifier.intify(settings.getCurrentFocusMode()));
399            if (settings.getCurrentFlashMode() != CameraCapabilities.FlashMode.NO_FLASH) {
400                mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE,
401                        intifier.intify(settings.getCurrentFlashMode()));
402            }
403            if (settings.getCurrentSceneMode() != CameraCapabilities.SceneMode.NO_SCENE_MODE) {
404                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_SCENE_MODE,
405                        intifier.intify(settings.getCurrentSceneMode()));
406            }
407
408            // If we're already ready to preview, this doesn't regress our state
409            if (mCameraState.getState() != AndroidCamera2StateHolder.CAMERA_PREVIEW_READY) {
410                mCameraState.setState(AndroidCamera2StateHolder.CAMERA_CONFIGURED);
411            }
412        }
413
414        private void setPreviewTexture(SurfaceTexture surfaceTexture) {
415            // TODO: Must be called after providing a .*Settings populated with sizes
416            // TODO: We don't technically offer a selection of sizes tailored to SurfaceTextures!
417
418            // TODO: Handle this error condition with a callback or exception
419            if (mCameraState.getState() != AndroidCamera2StateHolder.CAMERA_CONFIGURED) {
420                Log.e(TAG, "Ignoring texture setting at inappropriate time");
421                return;
422            }
423
424            // Avoid initializing another capture session unless we absolutely have to
425            if (surfaceTexture == mPreviewTexture) {
426                Log.i(TAG, "Optimizing out redundant preview texture setting");
427                return;
428            }
429
430            if (mPreviewSession != null) {
431                closePreviewSession();
432            }
433
434            mPreviewTexture = surfaceTexture;
435            surfaceTexture.setDefaultBufferSize(mPreviewSize.width(), mPreviewSize.height());
436
437            if (mPreviewSurface != null) {
438                mPreviewRequestBuilder.removeTarget(mPreviewSurface);
439                mPreviewSurface.release();
440            }
441            mPreviewSurface = new Surface(surfaceTexture);
442            mPreviewRequestBuilder.addTarget(mPreviewSurface);
443
444            try {
445                mCamera.createCaptureSession(Arrays.asList(mPreviewSurface),
446                        mCameraPreviewStateListener, this);
447            } catch (CameraAccessException ex) {
448                Log.e(TAG, "Failed to create camera capture session", ex);
449            }
450        }
451
452        private void closePreviewSession() {
453            try {
454                mPreviewSession.abortCaptures();
455                mPreviewSession = null;
456            } catch (CameraAccessException ex) {
457                Log.e(TAG, "Failed to close existing camera capture session", ex);
458            }
459            mCameraState.setState(AndroidCamera2StateHolder.CAMERA_CONFIGURED);
460        }
461
462        private CameraDevice.StateListener mCameraDeviceStateListener =
463                new CameraDevice.StateListener() {
464            @Override
465            public void onOpened(CameraDevice camera) {
466                mCamera = camera;
467                if (mOpenCallback != null) {
468                    try {
469                        CameraCharacteristics props =
470                                mCameraManager.getCameraCharacteristics(mCameraId);
471                        mCameraProxy = new AndroidCamera2ProxyImpl(mCameraIndex, mCamera,
472                                    getCameraDeviceInfo().getCharacteristics(mCameraIndex), props);
473                        mPreviewRequestBuilder =
474                                camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
475                        mCameraState.setState(AndroidCamera2StateHolder.CAMERA_UNCONFIGURED);
476                        mOpenCallback.onCameraOpened(mCameraProxy);
477                    } catch (CameraAccessException ex) {
478                        mOpenCallback.onDeviceOpenFailure(mCameraIndex,
479                                generateHistoryString(mCameraIndex));
480                    }
481                }
482            }
483
484            @Override
485            public void onDisconnected(CameraDevice camera) {
486                Log.w(TAG, "Camera device '" + mCameraIndex + "' was disconnected");
487            }
488
489            @Override
490            public void onError(CameraDevice camera, int error) {
491                Log.e(TAG, "Camera device '" + mCameraIndex + "' encountered error code '" +
492                        error + '\'');
493                if (mOpenCallback != null) {
494                    mOpenCallback.onDeviceOpenFailure(mCameraIndex,
495                            generateHistoryString(mCameraIndex));
496                }
497            }};
498
499        private CameraCaptureSession.StateListener mCameraPreviewStateListener =
500                new CameraCaptureSession.StateListener() {
501            @Override
502            public void onConfigured(CameraCaptureSession session) {
503                mPreviewSession = session;
504                mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY);
505            }
506
507            @Override
508            public void onConfigureFailed(CameraCaptureSession session) {
509                // TODO: Invoke a callback
510                Log.e(TAG, "Failed to configure the camera for capture");
511            }};
512    }
513
514    private class AndroidCamera2ProxyImpl extends CameraAgent.CameraProxy {
515        private final int mCameraIndex;
516        private final CameraDevice mCamera;
517        private final CameraDeviceInfo.Characteristics mCharacteristics;
518        private final AndroidCamera2Capabilities mCapabilities;
519
520        public AndroidCamera2ProxyImpl(int cameraIndex, CameraDevice camera,
521                CameraDeviceInfo.Characteristics characteristics,
522                CameraCharacteristics properties) {
523            mCameraIndex = cameraIndex;
524            mCamera = camera;
525            mCharacteristics = characteristics;
526            mCapabilities = new AndroidCamera2Capabilities(properties);
527        }
528
529        // TODO: Implement
530        @Override
531        public android.hardware.Camera getCamera() { return null; }
532
533        @Override
534        public int getCameraId() {
535            return mCameraIndex;
536        }
537
538        @Override
539        public CameraDeviceInfo.Characteristics getCharacteristics() {
540            return mCharacteristics;
541        }
542
543        @Override
544        public CameraCapabilities getCapabilities() {
545            return mCapabilities;
546        }
547
548        private AndroidCamera2Capabilities getSpecializedCapabilities() {
549            return mCapabilities;
550        }
551
552        // TODO: Implement
553        @Override
554        public void setPreviewDataCallback(Handler handler, CameraPreviewDataCallback cb) {}
555
556        // TODO: Implement
557        @Override
558        public void setOneShotPreviewCallback(Handler handler, CameraPreviewDataCallback cb) {}
559
560        // TODO: Implement
561        @Override
562        public void setPreviewDataCallbackWithBuffer(Handler handler, CameraPreviewDataCallback cb)
563                {}
564
565        // TODO: Implement
566        @Override
567        public void autoFocus(Handler handler, CameraAFCallback cb) {}
568
569        // TODO: Remove this method override once we handle this message
570        @Override
571        public void cancelAutoFocus() {}
572
573        // TODO: Implement
574        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
575        @Override
576        public void setAutoFocusMoveCallback(Handler handler, CameraAFMoveCallback cb) {}
577
578        // TODO: Implement
579        @Override
580        public void takePicture(Handler handler,
581                                CameraShutterCallback shutter,
582                                CameraPictureCallback raw,
583                                CameraPictureCallback postview,
584                                CameraPictureCallback jpeg) {}
585
586        // TODO: Remove this method override once we handle the message
587        @Override
588        public void setDisplayOrientation(int degrees) {}
589
590        // TODO: Implement
591        @Override
592        public void setZoomChangeListener(android.hardware.Camera.OnZoomChangeListener listener) {}
593
594        // TODO: Implement
595        @Override
596        public void setFaceDetectionCallback(Handler handler, CameraFaceDetectionCallback callback)
597                {}
598
599        // TODO: Remove this method override once we handle this message
600        @Override
601        public void startFaceDetection() {}
602
603        // TODO: Implement
604        @Override
605        public void setErrorCallback(Handler handler, CameraErrorCallback cb) {}
606
607        // TODO: Implement
608        @Override
609        public void setParameters(android.hardware.Camera.Parameters params) {}
610
611        // TODO: Implement
612        @Override
613        public android.hardware.Camera.Parameters getParameters() { return null; }
614
615        @Override
616        public CameraSettings getSettings() {
617            return mCameraHandler.buildSettings(mCapabilities);
618        }
619
620        @Override
621        public boolean applySettings(CameraSettings settings) {
622            return applySettingsHelper(settings, AndroidCamera2StateHolder.CAMERA_UNCONFIGURED |
623                    AndroidCamera2StateHolder.CAMERA_CONFIGURED |
624                    AndroidCamera2StateHolder.CAMERA_PREVIEW_READY);
625        }
626
627        // TODO: Implement
628        @Override
629        public String dumpDeviceSettings() { return null; }
630
631        @Override
632        public Handler getCameraHandler() {
633            return AndroidCamera2AgentImpl.this.getCameraHandler();
634        }
635
636        @Override
637        public DispatchThread getDispatchThread() {
638            return AndroidCamera2AgentImpl.this.getDispatchThread();
639        }
640
641        @Override
642        public CameraStateHolder getCameraState() {
643            return mCameraState;
644        }
645    }
646
647    private static class AndroidCamera2StateHolder extends CameraStateHolder {
648        // Usage flow: openCamera() -> applySettings() -> setPreviewTexture() -> startPreview()
649        /* Camera states */
650        /** No camera device is opened. */
651        public static final int CAMERA_UNOPENED = 1;
652        /** A camera is opened, but no settings have been provided. */
653        public static final int CAMERA_UNCONFIGURED = 2;
654        /** The open camera has been configured by providing it with settings. */
655        public static final int CAMERA_CONFIGURED = 3;
656        /** A capture session is ready to stream a preview, but still has no repeating request. */
657        public static final int CAMERA_PREVIEW_READY = 4;
658        /** A preview is currently being streamed. */
659        public static final int CAMERA_PREVIEW_ACTIVE = 5;
660
661        public AndroidCamera2StateHolder() {
662            this(CAMERA_UNOPENED);
663        }
664
665        public AndroidCamera2StateHolder(int state) {
666            super(state);
667        }
668    }
669
670    private static class AndroidCamera2DeviceInfo implements CameraDeviceInfo {
671        private final CameraManager mCameraManager;
672        private final String[] mCameraIds;
673        private final int mNumberOfCameras;
674        private final int mFirstBackCameraId;
675        private final int mFirstFrontCameraId;
676
677        public AndroidCamera2DeviceInfo(CameraManager cameraManager,
678                                        String[] cameraIds, int numberOfCameras) {
679            mCameraManager = cameraManager;
680            mCameraIds = cameraIds;
681            mNumberOfCameras = numberOfCameras;
682
683            int firstBackId = NO_DEVICE;
684            int firstFrontId = NO_DEVICE;
685            for (int id = 0; id < cameraIds.length; ++id) {
686                try {
687                    int lensDirection = cameraManager.getCameraCharacteristics(cameraIds[id])
688                            .get(CameraCharacteristics.LENS_FACING);
689                    if (firstBackId == NO_DEVICE &&
690                            lensDirection == CameraCharacteristics.LENS_FACING_BACK) {
691                        firstBackId = id;
692                    }
693                    if (firstFrontId == NO_DEVICE &&
694                            lensDirection == CameraCharacteristics.LENS_FACING_FRONT) {
695                        firstFrontId = id;
696                    }
697                } catch (CameraAccessException ex) {
698                    Log.w(TAG, "Couldn't get characteristics of camera '" + id + "'", ex);
699                }
700            }
701            mFirstBackCameraId = firstBackId;
702            mFirstFrontCameraId = firstFrontId;
703        }
704
705        @Override
706        public Characteristics getCharacteristics(int cameraId) {
707            String actualId = mCameraIds[cameraId];
708            try {
709                CameraCharacteristics info = mCameraManager.getCameraCharacteristics(actualId);
710                return new AndroidCharacteristics2(info);
711            } catch (CameraAccessException ex) {
712                return null;
713            }
714        }
715
716        @Override
717        public int getNumberOfCameras() {
718            return mNumberOfCameras;
719        }
720
721        @Override
722        public int getFirstBackCameraId() {
723            return mFirstBackCameraId;
724        }
725
726        @Override
727        public int getFirstFrontCameraId() {
728            return mFirstFrontCameraId;
729        }
730
731        private static class AndroidCharacteristics2 implements Characteristics {
732            private CameraCharacteristics mCameraInfo;
733
734            AndroidCharacteristics2(CameraCharacteristics cameraInfo) {
735                mCameraInfo = cameraInfo;
736            }
737
738            @Override
739            public boolean isFacingBack() {
740                return mCameraInfo.get(CameraCharacteristics.LENS_FACING)
741                        .equals(CameraCharacteristics.LENS_FACING_BACK);
742            }
743
744            @Override
745            public boolean isFacingFront() {
746                return mCameraInfo.get(CameraCharacteristics.LENS_FACING)
747                        .equals(CameraCharacteristics.LENS_FACING_FRONT);
748            }
749
750            @Override
751            public int getSensorOrientation() {
752                return mCameraInfo.get(CameraCharacteristics.SENSOR_ORIENTATION);
753            }
754
755            @Override
756            public boolean canDisableShutterSound() {
757                // The new API doesn't support this operation, so don't encourage people to try it.
758                // TODO: What kind of assumptions have callers made about this result's meaning?
759                return false;
760            }
761        }
762    }
763
764    private static final CameraExceptionCallback sCameraExceptionCallback =
765            new CameraExceptionCallback() {
766                @Override
767                public synchronized void onCameraException(RuntimeException e) {
768                    throw e;
769                }
770            };
771}
772