AndroidCameraAgentImpl.java revision f73c6cf2f04451ba4517abccd36a114667c69ae3
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                    case CameraActions.SET_PREVIEW_TEXTURE_ASYNC: {
404                        setPreviewTexture(msg.obj);
405                        break;
406                    }
407
408                    case CameraActions.SET_PREVIEW_DISPLAY_ASYNC: {
409                        try {
410                            mCamera.setPreviewDisplay((SurfaceHolder) msg.obj);
411                        } catch (IOException e) {
412                            throw new RuntimeException(e);
413                        }
414                        break;
415                    }
416
417                    case CameraActions.START_PREVIEW_ASYNC: {
418                        final CameraStartPreviewCallbackForward cbForward =
419                            (CameraStartPreviewCallbackForward) msg.obj;
420                        mCamera.startPreview();
421                        if (cbForward != null) {
422                            cbForward.onPreviewStarted();
423                        }
424                        break;
425                    }
426
427                    case CameraActions.STOP_PREVIEW: {
428                        mCamera.stopPreview();
429                        break;
430                    }
431
432                    case CameraActions.SET_PREVIEW_CALLBACK_WITH_BUFFER: {
433                        mCamera.setPreviewCallbackWithBuffer((PreviewCallback) msg.obj);
434                        break;
435                    }
436
437                    case CameraActions.SET_ONE_SHOT_PREVIEW_CALLBACK: {
438                        mCamera.setOneShotPreviewCallback((PreviewCallback) msg.obj);
439                        break;
440                    }
441
442                    case CameraActions.ADD_CALLBACK_BUFFER: {
443                        mCamera.addCallbackBuffer((byte[]) msg.obj);
444                        break;
445                    }
446
447                    case CameraActions.AUTO_FOCUS: {
448                        mCameraState.setState(AndroidCameraStateHolder.CAMERA_FOCUSING);
449                        mCamera.autoFocus((AutoFocusCallback) msg.obj);
450                        break;
451                    }
452
453                    case CameraActions.CANCEL_AUTO_FOCUS: {
454                        mCamera.cancelAutoFocus();
455                        mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE);
456                        break;
457                    }
458
459                    case CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK: {
460                        setAutoFocusMoveCallback(mCamera, msg.obj);
461                        break;
462                    }
463
464                    case CameraActions.SET_DISPLAY_ORIENTATION: {
465                        // Update preview orientation
466                        mCamera.setDisplayOrientation(
467                                mCharacteristics.getPreviewOrientation(msg.arg1));
468                        // Only set the JPEG capture orientation if requested to do so; otherwise,
469                        // capture in the sensor's physical orientation
470                        Parameters parameters = mParameterCache.getBlocking();
471                        parameters.setRotation(
472                                msg.arg2 > 0 ? mCharacteristics.getJpegOrientation(msg.arg1) : 0);
473                        mCamera.setParameters(parameters);
474                        break;
475                    }
476
477                    case CameraActions.SET_ZOOM_CHANGE_LISTENER: {
478                        mCamera.setZoomChangeListener((OnZoomChangeListener) msg.obj);
479                        break;
480                    }
481
482                    case CameraActions.SET_FACE_DETECTION_LISTENER: {
483                        setFaceDetectionListener((FaceDetectionListener) msg.obj);
484                        break;
485                    }
486
487                    case CameraActions.START_FACE_DETECTION: {
488                        startFaceDetection();
489                        break;
490                    }
491
492                    case CameraActions.STOP_FACE_DETECTION: {
493                        stopFaceDetection();
494                        break;
495                    }
496
497                    case CameraActions.SET_ERROR_CALLBACK: {
498                        mCamera.setErrorCallback((ErrorCallback) msg.obj);
499                        break;
500                    }
501
502                    case CameraActions.APPLY_SETTINGS: {
503                        Parameters parameters = mParameterCache.getBlocking();
504                        CameraSettings settings = (CameraSettings) msg.obj;
505                        applySettingsToParameters(settings, parameters);
506                        mCamera.setParameters(parameters);
507                        mParameterCache.invalidate();
508                        break;
509                    }
510
511                    case CameraActions.SET_PARAMETERS: {
512                        Parameters parameters = mParameterCache.getBlocking();
513                        parameters.unflatten((String) msg.obj);
514                        mCamera.setParameters(parameters);
515                        mParameterCache.invalidate();
516                        break;
517                    }
518
519                    case CameraActions.GET_PARAMETERS: {
520                        Parameters[] parametersHolder = (Parameters[]) msg.obj;
521                        Parameters parameters = mParameterCache.getBlocking();
522                        parametersHolder[0] = parameters;
523                        break;
524                    }
525
526                    case CameraActions.SET_PREVIEW_CALLBACK: {
527                        mCamera.setPreviewCallback((PreviewCallback) msg.obj);
528                        break;
529                    }
530
531                    case CameraActions.ENABLE_SHUTTER_SOUND: {
532                        enableShutterSound((msg.arg1 == 1) ? true : false);
533                        break;
534                    }
535
536                    case CameraActions.REFRESH_PARAMETERS: {
537                        mParameterCache.invalidate();;
538                        break;
539                    }
540
541                    case CameraActions.CAPTURE_PHOTO: {
542                        mCameraState.setState(AndroidCameraStateHolder.CAMERA_CAPTURING);
543                        capture((CaptureCallbacks) msg.obj);
544                        break;
545                    }
546
547                    default: {
548                        throw new RuntimeException("Invalid CameraProxy message=" + msg.what);
549                    }
550                }
551            } catch (final RuntimeException e) {
552                if (msg.what != CameraActions.RELEASE && mCamera != null) {
553                    try {
554                        mCamera.release();
555                        mCameraState.setState(AndroidCameraStateHolder.CAMERA_UNOPENED);
556                    } catch (Exception ex) {
557                        Log.e(TAG, "Fail to release the camera.");
558                    }
559                    mCamera = null;
560                } else {
561                    if (mCamera == null) {
562                        if (msg.what == CameraActions.OPEN_CAMERA) {
563                            final int cameraId = msg.arg1;
564                            if (msg.obj != null) {
565                                ((CameraOpenCallback) msg.obj).onDeviceOpenFailure(
566                                        msg.arg1, generateHistoryString(cameraId));
567                            }
568                        } else {
569                            Log.w(TAG, "Cannot handle message " + msg.what + ", mCamera is null.");
570                        }
571                        return;
572                    }
573                }
574                synchronized (mCameraExceptionCallback) {
575                    mCameraExceptionCallbackHandler.post(new Runnable() {
576                        @Override
577                        public void run() {
578                                mCameraExceptionCallback.onCameraException(e);
579                            }
580                        });
581                }
582            }
583        }
584
585        private void applySettingsToParameters(final CameraSettings settings,
586                final Parameters parameters) {
587            final CameraCapabilities.Stringifier stringifier = mCapabilities.getStringifier();
588            Size photoSize = settings.getCurrentPhotoSize();
589            parameters.setPictureSize(photoSize.width(), photoSize.height());
590            Size previewSize = settings.getCurrentPreviewSize();
591            parameters.setPreviewSize(previewSize.width(), previewSize.height());
592            if (settings.getPreviewFrameRate() == -1) {
593                parameters.setPreviewFpsRange(settings.getPreviewFpsRangeMin(),
594                        settings.getPreviewFpsRangeMax());
595            } else {
596                parameters.setPreviewFrameRate(settings.getPreviewFrameRate());
597            }
598            parameters.setPreviewFormat(settings.getCurrentPreviewFormat());
599            parameters.setJpegQuality(settings.getPhotoJpegCompressionQuality());
600            if (mCapabilities.supports(CameraCapabilities.Feature.ZOOM)) {
601                parameters.setZoom(zoomRatioToIndex(settings.getCurrentZoomRatio(),
602                        parameters.getZoomRatios()));
603            }
604            parameters.setExposureCompensation(settings.getExposureCompensationIndex());
605            if (mCapabilities.supports(CameraCapabilities.Feature.AUTO_EXPOSURE_LOCK)) {
606                parameters.setAutoExposureLock(settings.isAutoExposureLocked());
607            }
608            parameters.setFocusMode(stringifier.stringify(settings.getCurrentFocusMode()));
609            if (mCapabilities.supports(CameraCapabilities.Feature.AUTO_WHITE_BALANCE_LOCK)) {
610                parameters.setAutoWhiteBalanceLock(settings.isAutoWhiteBalanceLocked());
611            }
612            if (mCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA)) {
613                if (settings.getFocusAreas().size() != 0) {
614                    parameters.setFocusAreas(settings.getFocusAreas());
615                } else {
616                    parameters.setFocusAreas(null);
617                }
618            }
619            if (mCapabilities.supports(CameraCapabilities.Feature.METERING_AREA)) {
620                if (settings.getMeteringAreas().size() != 0) {
621                    parameters.setMeteringAreas(settings.getMeteringAreas());
622                } else {
623                    parameters.setMeteringAreas(null);
624                }
625            }
626            if (settings.getCurrentFlashMode() != CameraCapabilities.FlashMode.NO_FLASH) {
627                parameters.setFlashMode(stringifier.stringify(settings.getCurrentFlashMode()));
628            }
629            if (settings.getCurrentSceneMode() != CameraCapabilities.SceneMode.NO_SCENE_MODE) {
630                if (settings.getCurrentSceneMode() != null) {
631                    parameters
632                            .setSceneMode(stringifier.stringify(settings.getCurrentSceneMode()));
633                }
634            }
635            parameters.setRecordingHint(settings.isRecordingHintEnabled());
636            Size jpegThumbSize = settings.getExifThumbnailSize();
637            parameters.setJpegThumbnailSize(jpegThumbSize.width(), jpegThumbSize.height());
638            parameters.setPictureFormat(settings.getCurrentPhotoFormat());
639
640            CameraSettings.GpsData gpsData = settings.getGpsData();
641            if (gpsData == null) {
642                parameters.removeGpsData();
643            } else {
644                parameters.setGpsTimestamp(gpsData.timeStamp);
645                if (gpsData.processingMethod != null) {
646                    // It's a hack since we always use GPS time stamp but does
647                    // not use other fields sometimes. Setting processing
648                    // method to null means the other fields should not be used.
649                    parameters.setGpsAltitude(gpsData.altitude);
650                    parameters.setGpsLatitude(gpsData.latitude);
651                    parameters.setGpsLongitude(gpsData.longitude);
652                    parameters.setGpsProcessingMethod(gpsData.processingMethod);
653                }
654            }
655
656        }
657
658        /**
659         * @param ratio Desired zoom ratio, in [1.0f,+Inf).
660         * @param percentages Available zoom ratios, as percentages.
661         * @return Index of the closest corresponding ratio, rounded up toward
662         *         that of the maximum available ratio.
663         */
664        private int zoomRatioToIndex(float ratio, List<Integer> percentages) {
665            int percent = (int) (ratio * AndroidCameraCapabilities.ZOOM_MULTIPLIER);
666            int index = Collections.binarySearch(percentages, percent);
667            if (index >= 0) {
668                // Found the desired ratio in the supported list
669                return index;
670            } else {
671                // Didn't find an exact match. Where would it have been?
672                index = -(index + 1);
673                if (index == percentages.size()) {
674                    // Put it back in bounds by setting to the maximum allowable zoom
675                    --index;
676                }
677                return index;
678            }
679        }
680    }
681
682    /**
683     * A class which implements {@link CameraAgent.CameraProxy} and
684     * camera handler thread.
685     */
686    private class AndroidCameraProxyImpl extends CameraAgent.CameraProxy {
687        private final int mCameraId;
688        /* TODO: remove this Camera instance. */
689        private final Camera mCamera;
690        private final CameraDeviceInfo.Characteristics mCharacteristics;
691        private final AndroidCameraCapabilities mCapabilities;
692
693        private AndroidCameraProxyImpl(int cameraId, Camera camera,
694                CameraDeviceInfo.Characteristics characteristics,
695                AndroidCameraCapabilities capabilities) {
696            mCamera = camera;
697            mCameraId = cameraId;
698            mCharacteristics = characteristics;
699            mCapabilities = capabilities;
700        }
701
702        @Deprecated
703        @Override
704        public android.hardware.Camera getCamera() {
705            return mCamera;
706        }
707
708        @Override
709        public int getCameraId() {
710            return mCameraId;
711        }
712
713        @Override
714        public CameraDeviceInfo.Characteristics getCharacteristics() {
715            return mCharacteristics;
716        }
717
718        @Override
719        public CameraCapabilities getCapabilities() {
720            return new AndroidCameraCapabilities(mCapabilities);
721        }
722
723        @Override
724        public void setPreviewDataCallback(
725                final Handler handler, final CameraPreviewDataCallback cb) {
726            mDispatchThread.runJob(new Runnable() {
727                @Override
728                public void run() {
729                    mCameraHandler.obtainMessage(CameraActions.SET_PREVIEW_CALLBACK,
730                            PreviewCallbackForward.getNewInstance(
731                                    handler, AndroidCameraProxyImpl.this, cb))
732                            .sendToTarget();
733                }
734            });
735        }
736
737        @Override
738        public void setOneShotPreviewCallback(final Handler handler,
739                final CameraPreviewDataCallback cb) {
740            mDispatchThread.runJob(new Runnable() {
741                @Override
742                public void run() {
743                    mCameraHandler.obtainMessage(CameraActions.SET_ONE_SHOT_PREVIEW_CALLBACK,
744                            PreviewCallbackForward
745                                    .getNewInstance(handler, AndroidCameraProxyImpl.this, cb))
746                            .sendToTarget();
747                }
748            });
749        }
750
751        @Override
752        public void setPreviewDataCallbackWithBuffer(
753                final Handler handler, final CameraPreviewDataCallback cb) {
754            mDispatchThread.runJob(new Runnable() {
755                @Override
756                public void run() {
757                    mCameraHandler.obtainMessage(CameraActions.SET_PREVIEW_CALLBACK_WITH_BUFFER,
758                            PreviewCallbackForward
759                                    .getNewInstance(handler, AndroidCameraProxyImpl.this, cb))
760                            .sendToTarget();
761                }
762            });
763        }
764
765        @Override
766        public void autoFocus(final Handler handler, final CameraAFCallback cb) {
767            final AutoFocusCallback afCallback = new AutoFocusCallback() {
768                @Override
769                public void onAutoFocus(final boolean b, Camera camera) {
770                    if (mCameraState.getState() != AndroidCameraStateHolder.CAMERA_FOCUSING) {
771                        Log.w(TAG, "onAutoFocus callback returning when not focusing");
772                    } else {
773                        mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE);
774                    }
775                    handler.post(new Runnable() {
776                        @Override
777                        public void run() {
778                            cb.onAutoFocus(b, AndroidCameraProxyImpl.this);
779                        }
780                    });
781                }
782            };
783            mDispatchThread.runJob(new Runnable() {
784                @Override
785                public void run() {
786                    mCameraState.waitForStates(AndroidCameraStateHolder.CAMERA_IDLE);
787                    mCameraHandler.obtainMessage(CameraActions.AUTO_FOCUS, afCallback)
788                            .sendToTarget();
789                }
790            });
791        }
792
793        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
794        @Override
795        public void setAutoFocusMoveCallback(
796                final Handler handler, final CameraAFMoveCallback cb) {
797            mDispatchThread.runJob(new Runnable() {
798                @Override
799                public void run() {
800                    mCameraHandler.obtainMessage(CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK,
801                            AFMoveCallbackForward.getNewInstance(
802                                    handler, AndroidCameraProxyImpl.this, cb))
803                            .sendToTarget();
804                }
805            });
806        }
807
808        @Override
809        public void takePicture(
810                final Handler handler, final CameraShutterCallback shutter,
811                final CameraPictureCallback raw, final CameraPictureCallback post,
812                final CameraPictureCallback jpeg) {
813            final PictureCallback jpegCallback = new PictureCallback() {
814                @Override
815                public void onPictureTaken(final byte[] data, Camera camera) {
816                    if (mCameraState.getState() != AndroidCameraStateHolder.CAMERA_CAPTURING) {
817                        Log.w(TAG, "picture callback returning when not capturing");
818                    } else {
819                        mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE);
820                    }
821                    handler.post(new Runnable() {
822                        @Override
823                        public void run() {
824                            jpeg.onPictureTaken(data, AndroidCameraProxyImpl.this);
825                        }
826                    });
827                }
828            };
829
830            mDispatchThread.runJob(new Runnable() {
831                @Override
832                public void run() {
833                    mCameraState.waitForStates(AndroidCameraStateHolder.CAMERA_IDLE |
834                            AndroidCameraStateHolder.CAMERA_UNLOCKED);
835                    mCameraHandler.requestTakePicture(ShutterCallbackForward
836                                    .getNewInstance(handler, AndroidCameraProxyImpl.this, shutter),
837                            PictureCallbackForward
838                                    .getNewInstance(handler, AndroidCameraProxyImpl.this, raw),
839                            PictureCallbackForward
840                                    .getNewInstance(handler, AndroidCameraProxyImpl.this, post),
841                            jpegCallback
842                    );
843                }
844            });
845        }
846
847        @Override
848        public void setZoomChangeListener(final OnZoomChangeListener listener) {
849            mDispatchThread.runJob(new Runnable() {
850                @Override
851                public void run() {
852                    mCameraHandler.obtainMessage(CameraActions.SET_ZOOM_CHANGE_LISTENER, listener)
853                            .sendToTarget();
854                }
855            });
856        }
857
858        @Override
859        public void setFaceDetectionCallback(final Handler handler,
860                final CameraFaceDetectionCallback cb) {
861            mDispatchThread.runJob(new Runnable() {
862                @Override
863                public void run() {
864                    mCameraHandler.obtainMessage(CameraActions.SET_FACE_DETECTION_LISTENER,
865                            FaceDetectionCallbackForward
866                                    .getNewInstance(handler, AndroidCameraProxyImpl.this, cb))
867                            .sendToTarget();
868                }
869            });
870        }
871
872        @Override
873        public void setErrorCallback(final Handler handler, final CameraErrorCallback cb) {
874            mDispatchThread.runJob(new Runnable() {
875                @Override
876                public void run() {
877                    mCameraHandler.obtainMessage(CameraActions.SET_ERROR_CALLBACK,
878                            ErrorCallbackForward.getNewInstance(
879                                    handler, AndroidCameraProxyImpl.this, cb))
880                            .sendToTarget();
881                }
882            });
883        }
884
885        @Deprecated
886        @Override
887        public void setParameters(final Parameters params) {
888            if (params == null) {
889                Log.v(TAG, "null parameters in setParameters()");
890                return;
891            }
892            final String flattenedParameters = params.flatten();
893            mDispatchThread.runJob(new Runnable() {
894                @Override
895                public void run() {
896                    mCameraState.waitForStates(AndroidCameraStateHolder.CAMERA_IDLE |
897                            AndroidCameraStateHolder.CAMERA_UNLOCKED);
898                    mCameraHandler.obtainMessage(CameraActions.SET_PARAMETERS, flattenedParameters)
899                            .sendToTarget();
900                }
901            });
902        }
903
904        @Deprecated
905        @Override
906        public Parameters getParameters() {
907            final WaitDoneBundle bundle = new WaitDoneBundle();
908            final Parameters[] parametersHolder = new Parameters[1];
909            mDispatchThread.runJobSync(new Runnable() {
910                @Override
911                public void run() {
912                    Message getParametersMessage = mCameraHandler.obtainMessage(
913                            CameraActions.GET_PARAMETERS, parametersHolder);
914                    mCameraHandler.sendMessage(getParametersMessage);
915                    mCameraHandler.post(bundle.mUnlockRunnable);
916                }
917            }, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "get parameters");
918            return parametersHolder[0];
919        }
920
921        @Override
922        public CameraSettings getSettings() {
923            return new AndroidCameraSettings(mCapabilities, getParameters());
924        }
925
926        @Override
927        public boolean applySettings(CameraSettings settings) {
928            return applySettingsHelper(settings, AndroidCameraStateHolder.CAMERA_IDLE |
929                    AndroidCameraStateHolder.CAMERA_UNLOCKED);
930        }
931
932        @Override
933        public String dumpDeviceSettings() {
934            Parameters parameters = getParameters();
935            if (parameters != null) {
936                String flattened = getParameters().flatten();
937                StringTokenizer tokenizer = new StringTokenizer(flattened, ";");
938                String dumpedSettings = new String();
939                while (tokenizer.hasMoreElements()) {
940                    dumpedSettings += tokenizer.nextToken() + '\n';
941                }
942
943                return dumpedSettings;
944            } else {
945                return "[no parameters retrieved]";
946            }
947        }
948
949        @Override
950        public Handler getCameraHandler() {
951            return AndroidCameraAgentImpl.this.getCameraHandler();
952        }
953
954        @Override
955        public DispatchThread getDispatchThread() {
956            return AndroidCameraAgentImpl.this.getDispatchThread();
957        }
958
959        @Override
960        public CameraStateHolder getCameraState() {
961            return mCameraState;
962        }
963    }
964
965    private static class AndroidCameraStateHolder extends CameraStateHolder {
966        /* Camera states */
967        // These states are defined bitwise so we can easily to specify a set of
968        // states together.
969        public static final int CAMERA_UNOPENED = 1;
970        public static final int CAMERA_IDLE = 1 << 1;
971        public static final int CAMERA_UNLOCKED = 1 << 2;
972        public static final int CAMERA_CAPTURING = 1 << 3;
973        public static final int CAMERA_FOCUSING = 1 << 4;
974
975        public AndroidCameraStateHolder() {
976            this(CAMERA_UNOPENED);
977        }
978
979        public AndroidCameraStateHolder(int state) {
980            super(state);
981        }
982    }
983
984    /**
985     * A helper class to forward AutoFocusCallback to another thread.
986     */
987    private static class AFCallbackForward implements AutoFocusCallback {
988        private final Handler mHandler;
989        private final CameraProxy mCamera;
990        private final CameraAFCallback mCallback;
991
992        /**
993         * Returns a new instance of {@link AFCallbackForward}.
994         *
995         * @param handler The handler in which the callback will be invoked in.
996         * @param camera  The {@link CameraProxy} which the callback is from.
997         * @param cb      The callback to be invoked.
998         * @return        The instance of the {@link AFCallbackForward},
999         *                or null if any parameter is null.
1000         */
1001        public static AFCallbackForward getNewInstance(
1002                Handler handler, CameraProxy camera, CameraAFCallback cb) {
1003            if (handler == null || camera == null || cb == null) {
1004                return null;
1005            }
1006            return new AFCallbackForward(handler, camera, cb);
1007        }
1008
1009        private AFCallbackForward(
1010                Handler h, CameraProxy camera, CameraAFCallback cb) {
1011            mHandler = h;
1012            mCamera = camera;
1013            mCallback = cb;
1014        }
1015
1016        @Override
1017        public void onAutoFocus(final boolean b, Camera camera) {
1018            mHandler.post(new Runnable() {
1019                @Override
1020                public void run() {
1021                    mCallback.onAutoFocus(b, mCamera);
1022                }
1023            });
1024        }
1025    }
1026
1027    /**
1028     * A helper class to forward ErrorCallback to another thread.
1029     */
1030    private static class ErrorCallbackForward implements Camera.ErrorCallback {
1031        private final Handler mHandler;
1032        private final CameraProxy mCamera;
1033        private final CameraErrorCallback mCallback;
1034
1035        /**
1036         * Returns a new instance of {@link AFCallbackForward}.
1037         *
1038         * @param handler The handler in which the callback will be invoked in.
1039         * @param camera  The {@link CameraProxy} which the callback is from.
1040         * @param cb      The callback to be invoked.
1041         * @return        The instance of the {@link AFCallbackForward},
1042         *                or null if any parameter is null.
1043         */
1044        public static ErrorCallbackForward getNewInstance(
1045                Handler handler, CameraProxy camera, CameraErrorCallback cb) {
1046            if (handler == null || camera == null || cb == null) {
1047                return null;
1048            }
1049            return new ErrorCallbackForward(handler, camera, cb);
1050        }
1051
1052        private ErrorCallbackForward(
1053                Handler h, CameraProxy camera, CameraErrorCallback cb) {
1054            mHandler = h;
1055            mCamera = camera;
1056            mCallback = cb;
1057        }
1058
1059        @Override
1060        public void onError(final int error, Camera camera) {
1061            mHandler.post(new Runnable() {
1062                @Override
1063                public void run() {
1064                    mCallback.onError(error, mCamera);
1065                }
1066            });
1067        }
1068    }
1069
1070    /** A helper class to forward AutoFocusMoveCallback to another thread. */
1071    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1072    private static class AFMoveCallbackForward implements AutoFocusMoveCallback {
1073        private final Handler mHandler;
1074        private final CameraAFMoveCallback mCallback;
1075        private final CameraProxy mCamera;
1076
1077        /**
1078         * Returns a new instance of {@link AFMoveCallbackForward}.
1079         *
1080         * @param handler The handler in which the callback will be invoked in.
1081         * @param camera  The {@link CameraProxy} which the callback is from.
1082         * @param cb      The callback to be invoked.
1083         * @return        The instance of the {@link AFMoveCallbackForward},
1084         *                or null if any parameter is null.
1085         */
1086        public static AFMoveCallbackForward getNewInstance(
1087                Handler handler, CameraProxy camera, CameraAFMoveCallback cb) {
1088            if (handler == null || camera == null || cb == null) {
1089                return null;
1090            }
1091            return new AFMoveCallbackForward(handler, camera, cb);
1092        }
1093
1094        private AFMoveCallbackForward(
1095                Handler h, CameraProxy camera, CameraAFMoveCallback cb) {
1096            mHandler = h;
1097            mCamera = camera;
1098            mCallback = cb;
1099        }
1100
1101        @Override
1102        public void onAutoFocusMoving(
1103                final boolean moving, android.hardware.Camera camera) {
1104            mHandler.post(new Runnable() {
1105                @Override
1106                public void run() {
1107                    mCallback.onAutoFocusMoving(moving, mCamera);
1108                }
1109            });
1110        }
1111    }
1112
1113    /**
1114     * A helper class to forward ShutterCallback to to another thread.
1115     */
1116    private static class ShutterCallbackForward implements ShutterCallback {
1117        private final Handler mHandler;
1118        private final CameraShutterCallback mCallback;
1119        private final CameraProxy mCamera;
1120
1121        /**
1122         * Returns a new instance of {@link ShutterCallbackForward}.
1123         *
1124         * @param handler The handler in which the callback will be invoked in.
1125         * @param camera  The {@link CameraProxy} which the callback is from.
1126         * @param cb      The callback to be invoked.
1127         * @return        The instance of the {@link ShutterCallbackForward},
1128         *                or null if any parameter is null.
1129         */
1130        public static ShutterCallbackForward getNewInstance(
1131                Handler handler, CameraProxy camera, CameraShutterCallback cb) {
1132            if (handler == null || camera == null || cb == null) {
1133                return null;
1134            }
1135            return new ShutterCallbackForward(handler, camera, cb);
1136        }
1137
1138        private ShutterCallbackForward(
1139                Handler h, CameraProxy camera, CameraShutterCallback cb) {
1140            mHandler = h;
1141            mCamera = camera;
1142            mCallback = cb;
1143        }
1144
1145        @Override
1146        public void onShutter() {
1147            mHandler.post(new Runnable() {
1148                @Override
1149                public void run() {
1150                    mCallback.onShutter(mCamera);
1151                }
1152            });
1153        }
1154    }
1155
1156    /**
1157     * A helper class to forward PictureCallback to another thread.
1158     */
1159    private static class PictureCallbackForward implements PictureCallback {
1160        private final Handler mHandler;
1161        private final CameraPictureCallback mCallback;
1162        private final CameraProxy mCamera;
1163
1164        /**
1165         * Returns a new instance of {@link PictureCallbackForward}.
1166         *
1167         * @param handler The handler in which the callback will be invoked in.
1168         * @param camera  The {@link CameraProxy} which the callback is from.
1169         * @param cb      The callback to be invoked.
1170         * @return        The instance of the {@link PictureCallbackForward},
1171         *                or null if any parameters is null.
1172         */
1173        public static PictureCallbackForward getNewInstance(
1174                Handler handler, CameraProxy camera, CameraPictureCallback cb) {
1175            if (handler == null || camera == null || cb == null) {
1176                return null;
1177            }
1178            return new PictureCallbackForward(handler, camera, cb);
1179        }
1180
1181        private PictureCallbackForward(
1182                Handler h, CameraProxy camera, CameraPictureCallback cb) {
1183            mHandler = h;
1184            mCamera = camera;
1185            mCallback = cb;
1186        }
1187
1188        @Override
1189        public void onPictureTaken(
1190                final byte[] data, android.hardware.Camera camera) {
1191            mHandler.post(new Runnable() {
1192                @Override
1193                public void run() {
1194                    mCallback.onPictureTaken(data, mCamera);
1195                }
1196            });
1197        }
1198    }
1199
1200    /**
1201     * A helper class to forward PreviewCallback to another thread.
1202     */
1203    private static class PreviewCallbackForward implements PreviewCallback {
1204        private final Handler mHandler;
1205        private final CameraPreviewDataCallback mCallback;
1206        private final CameraProxy mCamera;
1207
1208        /**
1209         * Returns a new instance of {@link PreviewCallbackForward}.
1210         *
1211         * @param handler The handler in which the callback will be invoked in.
1212         * @param camera  The {@link CameraProxy} which the callback is from.
1213         * @param cb      The callback to be invoked.
1214         * @return        The instance of the {@link PreviewCallbackForward},
1215         *                or null if any parameters is null.
1216         */
1217        public static PreviewCallbackForward getNewInstance(
1218                Handler handler, CameraProxy camera, CameraPreviewDataCallback cb) {
1219            if (handler == null || camera == null || cb == null) {
1220                return null;
1221            }
1222            return new PreviewCallbackForward(handler, camera, cb);
1223        }
1224
1225        private PreviewCallbackForward(
1226                Handler h, CameraProxy camera, CameraPreviewDataCallback cb) {
1227            mHandler = h;
1228            mCamera = camera;
1229            mCallback = cb;
1230        }
1231
1232        @Override
1233        public void onPreviewFrame(
1234                final byte[] data, android.hardware.Camera camera) {
1235            mHandler.post(new Runnable() {
1236                @Override
1237                public void run() {
1238                    mCallback.onPreviewFrame(data, mCamera);
1239                }
1240            });
1241        }
1242    }
1243
1244    private static class FaceDetectionCallbackForward implements FaceDetectionListener {
1245        private final Handler mHandler;
1246        private final CameraFaceDetectionCallback mCallback;
1247        private final CameraProxy mCamera;
1248
1249        /**
1250         * Returns a new instance of {@link FaceDetectionCallbackForward}.
1251         *
1252         * @param handler The handler in which the callback will be invoked in.
1253         * @param camera  The {@link CameraProxy} which the callback is from.
1254         * @param cb      The callback to be invoked.
1255         * @return        The instance of the {@link FaceDetectionCallbackForward},
1256         *                or null if any parameter is null.
1257         */
1258        public static FaceDetectionCallbackForward getNewInstance(
1259                Handler handler, CameraProxy camera, CameraFaceDetectionCallback cb) {
1260            if (handler == null || camera == null || cb == null) {
1261                return null;
1262            }
1263            return new FaceDetectionCallbackForward(handler, camera, cb);
1264        }
1265
1266        private FaceDetectionCallbackForward(
1267                Handler h, CameraProxy camera, CameraFaceDetectionCallback cb) {
1268            mHandler = h;
1269            mCamera = camera;
1270            mCallback = cb;
1271        }
1272
1273        @Override
1274        public void onFaceDetection(
1275                final Camera.Face[] faces, Camera camera) {
1276            mHandler.post(new Runnable() {
1277                @Override
1278                public void run() {
1279                    mCallback.onFaceDetection(faces, mCamera);
1280                }
1281            });
1282        }
1283    }
1284}
1285