AndroidCameraAgentImpl.java revision 2569329d6cff25bfe9941df539df14a0aeb4c4f4
1/*
2 * Copyright (C) 2013 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.graphics.SurfaceTexture;
21import android.hardware.Camera;
22import android.hardware.Camera.AutoFocusCallback;
23import android.hardware.Camera.AutoFocusMoveCallback;
24import android.hardware.Camera.ErrorCallback;
25import android.hardware.Camera.FaceDetectionListener;
26import android.hardware.Camera.OnZoomChangeListener;
27import android.hardware.Camera.Parameters;
28import android.hardware.Camera.PictureCallback;
29import android.hardware.Camera.PreviewCallback;
30import android.hardware.Camera.ShutterCallback;
31import android.os.Build;
32import android.os.Handler;
33import android.os.HandlerThread;
34import android.os.Looper;
35import android.os.Message;
36import android.view.SurfaceHolder;
37
38import com.android.ex.camera2.portability.debug.Log;
39
40import java.io.IOException;
41import java.util.Collections;
42import java.util.List;
43import java.util.StringTokenizer;
44
45/**
46 * A class to implement {@link CameraAgent} of the Android camera framework.
47 */
48class AndroidCameraAgentImpl extends CameraAgent {
49    private static final Log.Tag TAG = new Log.Tag("AndCamAgntImp");
50
51    private CameraDeviceInfo.Characteristics mCharacteristics;
52    private AndroidCameraCapabilities mCapabilities;
53
54    private final CameraHandler mCameraHandler;
55    private final HandlerThread mCameraHandlerThread;
56    private final CameraStateHolder mCameraState;
57    private final DispatchThread mDispatchThread;
58
59    private Handler mCameraExceptionCallbackHandler;
60    private CameraExceptionCallback mCameraExceptionCallback =
61        new CameraExceptionCallback() {
62            @Override
63            public void onCameraException(RuntimeException e) {
64                throw e;
65            }
66        };
67
68    AndroidCameraAgentImpl() {
69        mCameraHandlerThread = new HandlerThread("Camera Handler Thread");
70        mCameraHandlerThread.start();
71        mCameraHandler = new CameraHandler(mCameraHandlerThread.getLooper());
72        mCameraExceptionCallbackHandler = mCameraHandler;
73        mCameraState = new AndroidCameraStateHolder();
74        mDispatchThread = new DispatchThread(mCameraHandler, mCameraHandlerThread);
75        mDispatchThread.start();
76    }
77
78    @Override
79    public void setCameraDefaultExceptionCallback(CameraExceptionCallback callback,
80            Handler handler) {
81        synchronized (mCameraExceptionCallback) {
82            mCameraExceptionCallback = callback;
83            mCameraExceptionCallbackHandler = handler;
84        }
85    }
86
87    @Override
88    public void recycle() {
89        closeCamera(null, true);
90        mDispatchThread.end();
91    }
92
93    @Override
94    public CameraDeviceInfo getCameraDeviceInfo() {
95        return AndroidCameraDeviceInfo.create();
96    }
97
98    @Override
99    protected Handler getCameraHandler() {
100        return mCameraHandler;
101    }
102
103    @Override
104    protected DispatchThread getDispatchThread() {
105        return mDispatchThread;
106    }
107
108    private static class AndroidCameraDeviceInfo implements CameraDeviceInfo {
109        private final Camera.CameraInfo[] mCameraInfos;
110        private final int mNumberOfCameras;
111        private final int mFirstBackCameraId;
112        private final int mFirstFrontCameraId;
113
114        private AndroidCameraDeviceInfo(Camera.CameraInfo[] info, int numberOfCameras,
115                int firstBackCameraId, int firstFrontCameraId) {
116
117            mCameraInfos = info;
118            mNumberOfCameras = numberOfCameras;
119            mFirstBackCameraId = firstBackCameraId;
120            mFirstFrontCameraId = firstFrontCameraId;
121        }
122
123        public static AndroidCameraDeviceInfo create() {
124            int numberOfCameras;
125            Camera.CameraInfo[] cameraInfos;
126            try {
127                numberOfCameras = Camera.getNumberOfCameras();
128                cameraInfos = new Camera.CameraInfo[numberOfCameras];
129                for (int i = 0; i < numberOfCameras; i++) {
130                    cameraInfos[i] = new Camera.CameraInfo();
131                    Camera.getCameraInfo(i, cameraInfos[i]);
132                }
133            } catch (RuntimeException ex) {
134                Log.e(TAG, "Exception while creating CameraDeviceInfo", ex);
135                return null;
136            }
137
138            int firstFront = NO_DEVICE;
139            int firstBack = NO_DEVICE;
140            // Get the first (smallest) back and first front camera id.
141            for (int i = numberOfCameras - 1; i >= 0; i--) {
142                if (cameraInfos[i].facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
143                    firstBack = i;
144                } else {
145                    if (cameraInfos[i].facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
146                        firstFront = i;
147                    }
148                }
149            }
150
151            return new AndroidCameraDeviceInfo(cameraInfos, numberOfCameras, firstBack, firstFront);
152        }
153
154        @Override
155        public Characteristics getCharacteristics(int cameraId) {
156            Camera.CameraInfo info = mCameraInfos[cameraId];
157            if (info != null) {
158                return new AndroidCharacteristics(info);
159            } else {
160                return null;
161            }
162        }
163
164        @Override
165        public int getNumberOfCameras() {
166            return mNumberOfCameras;
167        }
168
169        @Override
170        public int getFirstBackCameraId() {
171            return mFirstBackCameraId;
172        }
173
174        @Override
175        public int getFirstFrontCameraId() {
176            return mFirstFrontCameraId;
177        }
178
179        private static class AndroidCharacteristics extends Characteristics {
180            private Camera.CameraInfo mCameraInfo;
181
182            AndroidCharacteristics(Camera.CameraInfo cameraInfo) {
183                mCameraInfo = cameraInfo;
184            }
185
186            @Override
187            public boolean isFacingBack() {
188                return mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK;
189            }
190
191            @Override
192            public boolean isFacingFront() {
193                return mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT;
194            }
195
196            @Override
197            public int getSensorOrientation() {
198                return mCameraInfo.orientation;
199            }
200
201            @Override
202            public boolean canDisableShutterSound() {
203                return mCameraInfo.canDisableShutterSound;
204            }
205        }
206    }
207
208    private static class ParametersCache {
209        private Parameters mParameters;
210        private Camera mCamera;
211
212        public ParametersCache(Camera camera) {
213            mCamera = camera;
214        }
215
216        public synchronized void invalidate() {
217            mParameters = null;
218        }
219
220        /**
221         * Access parameters from the cache. If cache is empty, block by
222         * retrieving parameters directly from Camera, but if cache is present,
223         * returns immediately.
224         */
225        public synchronized Parameters getBlocking() {
226            if (mParameters == null) {
227                mParameters = mCamera.getParameters();
228            }
229            return mParameters;
230        }
231    }
232
233    /**
234     * The handler on which the actual camera operations happen.
235     */
236    private class CameraHandler extends HistoryHandler {
237
238        private Camera mCamera;
239        private int mCameraId;
240        private ParametersCache mParameterCache;
241
242        private class CaptureCallbacks {
243            public final ShutterCallback mShutter;
244            public final PictureCallback mRaw;
245            public final PictureCallback mPostView;
246            public final PictureCallback mJpeg;
247
248            CaptureCallbacks(ShutterCallback shutter, PictureCallback raw, PictureCallback postView,
249                    PictureCallback jpeg) {
250                mShutter = shutter;
251                mRaw = raw;
252                mPostView = postView;
253                mJpeg = jpeg;
254            }
255        }
256
257        CameraHandler(Looper looper) {
258            super(looper);
259        }
260
261        private void startFaceDetection() {
262            mCamera.startFaceDetection();
263        }
264
265        private void stopFaceDetection() {
266            mCamera.stopFaceDetection();
267        }
268
269        private void setFaceDetectionListener(FaceDetectionListener listener) {
270            mCamera.setFaceDetectionListener(listener);
271        }
272
273        private void setPreviewTexture(Object surfaceTexture) {
274            try {
275                mCamera.setPreviewTexture((SurfaceTexture) surfaceTexture);
276            } catch (IOException e) {
277                Log.e(TAG, "Could not set preview texture", e);
278            }
279        }
280
281        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
282        private void enableShutterSound(boolean enable) {
283            mCamera.enableShutterSound(enable);
284        }
285
286        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
287        private void setAutoFocusMoveCallback(
288                android.hardware.Camera camera, Object cb) {
289            try {
290                camera.setAutoFocusMoveCallback((AutoFocusMoveCallback) cb);
291            } catch (RuntimeException ex) {
292                Log.w(TAG, ex.getMessage());
293            }
294        }
295
296        private void capture(final CaptureCallbacks cb) {
297            try {
298                mCamera.takePicture(cb.mShutter, cb.mRaw, cb.mPostView, cb.mJpeg);
299            } catch (RuntimeException e) {
300                // TODO: output camera state and focus state for debugging.
301                Log.e(TAG, "take picture failed.");
302                throw e;
303            }
304        }
305
306        public void requestTakePicture(
307                final ShutterCallback shutter,
308                final PictureCallback raw,
309                final PictureCallback postView,
310                final PictureCallback jpeg) {
311            final CaptureCallbacks callbacks = new CaptureCallbacks(shutter, raw, postView, jpeg);
312            obtainMessage(CameraActions.CAPTURE_PHOTO, callbacks).sendToTarget();
313        }
314
315        /**
316         * This method does not deal with the API level check.  Everyone should
317         * check first for supported operations before sending message to this handler.
318         */
319        @Override
320        public void handleMessage(final Message msg) {
321            super.handleMessage(msg);
322            try {
323                switch (msg.what) {
324                    case CameraActions.OPEN_CAMERA: {
325                        final CameraOpenCallback openCallback = (CameraOpenCallback) msg.obj;
326                        final int cameraId = msg.arg1;
327                        if (mCameraState.getState() != AndroidCameraStateHolder.CAMERA_UNOPENED) {
328                            openCallback.onDeviceOpenedAlready(cameraId, generateHistoryString(cameraId));
329                            break;
330                        }
331
332                        Log.i(TAG, "Opening camera " + cameraId + " with camera1 API");
333                        mCamera = android.hardware.Camera.open(cameraId);
334                        if (mCamera != null) {
335                            mCameraId = cameraId;
336                            mParameterCache = new ParametersCache(mCamera);
337
338                            mCharacteristics =
339                                    AndroidCameraDeviceInfo.create().getCharacteristics(cameraId);
340                            mCapabilities = new AndroidCameraCapabilities(
341                                    mParameterCache.getBlocking());
342
343                            mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE);
344                            if (openCallback != null) {
345                                openCallback.onCameraOpened(
346                                        new AndroidCameraProxyImpl(cameraId, mCamera,
347                                                mCharacteristics, mCapabilities));
348                            }
349                        } else {
350                            if (openCallback != null) {
351                                openCallback.onDeviceOpenFailure(cameraId, generateHistoryString(cameraId));
352                            }
353                        }
354                        break;
355                    }
356
357                    case CameraActions.RELEASE: {
358                        if (mCamera != null) {
359                            mCamera.release();
360                            mCameraState.setState(AndroidCameraStateHolder.CAMERA_UNOPENED);
361                            mCamera = null;
362                        } else {
363                            Log.w(TAG, "Releasing camera without any camera opened.");
364                        }
365                        break;
366                    }
367
368                    case CameraActions.RECONNECT: {
369                        final CameraOpenCallbackForward cbForward =
370                                (CameraOpenCallbackForward) msg.obj;
371                        final int cameraId = msg.arg1;
372                        try {
373                            mCamera.reconnect();
374                        } catch (IOException ex) {
375                            if (cbForward != null) {
376                                cbForward.onReconnectionFailure(AndroidCameraAgentImpl.this,
377                                        generateHistoryString(mCameraId));
378                            }
379                            break;
380                        }
381
382                        mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE);
383                        if (cbForward != null) {
384                            cbForward.onCameraOpened(
385                                    new AndroidCameraProxyImpl(cameraId, mCamera, mCharacteristics,
386                                            mCapabilities));
387                        }
388                        break;
389                    }
390
391                    case CameraActions.UNLOCK: {
392                        mCamera.unlock();
393                        mCameraState.setState(AndroidCameraStateHolder.CAMERA_UNLOCKED);
394                        break;
395                    }
396
397                    case CameraActions.LOCK: {
398                        mCamera.lock();
399                        mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE);
400                        break;
401                    }
402
403                    // TODO: Lock the CameraSettings object's sizes
404                    case CameraActions.SET_PREVIEW_TEXTURE_ASYNC: {
405                        setPreviewTexture(msg.obj);
406                        break;
407                    }
408
409                    case CameraActions.SET_PREVIEW_DISPLAY_ASYNC: {
410                        try {
411                            mCamera.setPreviewDisplay((SurfaceHolder) msg.obj);
412                        } catch (IOException e) {
413                            throw new RuntimeException(e);
414                        }
415                        break;
416                    }
417
418                    case CameraActions.START_PREVIEW_ASYNC: {
419                        final CameraStartPreviewCallbackForward cbForward =
420                            (CameraStartPreviewCallbackForward) msg.obj;
421                        mCamera.startPreview();
422                        if (cbForward != null) {
423                            cbForward.onPreviewStarted();
424                        }
425                        break;
426                    }
427
428                    // TODO: Unlock the CameraSettings object's sizes
429                    case CameraActions.STOP_PREVIEW: {
430                        mCamera.stopPreview();
431                        break;
432                    }
433
434                    case CameraActions.SET_PREVIEW_CALLBACK_WITH_BUFFER: {
435                        mCamera.setPreviewCallbackWithBuffer((PreviewCallback) msg.obj);
436                        break;
437                    }
438
439                    case CameraActions.SET_ONE_SHOT_PREVIEW_CALLBACK: {
440                        mCamera.setOneShotPreviewCallback((PreviewCallback) msg.obj);
441                        break;
442                    }
443
444                    case CameraActions.ADD_CALLBACK_BUFFER: {
445                        mCamera.addCallbackBuffer((byte[]) msg.obj);
446                        break;
447                    }
448
449                    case CameraActions.AUTO_FOCUS: {
450                        mCameraState.setState(AndroidCameraStateHolder.CAMERA_FOCUSING);
451                        mCamera.autoFocus((AutoFocusCallback) msg.obj);
452                        break;
453                    }
454
455                    case CameraActions.CANCEL_AUTO_FOCUS: {
456                        mCamera.cancelAutoFocus();
457                        mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE);
458                        break;
459                    }
460
461                    case CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK: {
462                        setAutoFocusMoveCallback(mCamera, msg.obj);
463                        break;
464                    }
465
466                    case CameraActions.SET_DISPLAY_ORIENTATION: {
467                        // Update preview orientation
468                        mCamera.setDisplayOrientation(
469                                mCharacteristics.getPreviewOrientation(msg.arg1));
470                        // Only set the JPEG capture orientation if requested to do so; otherwise,
471                        // capture in the sensor's physical orientation
472                        Parameters parameters = mParameterCache.getBlocking();
473                        parameters.setRotation(
474                                msg.arg2 > 0 ? mCharacteristics.getJpegOrientation(msg.arg1) : 0);
475                        mCamera.setParameters(parameters);
476                        break;
477                    }
478
479                    case CameraActions.SET_ZOOM_CHANGE_LISTENER: {
480                        mCamera.setZoomChangeListener((OnZoomChangeListener) msg.obj);
481                        break;
482                    }
483
484                    case CameraActions.SET_FACE_DETECTION_LISTENER: {
485                        setFaceDetectionListener((FaceDetectionListener) msg.obj);
486                        break;
487                    }
488
489                    case CameraActions.START_FACE_DETECTION: {
490                        startFaceDetection();
491                        break;
492                    }
493
494                    case CameraActions.STOP_FACE_DETECTION: {
495                        stopFaceDetection();
496                        break;
497                    }
498
499                    case CameraActions.SET_ERROR_CALLBACK: {
500                        mCamera.setErrorCallback((ErrorCallback) msg.obj);
501                        break;
502                    }
503
504                    case CameraActions.APPLY_SETTINGS: {
505                        Parameters parameters = mParameterCache.getBlocking();
506                        CameraSettings settings = (CameraSettings) msg.obj;
507                        applySettingsToParameters(settings, parameters);
508                        mCamera.setParameters(parameters);
509                        mParameterCache.invalidate();
510                        break;
511                    }
512
513                    case CameraActions.SET_PARAMETERS: {
514                        Parameters parameters = mParameterCache.getBlocking();
515                        parameters.unflatten((String) msg.obj);
516                        mCamera.setParameters(parameters);
517                        mParameterCache.invalidate();
518                        break;
519                    }
520
521                    case CameraActions.GET_PARAMETERS: {
522                        Parameters[] parametersHolder = (Parameters[]) msg.obj;
523                        Parameters parameters = mParameterCache.getBlocking();
524                        parametersHolder[0] = parameters;
525                        break;
526                    }
527
528                    case CameraActions.SET_PREVIEW_CALLBACK: {
529                        mCamera.setPreviewCallback((PreviewCallback) msg.obj);
530                        break;
531                    }
532
533                    case CameraActions.ENABLE_SHUTTER_SOUND: {
534                        enableShutterSound((msg.arg1 == 1) ? true : false);
535                        break;
536                    }
537
538                    case CameraActions.REFRESH_PARAMETERS: {
539                        mParameterCache.invalidate();;
540                        break;
541                    }
542
543                    case CameraActions.CAPTURE_PHOTO: {
544                        mCameraState.setState(AndroidCameraStateHolder.CAMERA_CAPTURING);
545                        capture((CaptureCallbacks) msg.obj);
546                        break;
547                    }
548
549                    default: {
550                        throw new RuntimeException("Invalid CameraProxy message=" + msg.what);
551                    }
552                }
553            } catch (final RuntimeException e) {
554                if (msg.what != CameraActions.RELEASE && mCamera != null) {
555                    try {
556                        mCamera.release();
557                        mCameraState.setState(AndroidCameraStateHolder.CAMERA_UNOPENED);
558                    } catch (Exception ex) {
559                        Log.e(TAG, "Fail to release the camera.");
560                    }
561                    mCamera = null;
562                } else {
563                    if (mCamera == null) {
564                        if (msg.what == CameraActions.OPEN_CAMERA) {
565                            final int cameraId = msg.arg1;
566                            if (msg.obj != null) {
567                                ((CameraOpenCallback) msg.obj).onDeviceOpenFailure(
568                                        msg.arg1, generateHistoryString(cameraId));
569                            }
570                        } else {
571                            Log.w(TAG, "Cannot handle message " + msg.what + ", mCamera is null.");
572                        }
573                        return;
574                    }
575                }
576                synchronized (mCameraExceptionCallback) {
577                    mCameraExceptionCallbackHandler.post(new Runnable() {
578                        @Override
579                        public void run() {
580                                mCameraExceptionCallback.onCameraException(e);
581                            }
582                        });
583                }
584            }
585        }
586
587        private void applySettingsToParameters(final CameraSettings settings,
588                final Parameters parameters) {
589            final CameraCapabilities.Stringifier stringifier = mCapabilities.getStringifier();
590            Size photoSize = settings.getCurrentPhotoSize();
591            parameters.setPictureSize(photoSize.width(), photoSize.height());
592            Size previewSize = settings.getCurrentPreviewSize();
593            parameters.setPreviewSize(previewSize.width(), previewSize.height());
594            if (settings.getPreviewFrameRate() == -1) {
595                parameters.setPreviewFpsRange(settings.getPreviewFpsRangeMin(),
596                        settings.getPreviewFpsRangeMax());
597            } else {
598                parameters.setPreviewFrameRate(settings.getPreviewFrameRate());
599            }
600            parameters.setPreviewFormat(settings.getCurrentPreviewFormat());
601            parameters.setJpegQuality(settings.getPhotoJpegCompressionQuality());
602            if (mCapabilities.supports(CameraCapabilities.Feature.ZOOM)) {
603                parameters.setZoom(zoomRatioToIndex(settings.getCurrentZoomRatio(),
604                        parameters.getZoomRatios()));
605            }
606            parameters.setExposureCompensation(settings.getExposureCompensationIndex());
607            if (mCapabilities.supports(CameraCapabilities.Feature.AUTO_EXPOSURE_LOCK)) {
608                parameters.setAutoExposureLock(settings.isAutoExposureLocked());
609            }
610            parameters.setFocusMode(stringifier.stringify(settings.getCurrentFocusMode()));
611            if (mCapabilities.supports(CameraCapabilities.Feature.AUTO_WHITE_BALANCE_LOCK)) {
612                parameters.setAutoWhiteBalanceLock(settings.isAutoWhiteBalanceLocked());
613            }
614            if (mCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA)) {
615                if (settings.getFocusAreas().size() != 0) {
616                    parameters.setFocusAreas(settings.getFocusAreas());
617                }
618            }
619            if (mCapabilities.supports(CameraCapabilities.Feature.METERING_AREA)) {
620                if (settings.getMeteringAreas().size() != 0) {
621                    parameters.setMeteringAreas(settings.getMeteringAreas());
622                }
623            }
624            if (settings.getCurrentFlashMode() != CameraCapabilities.FlashMode.NO_FLASH) {
625                parameters.setFlashMode(stringifier.stringify(settings.getCurrentFlashMode()));
626            }
627            if (settings.getCurrentSceneMode() != CameraCapabilities.SceneMode.NO_SCENE_MODE) {
628                if (settings.getCurrentSceneMode() != null) {
629                    parameters
630                            .setSceneMode(stringifier.stringify(settings.getCurrentSceneMode()));
631                }
632            }
633            parameters.setRecordingHint(settings.isRecordingHintEnabled());
634            Size jpegThumbSize = settings.getExifThumbnailSize();
635            parameters.setJpegThumbnailSize(jpegThumbSize.width(), jpegThumbSize.height());
636            parameters.setPictureFormat(settings.getCurrentPhotoFormat());
637
638            CameraSettings.GpsData gpsData = settings.getGpsData();
639            if (gpsData == null) {
640                parameters.removeGpsData();
641            } else {
642                parameters.setGpsTimestamp(gpsData.timeStamp);
643                if (gpsData.processingMethod != null) {
644                    // It's a hack since we always use GPS time stamp but does
645                    // not use other fields sometimes. Setting processing
646                    // method to null means the other fields should not be used.
647                    parameters.setGpsAltitude(gpsData.altitude);
648                    parameters.setGpsLatitude(gpsData.latitude);
649                    parameters.setGpsLongitude(gpsData.longitude);
650                    parameters.setGpsProcessingMethod(gpsData.processingMethod);
651                }
652            }
653
654        }
655
656        /**
657         * @param ratio Desired zoom ratio, in [1.0f,+Inf).
658         * @param percentages Available zoom ratios, as percentages.
659         * @return Index of the closest corresponding ratio, rounded up toward
660         *         that of the maximum available ratio.
661         */
662        private int zoomRatioToIndex(float ratio, List<Integer> percentages) {
663            int percent = (int) (ratio * AndroidCameraCapabilities.ZOOM_MULTIPLIER);
664            int index = Collections.binarySearch(percentages, percent);
665            if (index >= 0) {
666                // Found the desired ratio in the supported list
667                return index;
668            } else {
669                // Didn't find an exact match. Where would it have been?
670                index = -(index + 1);
671                if (index == percentages.size()) {
672                    // Put it back in bounds by setting to the maximum allowable zoom
673                    --index;
674                }
675                return index;
676            }
677        }
678    }
679
680    /**
681     * A class which implements {@link CameraAgent.CameraProxy} and
682     * camera handler thread.
683     */
684    private class AndroidCameraProxyImpl extends CameraAgent.CameraProxy {
685        private final int mCameraId;
686        /* TODO: remove this Camera instance. */
687        private final Camera mCamera;
688        private final CameraDeviceInfo.Characteristics mCharacteristics;
689        private final AndroidCameraCapabilities mCapabilities;
690
691        private AndroidCameraProxyImpl(int cameraId, Camera camera,
692                CameraDeviceInfo.Characteristics characteristics,
693                AndroidCameraCapabilities capabilities) {
694            mCamera = camera;
695            mCameraId = cameraId;
696            mCharacteristics = characteristics;
697            mCapabilities = capabilities;
698        }
699
700        @Deprecated
701        @Override
702        public android.hardware.Camera getCamera() {
703            return mCamera;
704        }
705
706        @Override
707        public int getCameraId() {
708            return mCameraId;
709        }
710
711        @Override
712        public CameraDeviceInfo.Characteristics getCharacteristics() {
713            return mCharacteristics;
714        }
715
716        @Override
717        public CameraCapabilities getCapabilities() {
718            return new AndroidCameraCapabilities(mCapabilities);
719        }
720
721        @Override
722        public void setPreviewDataCallback(
723                final Handler handler, final CameraPreviewDataCallback cb) {
724            mDispatchThread.runJob(new Runnable() {
725                @Override
726                public void run() {
727                    mCameraHandler.obtainMessage(CameraActions.SET_PREVIEW_CALLBACK,
728                            PreviewCallbackForward.getNewInstance(
729                                    handler, AndroidCameraProxyImpl.this, cb))
730                            .sendToTarget();
731                }
732            });
733        }
734
735        @Override
736        public void setOneShotPreviewCallback(final Handler handler,
737                final CameraPreviewDataCallback cb) {
738            mDispatchThread.runJob(new Runnable() {
739                @Override
740                public void run() {
741                    mCameraHandler.obtainMessage(CameraActions.SET_ONE_SHOT_PREVIEW_CALLBACK,
742                            PreviewCallbackForward
743                                    .getNewInstance(handler, AndroidCameraProxyImpl.this, cb))
744                            .sendToTarget();
745                }
746            });
747        }
748
749        @Override
750        public void setPreviewDataCallbackWithBuffer(
751                final Handler handler, final CameraPreviewDataCallback cb) {
752            mDispatchThread.runJob(new Runnable() {
753                @Override
754                public void run() {
755                    mCameraHandler.obtainMessage(CameraActions.SET_PREVIEW_CALLBACK_WITH_BUFFER,
756                            PreviewCallbackForward
757                                    .getNewInstance(handler, AndroidCameraProxyImpl.this, cb))
758                            .sendToTarget();
759                }
760            });
761        }
762
763        @Override
764        public void autoFocus(final Handler handler, final CameraAFCallback cb) {
765            final AutoFocusCallback afCallback = new AutoFocusCallback() {
766                @Override
767                public void onAutoFocus(final boolean b, Camera camera) {
768                    if (mCameraState.getState() != AndroidCameraStateHolder.CAMERA_FOCUSING) {
769                        Log.w(TAG, "onAutoFocus callback returning when not focusing");
770                    } else {
771                        mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE);
772                    }
773                    handler.post(new Runnable() {
774                        @Override
775                        public void run() {
776                            cb.onAutoFocus(b, AndroidCameraProxyImpl.this);
777                        }
778                    });
779                }
780            };
781            mDispatchThread.runJob(new Runnable() {
782                @Override
783                public void run() {
784                    mCameraState.waitForStates(AndroidCameraStateHolder.CAMERA_IDLE);
785                    mCameraHandler.obtainMessage(CameraActions.AUTO_FOCUS, afCallback)
786                            .sendToTarget();
787                }
788            });
789        }
790
791        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
792        @Override
793        public void setAutoFocusMoveCallback(
794                final Handler handler, final CameraAFMoveCallback cb) {
795            mDispatchThread.runJob(new Runnable() {
796                @Override
797                public void run() {
798                    mCameraHandler.obtainMessage(CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK,
799                            AFMoveCallbackForward.getNewInstance(
800                                    handler, AndroidCameraProxyImpl.this, cb))
801                            .sendToTarget();
802                }
803            });
804        }
805
806        @Override
807        public void takePicture(
808                final Handler handler, final CameraShutterCallback shutter,
809                final CameraPictureCallback raw, final CameraPictureCallback post,
810                final CameraPictureCallback jpeg) {
811            final PictureCallback jpegCallback = new PictureCallback() {
812                @Override
813                public void onPictureTaken(final byte[] data, Camera camera) {
814                    if (mCameraState.getState() != AndroidCameraStateHolder.CAMERA_CAPTURING) {
815                        Log.w(TAG, "picture callback returning when not capturing");
816                    } else {
817                        mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE);
818                    }
819                    handler.post(new Runnable() {
820                        @Override
821                        public void run() {
822                            jpeg.onPictureTaken(data, AndroidCameraProxyImpl.this);
823                        }
824                    });
825                }
826            };
827
828            mDispatchThread.runJob(new Runnable() {
829                @Override
830                public void run() {
831                    mCameraState.waitForStates(AndroidCameraStateHolder.CAMERA_IDLE |
832                            AndroidCameraStateHolder.CAMERA_UNLOCKED);
833                    mCameraHandler.requestTakePicture(ShutterCallbackForward
834                                    .getNewInstance(handler, AndroidCameraProxyImpl.this, shutter),
835                            PictureCallbackForward
836                                    .getNewInstance(handler, AndroidCameraProxyImpl.this, raw),
837                            PictureCallbackForward
838                                    .getNewInstance(handler, AndroidCameraProxyImpl.this, post),
839                            jpegCallback
840                    );
841                }
842            });
843        }
844
845        @Override
846        public void setZoomChangeListener(final OnZoomChangeListener listener) {
847            mDispatchThread.runJob(new Runnable() {
848                @Override
849                public void run() {
850                    mCameraHandler.obtainMessage(CameraActions.SET_ZOOM_CHANGE_LISTENER, listener)
851                            .sendToTarget();
852                }
853            });
854        }
855
856        @Override
857        public void setFaceDetectionCallback(final Handler handler,
858                final CameraFaceDetectionCallback cb) {
859            mDispatchThread.runJob(new Runnable() {
860                @Override
861                public void run() {
862                    mCameraHandler.obtainMessage(CameraActions.SET_FACE_DETECTION_LISTENER,
863                            FaceDetectionCallbackForward
864                                    .getNewInstance(handler, AndroidCameraProxyImpl.this, cb))
865                            .sendToTarget();
866                }
867            });
868        }
869
870        @Override
871        public void setErrorCallback(final Handler handler, final CameraErrorCallback cb) {
872            mDispatchThread.runJob(new Runnable() {
873                @Override
874                public void run() {
875                    mCameraHandler.obtainMessage(CameraActions.SET_ERROR_CALLBACK,
876                            ErrorCallbackForward.getNewInstance(
877                                    handler, AndroidCameraProxyImpl.this, cb))
878                            .sendToTarget();
879                }
880            });
881        }
882
883        @Deprecated
884        @Override
885        public void setParameters(final Parameters params) {
886            if (params == null) {
887                Log.v(TAG, "null parameters in setParameters()");
888                return;
889            }
890            final String flattenedParameters = params.flatten();
891            mDispatchThread.runJob(new Runnable() {
892                @Override
893                public void run() {
894                    mCameraState.waitForStates(AndroidCameraStateHolder.CAMERA_IDLE |
895                            AndroidCameraStateHolder.CAMERA_UNLOCKED);
896                    mCameraHandler.obtainMessage(CameraActions.SET_PARAMETERS, flattenedParameters)
897                            .sendToTarget();
898                }
899            });
900        }
901
902        @Deprecated
903        @Override
904        public Parameters getParameters() {
905            final WaitDoneBundle bundle = new WaitDoneBundle();
906            final Parameters[] parametersHolder = new Parameters[1];
907            mDispatchThread.runJobSync(new Runnable() {
908                @Override
909                public void run() {
910                    Message getParametersMessage = mCameraHandler.obtainMessage(
911                            CameraActions.GET_PARAMETERS, parametersHolder);
912                    mCameraHandler.sendMessage(getParametersMessage);
913                    mCameraHandler.post(bundle.mUnlockRunnable);
914                }
915            }, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "get parameters");
916            return parametersHolder[0];
917        }
918
919        @Override
920        public CameraSettings getSettings() {
921            return new AndroidCameraSettings(mCapabilities, getParameters());
922        }
923
924        @Override
925        public boolean applySettings(CameraSettings settings) {
926            return applySettingsHelper(settings, AndroidCameraStateHolder.CAMERA_IDLE |
927                    AndroidCameraStateHolder.CAMERA_UNLOCKED);
928        }
929
930        @Override
931        public String dumpDeviceSettings() {
932            Parameters parameters = getParameters();
933            if (parameters != null) {
934                String flattened = getParameters().flatten();
935                StringTokenizer tokenizer = new StringTokenizer(flattened, ";");
936                String dumpedSettings = new String();
937                while (tokenizer.hasMoreElements()) {
938                    dumpedSettings += tokenizer.nextToken() + '\n';
939                }
940
941                return dumpedSettings;
942            } else {
943                return "[no parameters retrieved]";
944            }
945        }
946
947        @Override
948        public Handler getCameraHandler() {
949            return AndroidCameraAgentImpl.this.getCameraHandler();
950        }
951
952        @Override
953        public DispatchThread getDispatchThread() {
954            return AndroidCameraAgentImpl.this.getDispatchThread();
955        }
956
957        @Override
958        public CameraStateHolder getCameraState() {
959            return mCameraState;
960        }
961    }
962
963    private static class AndroidCameraStateHolder extends CameraStateHolder {
964        /* Camera states */
965        // These states are defined bitwise so we can easily to specify a set of
966        // states together.
967        public static final int CAMERA_UNOPENED = 1;
968        public static final int CAMERA_IDLE = 1 << 1;
969        public static final int CAMERA_UNLOCKED = 1 << 2;
970        public static final int CAMERA_CAPTURING = 1 << 3;
971        public static final int CAMERA_FOCUSING = 1 << 4;
972
973        public AndroidCameraStateHolder() {
974            this(CAMERA_UNOPENED);
975        }
976
977        public AndroidCameraStateHolder(int state) {
978            super(state);
979        }
980    }
981
982    /**
983     * A helper class to forward AutoFocusCallback to another thread.
984     */
985    private static class AFCallbackForward implements AutoFocusCallback {
986        private final Handler mHandler;
987        private final CameraProxy mCamera;
988        private final CameraAFCallback mCallback;
989
990        /**
991         * Returns a new instance of {@link AFCallbackForward}.
992         *
993         * @param handler The handler in which the callback will be invoked in.
994         * @param camera  The {@link CameraProxy} which the callback is from.
995         * @param cb      The callback to be invoked.
996         * @return        The instance of the {@link AFCallbackForward},
997         *                or null if any parameter is null.
998         */
999        public static AFCallbackForward getNewInstance(
1000                Handler handler, CameraProxy camera, CameraAFCallback cb) {
1001            if (handler == null || camera == null || cb == null) {
1002                return null;
1003            }
1004            return new AFCallbackForward(handler, camera, cb);
1005        }
1006
1007        private AFCallbackForward(
1008                Handler h, CameraProxy camera, CameraAFCallback cb) {
1009            mHandler = h;
1010            mCamera = camera;
1011            mCallback = cb;
1012        }
1013
1014        @Override
1015        public void onAutoFocus(final boolean b, Camera camera) {
1016            mHandler.post(new Runnable() {
1017                @Override
1018                public void run() {
1019                    mCallback.onAutoFocus(b, mCamera);
1020                }
1021            });
1022        }
1023    }
1024
1025    /**
1026     * A helper class to forward ErrorCallback to another thread.
1027     */
1028    private static class ErrorCallbackForward implements Camera.ErrorCallback {
1029        private final Handler mHandler;
1030        private final CameraProxy mCamera;
1031        private final CameraErrorCallback mCallback;
1032
1033        /**
1034         * Returns a new instance of {@link AFCallbackForward}.
1035         *
1036         * @param handler The handler in which the callback will be invoked in.
1037         * @param camera  The {@link CameraProxy} which the callback is from.
1038         * @param cb      The callback to be invoked.
1039         * @return        The instance of the {@link AFCallbackForward},
1040         *                or null if any parameter is null.
1041         */
1042        public static ErrorCallbackForward getNewInstance(
1043                Handler handler, CameraProxy camera, CameraErrorCallback cb) {
1044            if (handler == null || camera == null || cb == null) {
1045                return null;
1046            }
1047            return new ErrorCallbackForward(handler, camera, cb);
1048        }
1049
1050        private ErrorCallbackForward(
1051                Handler h, CameraProxy camera, CameraErrorCallback cb) {
1052            mHandler = h;
1053            mCamera = camera;
1054            mCallback = cb;
1055        }
1056
1057        @Override
1058        public void onError(final int error, Camera camera) {
1059            mHandler.post(new Runnable() {
1060                @Override
1061                public void run() {
1062                    mCallback.onError(error, mCamera);
1063                }
1064            });
1065        }
1066    }
1067
1068    /** A helper class to forward AutoFocusMoveCallback to another thread. */
1069    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1070    private static class AFMoveCallbackForward implements AutoFocusMoveCallback {
1071        private final Handler mHandler;
1072        private final CameraAFMoveCallback mCallback;
1073        private final CameraProxy mCamera;
1074
1075        /**
1076         * Returns a new instance of {@link AFMoveCallbackForward}.
1077         *
1078         * @param handler The handler in which the callback will be invoked in.
1079         * @param camera  The {@link CameraProxy} which the callback is from.
1080         * @param cb      The callback to be invoked.
1081         * @return        The instance of the {@link AFMoveCallbackForward},
1082         *                or null if any parameter is null.
1083         */
1084        public static AFMoveCallbackForward getNewInstance(
1085                Handler handler, CameraProxy camera, CameraAFMoveCallback cb) {
1086            if (handler == null || camera == null || cb == null) {
1087                return null;
1088            }
1089            return new AFMoveCallbackForward(handler, camera, cb);
1090        }
1091
1092        private AFMoveCallbackForward(
1093                Handler h, CameraProxy camera, CameraAFMoveCallback cb) {
1094            mHandler = h;
1095            mCamera = camera;
1096            mCallback = cb;
1097        }
1098
1099        @Override
1100        public void onAutoFocusMoving(
1101                final boolean moving, android.hardware.Camera camera) {
1102            mHandler.post(new Runnable() {
1103                @Override
1104                public void run() {
1105                    mCallback.onAutoFocusMoving(moving, mCamera);
1106                }
1107            });
1108        }
1109    }
1110
1111    /**
1112     * A helper class to forward ShutterCallback to to another thread.
1113     */
1114    private static class ShutterCallbackForward implements ShutterCallback {
1115        private final Handler mHandler;
1116        private final CameraShutterCallback mCallback;
1117        private final CameraProxy mCamera;
1118
1119        /**
1120         * Returns a new instance of {@link ShutterCallbackForward}.
1121         *
1122         * @param handler The handler in which the callback will be invoked in.
1123         * @param camera  The {@link CameraProxy} which the callback is from.
1124         * @param cb      The callback to be invoked.
1125         * @return        The instance of the {@link ShutterCallbackForward},
1126         *                or null if any parameter is null.
1127         */
1128        public static ShutterCallbackForward getNewInstance(
1129                Handler handler, CameraProxy camera, CameraShutterCallback cb) {
1130            if (handler == null || camera == null || cb == null) {
1131                return null;
1132            }
1133            return new ShutterCallbackForward(handler, camera, cb);
1134        }
1135
1136        private ShutterCallbackForward(
1137                Handler h, CameraProxy camera, CameraShutterCallback cb) {
1138            mHandler = h;
1139            mCamera = camera;
1140            mCallback = cb;
1141        }
1142
1143        @Override
1144        public void onShutter() {
1145            mHandler.post(new Runnable() {
1146                @Override
1147                public void run() {
1148                    mCallback.onShutter(mCamera);
1149                }
1150            });
1151        }
1152    }
1153
1154    /**
1155     * A helper class to forward PictureCallback to another thread.
1156     */
1157    private static class PictureCallbackForward implements PictureCallback {
1158        private final Handler mHandler;
1159        private final CameraPictureCallback mCallback;
1160        private final CameraProxy mCamera;
1161
1162        /**
1163         * Returns a new instance of {@link PictureCallbackForward}.
1164         *
1165         * @param handler The handler in which the callback will be invoked in.
1166         * @param camera  The {@link CameraProxy} which the callback is from.
1167         * @param cb      The callback to be invoked.
1168         * @return        The instance of the {@link PictureCallbackForward},
1169         *                or null if any parameters is null.
1170         */
1171        public static PictureCallbackForward getNewInstance(
1172                Handler handler, CameraProxy camera, CameraPictureCallback cb) {
1173            if (handler == null || camera == null || cb == null) {
1174                return null;
1175            }
1176            return new PictureCallbackForward(handler, camera, cb);
1177        }
1178
1179        private PictureCallbackForward(
1180                Handler h, CameraProxy camera, CameraPictureCallback cb) {
1181            mHandler = h;
1182            mCamera = camera;
1183            mCallback = cb;
1184        }
1185
1186        @Override
1187        public void onPictureTaken(
1188                final byte[] data, android.hardware.Camera camera) {
1189            mHandler.post(new Runnable() {
1190                @Override
1191                public void run() {
1192                    mCallback.onPictureTaken(data, mCamera);
1193                }
1194            });
1195        }
1196    }
1197
1198    /**
1199     * A helper class to forward PreviewCallback to another thread.
1200     */
1201    private static class PreviewCallbackForward implements PreviewCallback {
1202        private final Handler mHandler;
1203        private final CameraPreviewDataCallback mCallback;
1204        private final CameraProxy mCamera;
1205
1206        /**
1207         * Returns a new instance of {@link PreviewCallbackForward}.
1208         *
1209         * @param handler The handler in which the callback will be invoked in.
1210         * @param camera  The {@link CameraProxy} which the callback is from.
1211         * @param cb      The callback to be invoked.
1212         * @return        The instance of the {@link PreviewCallbackForward},
1213         *                or null if any parameters is null.
1214         */
1215        public static PreviewCallbackForward getNewInstance(
1216                Handler handler, CameraProxy camera, CameraPreviewDataCallback cb) {
1217            if (handler == null || camera == null || cb == null) {
1218                return null;
1219            }
1220            return new PreviewCallbackForward(handler, camera, cb);
1221        }
1222
1223        private PreviewCallbackForward(
1224                Handler h, CameraProxy camera, CameraPreviewDataCallback cb) {
1225            mHandler = h;
1226            mCamera = camera;
1227            mCallback = cb;
1228        }
1229
1230        @Override
1231        public void onPreviewFrame(
1232                final byte[] data, android.hardware.Camera camera) {
1233            mHandler.post(new Runnable() {
1234                @Override
1235                public void run() {
1236                    mCallback.onPreviewFrame(data, mCamera);
1237                }
1238            });
1239        }
1240    }
1241
1242    private static class FaceDetectionCallbackForward implements FaceDetectionListener {
1243        private final Handler mHandler;
1244        private final CameraFaceDetectionCallback mCallback;
1245        private final CameraProxy mCamera;
1246
1247        /**
1248         * Returns a new instance of {@link FaceDetectionCallbackForward}.
1249         *
1250         * @param handler The handler in which the callback will be invoked in.
1251         * @param camera  The {@link CameraProxy} which the callback is from.
1252         * @param cb      The callback to be invoked.
1253         * @return        The instance of the {@link FaceDetectionCallbackForward},
1254         *                or null if any parameter is null.
1255         */
1256        public static FaceDetectionCallbackForward getNewInstance(
1257                Handler handler, CameraProxy camera, CameraFaceDetectionCallback cb) {
1258            if (handler == null || camera == null || cb == null) {
1259                return null;
1260            }
1261            return new FaceDetectionCallbackForward(handler, camera, cb);
1262        }
1263
1264        private FaceDetectionCallbackForward(
1265                Handler h, CameraProxy camera, CameraFaceDetectionCallback cb) {
1266            mHandler = h;
1267            mCamera = camera;
1268            mCallback = cb;
1269        }
1270
1271        @Override
1272        public void onFaceDetection(
1273                final Camera.Face[] faces, Camera camera) {
1274            mHandler.post(new Runnable() {
1275                @Override
1276                public void run() {
1277                    mCallback.onFaceDetection(faces, mCamera);
1278                }
1279            });
1280        }
1281    }
1282}
1283