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