1/*
2 * Copyright (C) 2015 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.camera.captureintent.state;
18
19import android.graphics.Bitmap;
20import android.graphics.Matrix;
21import android.graphics.Point;
22import android.graphics.PointF;
23import android.graphics.RectF;
24import android.media.MediaActionSound;
25import android.net.Uri;
26
27import com.android.camera.async.RefCountBase;
28import com.android.camera.captureintent.CaptureIntentConfig;
29import com.android.camera.captureintent.CaptureIntentModuleUI;
30import com.android.camera.captureintent.PictureDecoder;
31import com.android.camera.captureintent.event.EventCameraBusy;
32import com.android.camera.captureintent.event.EventCameraQuickExpose;
33import com.android.camera.captureintent.event.EventCameraReady;
34import com.android.camera.captureintent.event.EventClickOnCameraKey;
35import com.android.camera.captureintent.event.EventFastPictureBitmapAvailable;
36import com.android.camera.captureintent.event.EventOnSurfaceTextureUpdated;
37import com.android.camera.captureintent.event.EventOnTextureViewLayoutChanged;
38import com.android.camera.captureintent.event.EventPause;
39import com.android.camera.captureintent.event.EventPictureCompressed;
40import com.android.camera.captureintent.event.EventPictureDecoded;
41import com.android.camera.captureintent.event.EventTapOnCancelShutterButton;
42import com.android.camera.captureintent.event.EventTapOnPreview;
43import com.android.camera.captureintent.event.EventTapOnShutterButton;
44import com.android.camera.captureintent.event.EventTapOnSwitchCameraButton;
45import com.android.camera.captureintent.event.EventTimerCountDownToZero;
46import com.android.camera.captureintent.event.EventZoomRatioChanged;
47import com.android.camera.captureintent.resource.ResourceCaptureTools;
48import com.android.camera.captureintent.resource.ResourceCaptureToolsImpl;
49import com.android.camera.captureintent.resource.ResourceConstructed;
50import com.android.camera.captureintent.resource.ResourceOpenedCamera;
51import com.android.camera.captureintent.resource.ResourceSurfaceTexture;
52import com.android.camera.captureintent.stateful.EventHandler;
53import com.android.camera.captureintent.stateful.State;
54import com.android.camera.captureintent.stateful.StateImpl;
55import com.android.camera.debug.Log;
56import com.android.camera.device.CameraId;
57import com.android.camera.one.OneCamera;
58import com.android.camera.one.OneCamera.Facing;
59import com.android.camera.one.OneCameraAccessException;
60import com.android.camera.one.OneCameraCharacteristics;
61import com.android.camera.session.CaptureSession;
62import com.android.camera.session.CaptureSessionManager;
63import com.android.camera.settings.Keys;
64import com.android.camera.settings.SettingsManager;
65import com.android.camera.ui.CountDownView;
66import com.android.camera.ui.TouchCoordinate;
67import com.android.camera.ui.focus.FocusController;
68import com.android.camera.util.Size;
69
70import com.google.common.base.Optional;
71
72import javax.annotation.Nullable;
73
74/**
75 * Represents a state that allows users to take a picture. The capture UI
76 * should be presented in this state so users can perform actions:
77 * 1. tap on shutter button to take a picture.
78 * 2. tap on viewfinder to focus.
79 * 3. switch between front and back camera.
80 */
81public final class StateReadyForCapture extends StateImpl {
82    private static final Log.Tag TAG = new Log.Tag("StateReadyCap");
83
84    private final RefCountBase<ResourceCaptureTools> mResourceCaptureTools;
85
86    private boolean mShouldUpdateTransformOnNextSurfaceTextureUpdate;
87    private boolean mIsCountingDown;
88    private boolean mIsTakingPicture;
89    private boolean mIsDecodingPicture;
90
91    public static StateReadyForCapture from(
92            StateStartingPreview startingPreview,
93            RefCountBase<ResourceConstructed> resourceConstructed,
94            RefCountBase<ResourceSurfaceTexture> resourceSurfaceTexture,
95            RefCountBase<ResourceOpenedCamera> resourceOpenedCamera) {
96        return new StateReadyForCapture(
97                startingPreview, resourceConstructed, resourceSurfaceTexture, resourceOpenedCamera);
98    }
99
100    public static StateReadyForCapture from(
101            StateReviewingPicture reviewingPicture,
102            RefCountBase<ResourceCaptureTools> resourceCaptureTools) {
103        return new StateReadyForCapture(reviewingPicture, resourceCaptureTools);
104    }
105
106    private StateReadyForCapture(
107            State previousState,
108            RefCountBase<ResourceConstructed> resourceConstructed,
109            RefCountBase<ResourceSurfaceTexture> resourceSurfaceTexture,
110            RefCountBase<ResourceOpenedCamera> resourceOpenedCamera) {
111        super(previousState);
112        mResourceCaptureTools = ResourceCaptureToolsImpl.create(
113                resourceConstructed, resourceSurfaceTexture, resourceOpenedCamera);
114        mIsCountingDown = false;
115        mIsTakingPicture = false;
116        mIsDecodingPicture = false;
117        mShouldUpdateTransformOnNextSurfaceTextureUpdate = true;
118        registerEventHandlers();
119    }
120
121    private StateReadyForCapture(
122            State previousState,
123            RefCountBase<ResourceCaptureTools> resourceCaptureTools) {
124        super(previousState);
125        mResourceCaptureTools = resourceCaptureTools;
126        mResourceCaptureTools.addRef();  // Will be balanced in onLeave().
127        mIsCountingDown = false;
128        mIsTakingPicture = false;
129        mIsDecodingPicture = false;
130        mShouldUpdateTransformOnNextSurfaceTextureUpdate = true;
131        registerEventHandlers();
132    }
133
134    private void takePicture(@Nullable final TouchCoordinate touchPointInsideShutterButton) {
135        final int countDownDuration =
136                mResourceCaptureTools.get().getResourceConstructed().get()
137                        .getSettingsManager().getInteger(
138                        SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION);
139
140        /** Prepare a CaptureLoggingInfo object. */
141        final ResourceCaptureTools.CaptureLoggingInfo captureLoggingInfo
142                = new ResourceCaptureTools.CaptureLoggingInfo() {
143            @Override
144            public TouchCoordinate getTouchPointInsideShutterButton() {
145                return touchPointInsideShutterButton;
146            }
147
148            @Override
149            public int getCountDownDuration() {
150                return countDownDuration;
151            }
152        };
153
154        /** Start counting down if the duration is not zero. */
155        if (countDownDuration > 0) {
156            startCountDown(countDownDuration, captureLoggingInfo);
157        } else {
158            /** Otherwise, just take a picture immediately. */
159            takePictureNow(captureLoggingInfo);
160        }
161    }
162
163    private void startCountDown(
164            final int countDownDuration,
165            final ResourceCaptureTools.CaptureLoggingInfo captureLoggingInfo) {
166        mIsCountingDown = true;
167        mResourceCaptureTools.get().getMainThread().execute(new Runnable() {
168            @Override
169            public void run() {
170                CaptureIntentModuleUI moduleUI = mResourceCaptureTools.get().getModuleUI();
171                moduleUI.setCountdownFinishedListener(
172                        new CountDownView.OnCountDownStatusListener() {
173                            @Override
174                            public void onRemainingSecondsChanged(
175                                    int remainingSeconds) {
176                                mResourceCaptureTools.get()
177                                        .playCountDownSound(remainingSeconds);
178                            }
179
180                            @Override
181                            public void onCountDownFinished() {
182                                getStateMachine().processEvent(
183                                        new EventTimerCountDownToZero(
184                                                captureLoggingInfo));
185                            }
186                        });
187                moduleUI.startCountdown(countDownDuration);
188            }
189        });
190    }
191
192    private void cancelCountDown() {
193        // Cancel in this state means that the countdown was cancelled.
194        mIsCountingDown = false;
195        mResourceCaptureTools.get().getMainThread().execute(new Runnable() {
196            @Override
197            public void run() {
198                mResourceCaptureTools.get().getModuleUI().cancelCountDown();
199                mResourceCaptureTools.get().getModuleUI().showPictureCaptureUI();
200            }
201        });
202    }
203
204    private void takePictureNow(ResourceCaptureTools.CaptureLoggingInfo captureLoggingInfo) {
205        mIsTakingPicture = true;
206        mResourceCaptureTools.get().takePictureNow(mPictureCallback, captureLoggingInfo);
207    }
208
209    private void registerEventHandlers() {
210        /** Handles EventPause. */
211        EventHandler<EventPause> pauseHandler = new EventHandler<EventPause>() {
212            @Override
213            public Optional<State> processEvent(EventPause event) {
214                return Optional.of((State) StateBackgroundWithSurfaceTexture.from(
215                        StateReadyForCapture.this,
216                        mResourceCaptureTools.get().getResourceConstructed(),
217                        mResourceCaptureTools.get().getResourceSurfaceTexture()));
218            }
219        };
220        setEventHandler(EventPause.class, pauseHandler);
221
222        /** Handles EventOnSurfaceTextureUpdated. */
223        EventHandler<EventOnSurfaceTextureUpdated> onSurfaceTextureUpdatedHandler =
224                new EventHandler<EventOnSurfaceTextureUpdated>() {
225                    @Override
226                    public Optional<State> processEvent(EventOnSurfaceTextureUpdated event) {
227                        if (mShouldUpdateTransformOnNextSurfaceTextureUpdate) {
228                            mShouldUpdateTransformOnNextSurfaceTextureUpdate = false;
229
230                            // We have to provide a preview layout size to
231                            // ResourceSurfaceTexture. Otherwise, it will
232                            // not be able to calculate transform matrix.
233                            Size previewSurfaceSize = mResourceCaptureTools.get().getModuleUI()
234                                    .getPreviewSurfaceSize();
235                            mResourceCaptureTools.get().getResourceSurfaceTexture().get()
236                                    .setPreviewLayoutSize(previewSurfaceSize);
237
238                            removeEventHandler(EventOnSurfaceTextureUpdated.class);
239                        }
240                        return NO_CHANGE;
241                    }
242                };
243        setEventHandler(EventOnSurfaceTextureUpdated.class, onSurfaceTextureUpdatedHandler);
244
245        /** Handles EventOnTextureViewLayoutChanged. */
246        EventHandler<EventOnTextureViewLayoutChanged> onTextureViewLayoutChangedHandler =
247                new EventHandler<EventOnTextureViewLayoutChanged>() {
248                    @Override
249                    public Optional<State> processEvent(EventOnTextureViewLayoutChanged event) {
250                        mResourceCaptureTools.get().getResourceSurfaceTexture().get()
251                                .setPreviewLayoutSize(event.getLayoutSize());
252                        return NO_CHANGE;
253                    }
254                };
255        setEventHandler(
256                EventOnTextureViewLayoutChanged.class, onTextureViewLayoutChangedHandler);
257
258        /** Handles EventCameraBusy. */
259        setEventHandler(
260                EventCameraBusy.class, mEventCameraBusyHandler);
261
262        /** Handles EventCameraReady. */
263        setEventHandler(
264                EventCameraReady.class, mEventCameraReadyHandler);
265
266        /** Handles EventTapOnShutterButton. */
267        EventHandler<EventTapOnShutterButton> tapOnShutterButtonHandler =
268                new EventHandler<EventTapOnShutterButton>() {
269                    @Override
270                    public Optional<State> processEvent(final EventTapOnShutterButton event) {
271                        takePicture(event.getTouchCoordinate());
272                        return NO_CHANGE;
273                    }
274                };
275        setEventHandler(EventTapOnShutterButton.class, tapOnShutterButtonHandler);
276
277        /** Handles EventClickOnCameraKey */
278        EventHandler<EventClickOnCameraKey> clickOnVolumeKeyHandler =
279                new EventHandler<EventClickOnCameraKey>() {
280                    @Override
281                    public Optional<State> processEvent(EventClickOnCameraKey event) {
282                        if (mIsCountingDown) {
283                            cancelCountDown();
284                            return NO_CHANGE;
285                        }
286                        takePicture(null);
287                        return NO_CHANGE;
288                    }
289                };
290        setEventHandler(EventClickOnCameraKey.class, clickOnVolumeKeyHandler);
291
292        /** Handles EventTimerCountDownToZero. */
293        EventHandler<EventTimerCountDownToZero> timerCountDownToZeroHandler =
294                new EventHandler<EventTimerCountDownToZero>() {
295                    @Override
296                    public Optional<State> processEvent(EventTimerCountDownToZero event) {
297                        if (mIsCountingDown) {
298                            mIsCountingDown = false;
299                            takePictureNow(event.getCaptureLoggingInfo());
300                        }
301                        return NO_CHANGE;
302                    }
303                };
304        setEventHandler(EventTimerCountDownToZero.class, timerCountDownToZeroHandler);
305
306        /** Handles EventTapOnSwitchCameraButton. */
307        EventHandler<EventTapOnSwitchCameraButton> tapOnSwitchCameraButtonHandler =
308                new EventHandler<EventTapOnSwitchCameraButton>() {
309                    @Override
310                    public Optional<State> processEvent(EventTapOnSwitchCameraButton event) {
311                        final ResourceConstructed resourceConstructed =
312                                mResourceCaptureTools.get().getResourceConstructed().get();
313
314                        // Freeze the screen.
315                        mResourceCaptureTools.get().getMainThread().execute(new Runnable() {
316                            @Override
317                            public void run() {
318                                resourceConstructed.getModuleUI().freezeScreenUntilPreviewReady();
319                            }
320                        });
321
322                        OneCamera.Facing cameraFacing =
323                                resourceConstructed.getCameraFacingSetting().getCameraFacing();
324                        CameraId cameraId =  resourceConstructed.getOneCameraManager()
325                              .findFirstCameraFacing(cameraFacing);
326                        OneCameraCharacteristics characteristics;
327                        try {
328                            characteristics = resourceConstructed.getOneCameraManager()
329                                    .getOneCameraCharacteristics(cameraId);
330                        } catch (OneCameraAccessException ex) {
331                            return Optional.of((State) StateFatal.from(
332                                    StateReadyForCapture.this,
333                                    mResourceCaptureTools.get().getResourceConstructed()));
334                        }
335
336                        return Optional.of((State) StateOpeningCamera.from(
337                                StateReadyForCapture.this,
338                                mResourceCaptureTools.get().getResourceConstructed(),
339                                mResourceCaptureTools.get().getResourceSurfaceTexture(),
340                                cameraFacing,
341                                cameraId,
342                                characteristics));
343                    }
344                };
345        setEventHandler(EventTapOnSwitchCameraButton.class, tapOnSwitchCameraButtonHandler);
346
347        /** Handles EventTapOnPreview. */
348        EventHandler<EventTapOnPreview> tapOnPreviewHandler = new EventHandler<EventTapOnPreview>() {
349            @Override
350            public Optional<State> processEvent(EventTapOnPreview event) {
351                OneCameraCharacteristics cameraCharacteristics = mResourceCaptureTools.get()
352                      .getResourceOpenedCamera().get().getCameraCharacteristics();
353                if (cameraCharacteristics.isAutoExposureSupported() ||
354                      cameraCharacteristics.isAutoFocusSupported()) {
355                    final Point tapPoint = event.getTapPoint();
356                    mResourceCaptureTools.get().getFocusController().showActiveFocusAt(
357                          tapPoint.x, tapPoint.y);
358
359                    RectF previewRect = mResourceCaptureTools.get().getModuleUI().getPreviewRect();
360                    int rotationDegree = mResourceCaptureTools.get().getResourceConstructed().get()
361                          .getOrientationManager().getDisplayRotation().getDegrees();
362
363                    // Normalize coordinates to [0,1] per CameraOne API.
364                    float points[] = new float[2];
365                    points[0] = (tapPoint.x - previewRect.left) / previewRect.width();
366                    points[1] = (tapPoint.y - previewRect.top) / previewRect.height();
367
368                    // Rotate coordinates to portrait orientation per CameraOne API.
369                    Matrix rotationMatrix = new Matrix();
370                    rotationMatrix.setRotate(rotationDegree, 0.5f, 0.5f);
371                    rotationMatrix.mapPoints(points);
372
373                    // Invert X coordinate on front camera since the display is mirrored.
374                    if (cameraCharacteristics.getCameraDirection() == Facing.FRONT) {
375                        points[0] = 1 - points[0];
376                    }
377
378                    mResourceCaptureTools.get().getResourceOpenedCamera().get()
379                          .triggerFocusAndMeterAtPoint(
380                                new PointF(points[0], points[1]));
381                }
382
383                return NO_CHANGE;
384            }
385        };
386        setEventHandler(EventTapOnPreview.class, tapOnPreviewHandler);
387
388        /** Handles EventZoomRatioChanged. */
389        EventHandler<EventZoomRatioChanged> zoomRatioChangedHandler =
390                new EventHandler<EventZoomRatioChanged>() {
391                    @Override
392                    public Optional<State> processEvent(EventZoomRatioChanged event) {
393                        mResourceCaptureTools.get().getResourceOpenedCamera().get().setZoomRatio(
394                                event.getZoomRatio());
395                        return NO_CHANGE;
396                    }
397                };
398        setEventHandler(EventZoomRatioChanged.class, zoomRatioChangedHandler);
399
400        /** Handles EventPictureCompressed. */
401        EventHandler<EventPictureCompressed> pictureCompressedHandler =
402                new EventHandler<EventPictureCompressed>() {
403                    @Override
404                    public Optional<State> processEvent(EventPictureCompressed event) {
405                        if (mIsTakingPicture) {
406                            mIsTakingPicture = false;
407                            mIsDecodingPicture = true;
408
409                            final byte[] pictureData = event.getPictureData();
410                            final int pictureOrientation = event.getOrientation();
411                            mResourceCaptureTools.get().getResourceConstructed().get().getCameraHandler().post(
412                                    new Runnable() {
413                                        @Override
414                                        public void run() {
415                                            final Bitmap pictureBitmap = PictureDecoder.decode(
416                                                    pictureData,
417                                                    CaptureIntentConfig.DOWN_SAMPLE_FACTOR,
418                                                    pictureOrientation,
419                                                    false);
420                                            getStateMachine().processEvent(
421                                                    new EventPictureDecoded(pictureBitmap, pictureData));
422                                        }
423                                    });
424                        }
425                        return NO_CHANGE;
426                    }
427                };
428        setEventHandler(EventPictureCompressed.class, pictureCompressedHandler);
429
430        /** Handles EventPictureDecoded. */
431        EventHandler<EventPictureDecoded> pictureDecodedHandler =
432                new EventHandler<EventPictureDecoded>() {
433                    @Override
434                    public Optional<State> processEvent(EventPictureDecoded event) {
435                        // Do nothing if we are not in the decoding image sub-state. There is a
436                        // chance that EventPictureDecoded for an old image might come after people
437                        // hitting retake button. We have to ignore it or it will take us to
438                        // StateReviewingPicture.
439                        if (!mIsDecodingPicture) {
440                            return NO_CHANGE;
441                        }
442
443                        mIsDecodingPicture = false;
444                        return Optional.of((State) StateReviewingPicture.from(
445                                StateReadyForCapture.this, mResourceCaptureTools,
446                                event.getPictureBitmap(), Optional.of(event.getPictureData())));
447                    }
448                };
449        setEventHandler(EventPictureDecoded.class, pictureDecodedHandler);
450
451        /** Handles EventFastPictureBitmapAvailable. */
452        EventHandler<EventFastPictureBitmapAvailable> fastPictureBitmapAvailableHandler =
453                new EventHandler<EventFastPictureBitmapAvailable>() {
454                    @Override
455                    public Optional<State> processEvent(EventFastPictureBitmapAvailable event) {
456                        if (mIsTakingPicture && !mIsDecodingPicture) {
457                            return Optional.of((State) StateReviewingPicture.from(
458                                    StateReadyForCapture.this, mResourceCaptureTools,
459                                    event.getThumbnailBitmap(), Optional.<byte[]>absent()));
460                        }
461                        return NO_CHANGE;
462                    }
463                };
464        setEventHandler(EventFastPictureBitmapAvailable.class, fastPictureBitmapAvailableHandler);
465
466        /** Handles EventCameraQuickExpose. */
467        EventHandler<EventCameraQuickExpose> cameraQuickExposeHandler =
468                new EventHandler<EventCameraQuickExpose>() {
469                    @Override
470                    public Optional<State> processEvent(EventCameraQuickExpose event) {
471                        if (mIsTakingPicture) {
472                            mResourceCaptureTools.get().getMainThread().execute(new Runnable() {
473                                @Override
474                                public void run() {
475
476                                    ResourceConstructed resourceConstructed =
477                                            mResourceCaptureTools.get().getResourceConstructed()
478                                                    .get();
479                                    // Freeze the screen.
480                                    resourceConstructed.getModuleUI()
481                                            .freezeScreenUntilPreviewReady();
482                                    // Disable shutter button.
483                                    mResourceCaptureTools.get().getModuleUI()
484                                            .setShutterButtonEnabled(false);
485                                    // Starts the short version of the capture animation UI.
486                                    mResourceCaptureTools.get().getModuleUI()
487                                            .startFlashAnimation(true);
488                                    mResourceCaptureTools.get().getMediaActionSound().play(
489                                            MediaActionSound.SHUTTER_CLICK);
490                                }
491                            });
492                        }
493                        return NO_CHANGE;
494                    }
495                };
496        setEventHandler(EventCameraQuickExpose.class, cameraQuickExposeHandler);
497
498        /** Handles EventTapOnCancelShutterButton. */
499        EventHandler<EventTapOnCancelShutterButton> tapOnCancelShutterButtonHandler =
500                new EventHandler<EventTapOnCancelShutterButton>() {
501                    @Override
502                    public Optional<State> processEvent(EventTapOnCancelShutterButton event) {
503                        cancelCountDown();
504                        return NO_CHANGE;
505                    }
506                };
507        setEventHandler(EventTapOnCancelShutterButton.class, tapOnCancelShutterButtonHandler);
508    }
509
510    @Override
511    public Optional<State> onEnter() {
512        // Register various listeners. These will be unregistered in onLeave().
513        final OneCamera camera =
514                mResourceCaptureTools.get().getResourceOpenedCamera().get().getCamera();
515        camera.setFocusDistanceListener(mResourceCaptureTools.get().getFocusController());
516        camera.setFocusStateListener(mFocusStateListener);
517        camera.setReadyStateChangedListener(mReadyStateChangedListener);
518        mResourceCaptureTools.get().getCaptureSessionManager()
519                .addSessionListener(mCaptureSessionListener);
520
521        // Display capture UI.
522        mResourceCaptureTools.get().getMainThread().execute(new Runnable() {
523            @Override
524            public void run() {
525                mResourceCaptureTools.get().getModuleUI().cancelCountDown();
526                mResourceCaptureTools.get().getModuleUI().showPictureCaptureUI();
527                mResourceCaptureTools.get().getModuleUI().initializeZoom(
528                        mResourceCaptureTools.get().getResourceOpenedCamera().get().getZoomRatio());
529            }
530        });
531        return NO_CHANGE;
532    }
533
534    @Override
535    public void onLeave() {
536        final OneCamera camera =
537                mResourceCaptureTools.get().getResourceOpenedCamera().get().getCamera();
538        camera.setFocusDistanceListener(null);
539        camera.setFocusStateListener(null);
540        camera.setReadyStateChangedListener(null);
541
542        mResourceCaptureTools.get().getCaptureSessionManager()
543                .removeSessionListener(mCaptureSessionListener);
544        mResourceCaptureTools.close();
545    }
546
547    private void onFocusStateUpdated(OneCamera.AutoFocusState focusState) {
548        final FocusController focusController = mResourceCaptureTools.get().getFocusController();
549        switch (focusState) {
550            case PASSIVE_SCAN:
551                focusController.showPassiveFocusAtCenter();
552                break;
553            case ACTIVE_SCAN:
554                break;
555            case PASSIVE_FOCUSED:
556            case PASSIVE_UNFOCUSED:
557                focusController.clearFocusIndicator();
558                break;
559            case ACTIVE_FOCUSED:
560            case ACTIVE_UNFOCUSED:
561                focusController.clearFocusIndicator();
562                break;
563        }
564    }
565
566    private final OneCamera.FocusStateListener mFocusStateListener =
567            new OneCamera.FocusStateListener() {
568                @Override
569                public void onFocusStatusUpdate(final OneCamera.AutoFocusState focusState,
570                        final long frameNumber) {
571                    onFocusStateUpdated(focusState);
572                }
573            };
574
575    private final EventHandler<EventCameraBusy> mEventCameraBusyHandler =
576            new EventHandler<EventCameraBusy>() {
577                @Override
578                public Optional<State> processEvent(EventCameraBusy event) {
579                    mResourceCaptureTools.get().getMainThread().execute(new Runnable() {
580                        @Override
581                        public void run() {
582                            mResourceCaptureTools.get().getModuleUI().setShutterButtonEnabled(
583                                    false);
584                        }
585                    });
586                    return NO_CHANGE;
587                }
588            };
589
590    private final EventHandler<EventCameraReady> mEventCameraReadyHandler =
591            new EventHandler<EventCameraReady>() {
592                @Override
593                public Optional<State> processEvent(EventCameraReady event) {
594                    mResourceCaptureTools.get().getMainThread().execute(new Runnable() {
595                        @Override
596                        public void run() {
597                            mResourceCaptureTools.get().getModuleUI().setShutterButtonEnabled(true);
598                        }
599                    });
600                    return NO_CHANGE;
601                }
602            };
603
604    private final OneCamera.ReadyStateChangedListener mReadyStateChangedListener =
605            new OneCamera.ReadyStateChangedListener() {
606                /**
607                 * Called when the camera is either ready or not ready to take a picture
608                 * right now.
609                 */
610                @Override
611                public void onReadyStateChanged(final boolean readyForCapture) {
612                    if (readyForCapture) {
613                        getStateMachine().processEvent(new EventCameraReady());
614                    } else {
615                        getStateMachine().processEvent(new EventCameraBusy());
616                    }
617                }
618            };
619
620    private final OneCamera.PictureCallback mPictureCallback = new OneCamera.PictureCallback() {
621        @Override
622        public void onQuickExpose() {
623            getStateMachine().processEvent(new EventCameraQuickExpose());
624        }
625
626        @Override
627        public void onThumbnailResult(byte[] jpegData) {
628        }
629
630        @Override
631        public void onPictureTaken(CaptureSession session) {
632        }
633
634        @Override
635        public void onPictureSaved(Uri uri) {
636        }
637
638        @Override
639        public void onPictureTakingFailed() {
640        }
641
642        @Override
643        public void onTakePictureProgress(float progress) {
644        }
645    };
646
647    private final CaptureSessionManager.SessionListener mCaptureSessionListener =
648            new CaptureSessionManager.SessionListener() {
649                @Override
650                public void onSessionThumbnailUpdate(Bitmap thumbnailBitmap) {
651                    getStateMachine().processEvent(
652                            new EventFastPictureBitmapAvailable(thumbnailBitmap));
653                }
654
655                @Override
656                public void onSessionPictureDataUpdate(byte[] pictureData, int orientation) {
657                    getStateMachine().processEvent(
658                            new EventPictureCompressed(pictureData, orientation));
659                }
660
661                @Override
662                public void onSessionQueued(Uri sessionUri) {
663                }
664
665                @Override
666                public void onSessionUpdated(Uri sessionUri) {
667                }
668
669                @Override
670                public void onSessionCaptureIndicatorUpdate(Bitmap bitmap, int rotationDegrees) {
671                }
672
673                @Override
674                public void onSessionDone(Uri sessionUri) {
675                }
676
677                @Override
678                public void onSessionFailed(Uri sessionUri, int failureMessageId,
679                        boolean removeFromFilmstrip) {
680                }
681
682                @Override
683                public void onSessionCanceled(Uri mediaUri) {
684                }
685
686                @Override
687                public void onSessionProgress(Uri sessionUri, int progress) {
688                }
689
690                @Override
691                public void onSessionProgressText(Uri sessionUri, int messageId) {
692                }
693            };
694}