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