2 * Copyright (C) 2015 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.camera.captureintent.state;
19 import android.graphics.Bitmap;
20 import android.graphics.Matrix;
21 import android.graphics.Point;
22 import android.graphics.PointF;
23 import android.graphics.RectF;
24 import android.media.MediaActionSound;
25 import android.net.Uri;
27 import com.android.camera.async.RefCountBase;
28 import com.android.camera.captureintent.CaptureIntentConfig;
29 import com.android.camera.captureintent.CaptureIntentModuleUI;
30 import com.android.camera.captureintent.PictureDecoder;
31 import com.android.camera.captureintent.event.EventCameraBusy;
32 import com.android.camera.captureintent.event.EventCameraQuickExpose;
33 import com.android.camera.captureintent.event.EventCameraReady;
34 import com.android.camera.captureintent.event.EventFastPictureBitmapAvailable;
35 import com.android.camera.captureintent.event.EventOnSurfaceTextureUpdated;
36 import com.android.camera.captureintent.event.EventOnTextureViewLayoutChanged;
37 import com.android.camera.captureintent.event.EventPause;
38 import com.android.camera.captureintent.event.EventPictureCompressed;
39 import com.android.camera.captureintent.event.EventPictureDecoded;
40 import com.android.camera.captureintent.event.EventTapOnCancelShutterButton;
41 import com.android.camera.captureintent.event.EventTapOnPreview;
42 import com.android.camera.captureintent.event.EventTapOnShutterButton;
43 import com.android.camera.captureintent.event.EventTapOnSwitchCameraButton;
44 import com.android.camera.captureintent.event.EventTimerCountDownToZero;
45 import com.android.camera.captureintent.event.EventZoomRatioChanged;
46 import com.android.camera.captureintent.resource.ResourceCaptureTools;
47 import com.android.camera.captureintent.resource.ResourceCaptureToolsImpl;
48 import com.android.camera.captureintent.resource.ResourceConstructed;
49 import com.android.camera.captureintent.resource.ResourceOpenedCamera;
50 import com.android.camera.captureintent.resource.ResourceSurfaceTexture;
51 import com.android.camera.captureintent.stateful.EventHandler;
52 import com.android.camera.captureintent.stateful.State;
53 import com.android.camera.captureintent.stateful.StateImpl;
54 import com.android.camera.debug.Log;
55 import com.android.camera.device.CameraId;
56 import com.android.camera.one.OneCamera;
57 import com.android.camera.one.OneCameraAccessException;
58 import com.android.camera.one.OneCameraCharacteristics;
59 import com.android.camera.session.CaptureSession;
60 import com.android.camera.session.CaptureSessionManager;
61 import com.android.camera.settings.Keys;
62 import com.android.camera.settings.SettingsManager;
63 import com.android.camera.ui.CountDownView;
64 import com.android.camera.ui.TouchCoordinate;
65 import com.android.camera.ui.focus.FocusController;
66 import com.google.common.base.Optional;
69 * Represents a state that allows users to take a picture. The capture UI
70 * should be presented in this state so users can perform actions:
71 * 1. tap on shutter button to take a picture.
72 * 2. tap on viewfinder to focus.
73 * 3. switch between front and back camera.
75 public final class StateReadyForCapture extends StateImpl {
76 private static final Log.Tag TAG = new Log.Tag("StateReadyCap");
78 private final RefCountBase<ResourceCaptureTools> mResourceCaptureTools;
80 private boolean mShouldUpdateTransformOnNextSurfaceTextureUpdate;
81 private boolean mIsCountingDown;
82 private boolean mIsTakingPicture;
83 private boolean mIsDecodingPicture;
85 public static StateReadyForCapture from(
86 StateStartingPreview startingPreview,
87 RefCountBase<ResourceConstructed> resourceConstructed,
88 RefCountBase<ResourceSurfaceTexture> resourceSurfaceTexture,
89 RefCountBase<ResourceOpenedCamera> resourceOpenedCamera) {
90 return new StateReadyForCapture(
91 startingPreview, resourceConstructed, resourceSurfaceTexture, resourceOpenedCamera);
94 public static StateReadyForCapture from(
95 StateReviewingPicture reviewingPicture,
96 RefCountBase<ResourceCaptureTools> resourceCaptureTools) {
97 return new StateReadyForCapture(reviewingPicture, resourceCaptureTools);
100 private StateReadyForCapture(
102 RefCountBase<ResourceConstructed> resourceConstructed,
103 RefCountBase<ResourceSurfaceTexture> resourceSurfaceTexture,
104 RefCountBase<ResourceOpenedCamera> resourceOpenedCamera) {
105 super(previousState);
106 mResourceCaptureTools = ResourceCaptureToolsImpl.create(
107 resourceConstructed, resourceSurfaceTexture, resourceOpenedCamera);
108 mIsCountingDown = false;
109 mIsTakingPicture = false;
110 mIsDecodingPicture = false;
111 mShouldUpdateTransformOnNextSurfaceTextureUpdate = true;
112 registerEventHandlers();
115 private StateReadyForCapture(
117 RefCountBase<ResourceCaptureTools> resourceCaptureTools) {
118 super(previousState);
119 mResourceCaptureTools = resourceCaptureTools;
120 mResourceCaptureTools.addRef(); // Will be balanced in onLeave().
121 mIsCountingDown = false;
122 mIsTakingPicture = false;
123 mIsDecodingPicture = false;
124 mShouldUpdateTransformOnNextSurfaceTextureUpdate = true;
125 registerEventHandlers();
128 private void registerEventHandlers() {
129 /** Handles EventPause. */
130 EventHandler<EventPause> pauseHandler = new EventHandler<EventPause>() {
132 public Optional<State> processEvent(EventPause event) {
133 return Optional.of((State) StateBackgroundWithSurfaceTexture.from(
134 StateReadyForCapture.this,
135 mResourceCaptureTools.get().getResourceConstructed(),
136 mResourceCaptureTools.get().getResourceSurfaceTexture()));
139 setEventHandler(EventPause.class, pauseHandler);
141 /** Handles EventOnSurfaceTextureUpdated. */
142 EventHandler<EventOnSurfaceTextureUpdated> onSurfaceTextureUpdatedHandler =
143 new EventHandler<EventOnSurfaceTextureUpdated>() {
145 public Optional<State> processEvent(EventOnSurfaceTextureUpdated event) {
146 if (mShouldUpdateTransformOnNextSurfaceTextureUpdate) {
147 mShouldUpdateTransformOnNextSurfaceTextureUpdate = false;
148 mResourceCaptureTools.get().getResourceSurfaceTexture().get()
149 .updatePreviewTransform();
150 removeEventHandler(EventOnSurfaceTextureUpdated.class);
155 setEventHandler(EventOnSurfaceTextureUpdated.class, onSurfaceTextureUpdatedHandler);
157 /** Handles EventOnTextureViewLayoutChanged. */
158 EventHandler<EventOnTextureViewLayoutChanged> onTextureViewLayoutChangedHandler =
159 new EventHandler<EventOnTextureViewLayoutChanged>() {
161 public Optional<State> processEvent(EventOnTextureViewLayoutChanged event) {
162 mResourceCaptureTools.get().getResourceSurfaceTexture().get()
163 .setPreviewLayoutSize(event.getLayoutSize());
168 EventOnTextureViewLayoutChanged.class, onTextureViewLayoutChangedHandler);
170 /** Handles EventCameraBusy. */
172 EventCameraBusy.class, mEventCameraBusyHandler);
174 /** Handles EventCameraReady. */
176 EventCameraReady.class, mEventCameraReadyHandler);
178 /** Handles EventTapOnShutterButton. */
179 EventHandler<EventTapOnShutterButton> tapOnShutterButtonHandler =
180 new EventHandler<EventTapOnShutterButton>() {
182 public Optional<State> processEvent(final EventTapOnShutterButton event) {
183 final int countDownDuration =
184 mResourceCaptureTools.get().getResourceConstructed().get()
185 .getSettingsManager().getInteger(
186 SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION);
188 /** Prepare a CaptureLoggingInfo object. */
189 final ResourceCaptureTools.CaptureLoggingInfo captureLoggingInfo
190 = new ResourceCaptureTools.CaptureLoggingInfo() {
192 public TouchCoordinate getTouchPointInsideShutterButton() {
193 return event.getTouchCoordinate();
197 public int getCountDownDuration() {
198 return countDownDuration;
202 /** Start counting down if the duration is not zero. */
203 if (countDownDuration > 0) {
204 mIsCountingDown = true;
205 mResourceCaptureTools.get().getMainThread().execute(new Runnable() {
208 CaptureIntentModuleUI moduleUI = mResourceCaptureTools.get().getModuleUI();
209 moduleUI.setCountdownFinishedListener(
210 new CountDownView.OnCountDownStatusListener() {
212 public void onRemainingSecondsChanged(
213 int remainingSeconds) {
214 mResourceCaptureTools.get()
215 .playCountDownSound(remainingSeconds);
219 public void onCountDownFinished() {
220 getStateMachine().processEvent(
221 new EventTimerCountDownToZero(
222 captureLoggingInfo));
225 moduleUI.startCountdown(
232 /** Otherwise, just take a picture immediately. */
233 mIsTakingPicture = true;
234 mResourceCaptureTools.get().takePictureNow(
235 mPictureCallback, captureLoggingInfo);
239 setEventHandler(EventTapOnShutterButton.class, tapOnShutterButtonHandler);
241 /** Handles EventTimerCountDownToZero. */
242 EventHandler<EventTimerCountDownToZero> timerCountDownToZeroHandler =
243 new EventHandler<EventTimerCountDownToZero>() {
245 public Optional<State> processEvent(EventTimerCountDownToZero event) {
246 if (mIsCountingDown) {
247 mIsCountingDown = false;
248 mIsTakingPicture = true;
249 mResourceCaptureTools.get().takePictureNow(
251 event.getCaptureLoggingInfo());
256 setEventHandler(EventTimerCountDownToZero.class, timerCountDownToZeroHandler);
258 /** Handles EventTapOnSwitchCameraButton. */
259 EventHandler<EventTapOnSwitchCameraButton> tapOnSwitchCameraButtonHandler =
260 new EventHandler<EventTapOnSwitchCameraButton>() {
262 public Optional<State> processEvent(EventTapOnSwitchCameraButton event) {
263 final ResourceConstructed resourceConstructed =
264 mResourceCaptureTools.get().getResourceConstructed().get();
266 // Freeze the screen.
267 mResourceCaptureTools.get().getMainThread().execute(new Runnable() {
270 resourceConstructed.getModuleUI().freezeScreenUntilPreviewReady();
274 OneCamera.Facing cameraFacing =
275 resourceConstructed.getCameraFacingSetting().getCameraFacing();
276 CameraId cameraId = resourceConstructed.getOneCameraManager()
277 .findFirstCameraFacing(cameraFacing);
278 OneCameraCharacteristics characteristics;
280 characteristics = resourceConstructed.getOneCameraManager()
281 .getOneCameraCharacteristics(cameraId);
282 } catch (OneCameraAccessException ex) {
283 return Optional.of((State) StateFatal.from(
284 StateReadyForCapture.this,
285 mResourceCaptureTools.get().getResourceConstructed()));
288 return Optional.of((State) StateOpeningCamera.from(
289 StateReadyForCapture.this,
290 mResourceCaptureTools.get().getResourceConstructed(),
291 mResourceCaptureTools.get().getResourceSurfaceTexture(),
297 setEventHandler(EventTapOnSwitchCameraButton.class, tapOnSwitchCameraButtonHandler);
299 /** Handles EventTapOnPreview. */
300 EventHandler<EventTapOnPreview> tapOnPreviewHandler = new EventHandler<EventTapOnPreview>() {
302 public Optional<State> processEvent(EventTapOnPreview event) {
303 final Point tapPoint = event.getTapPoint();
304 mResourceCaptureTools.get().getFocusController().showActiveFocusAt(
305 tapPoint.x, tapPoint.y);
307 RectF previewRect = mResourceCaptureTools.get().getModuleUI().getPreviewRect();
308 int rotationDegree = mResourceCaptureTools.get().getResourceConstructed().get()
309 .getOrientationManager().getDisplayRotation().getDegrees();
311 // Normalize coordinates to [0,1] per CameraOne API.
312 float points[] = new float[2];
313 points[0] = (tapPoint.x - previewRect.left) / previewRect.width();
314 points[1] = (tapPoint.y - previewRect.top) / previewRect.height();
316 // Rotate coordinates to portrait orientation per CameraOne API.
317 Matrix rotationMatrix = new Matrix();
318 rotationMatrix.setRotate(rotationDegree, 0.5f, 0.5f);
319 rotationMatrix.mapPoints(points);
320 mResourceCaptureTools.get().getResourceOpenedCamera().get().triggerFocusAndMeterAtPoint(
321 new PointF(points[0], points[1]));
326 setEventHandler(EventTapOnPreview.class, tapOnPreviewHandler);
328 /** Handles EventZoomRatioChanged. */
329 EventHandler<EventZoomRatioChanged> zoomRatioChangedHandler =
330 new EventHandler<EventZoomRatioChanged>() {
332 public Optional<State> processEvent(EventZoomRatioChanged event) {
333 mResourceCaptureTools.get().getResourceOpenedCamera().get().setZoomRatio(
334 event.getZoomRatio());
338 setEventHandler(EventZoomRatioChanged.class, zoomRatioChangedHandler);
340 /** Handles EventPictureCompressed. */
341 EventHandler<EventPictureCompressed> pictureCompressedHandler =
342 new EventHandler<EventPictureCompressed>() {
344 public Optional<State> processEvent(EventPictureCompressed event) {
345 if (mIsTakingPicture) {
346 mIsTakingPicture = false;
347 mIsDecodingPicture = true;
349 final byte[] pictureData = event.getPictureData();
350 final int pictureOrientation = event.getOrientation();
351 mResourceCaptureTools.get().getResourceConstructed().get().getCameraHandler().post(
355 final Bitmap pictureBitmap = PictureDecoder.decode(
357 CaptureIntentConfig.DOWN_SAMPLE_FACTOR,
360 getStateMachine().processEvent(
361 new EventPictureDecoded(pictureBitmap, pictureData));
368 setEventHandler(EventPictureCompressed.class, pictureCompressedHandler);
370 /** Handles EventPictureDecoded. */
371 EventHandler<EventPictureDecoded> pictureDecodedHandler =
372 new EventHandler<EventPictureDecoded>() {
374 public Optional<State> processEvent(EventPictureDecoded event) {
375 return Optional.of((State) StateReviewingPicture.from(
376 StateReadyForCapture.this, mResourceCaptureTools,
377 event.getPictureBitmap(), Optional.of(event.getPictureData())));
380 setEventHandler(EventPictureDecoded.class, pictureDecodedHandler);
382 /** Handles EventFastPictureBitmapAvailable. */
383 EventHandler<EventFastPictureBitmapAvailable> fastPictureBitmapAvailableHandler =
384 new EventHandler<EventFastPictureBitmapAvailable>() {
386 public Optional<State> processEvent(EventFastPictureBitmapAvailable event) {
387 if (mIsTakingPicture && !mIsDecodingPicture) {
388 return Optional.of((State) StateReviewingPicture.from(
389 StateReadyForCapture.this, mResourceCaptureTools,
390 event.getThumbnailBitmap(), Optional.<byte[]>absent()));
395 setEventHandler(EventFastPictureBitmapAvailable.class, fastPictureBitmapAvailableHandler);
397 /** Handles EventCameraQuickExpose. */
398 EventHandler<EventCameraQuickExpose> cameraQuickExposeHandler =
399 new EventHandler<EventCameraQuickExpose>() {
401 public Optional<State> processEvent(EventCameraQuickExpose event) {
402 if (mIsTakingPicture) {
403 mResourceCaptureTools.get().getMainThread().execute(new Runnable() {
406 // Disable shutter button.
407 mResourceCaptureTools.get().getModuleUI().setShutterButtonEnabled(false);
408 // Starts the short version of the capture animation UI.
409 mResourceCaptureTools.get().getModuleUI().startFlashAnimation(true);
410 mResourceCaptureTools.get().getMediaActionSound().play(
411 MediaActionSound.SHUTTER_CLICK);
418 setEventHandler(EventCameraQuickExpose.class, cameraQuickExposeHandler);
420 /** Handles EventTapOnCancelShutterButton. */
421 EventHandler<EventTapOnCancelShutterButton> tapOnCancelShutterButtonHandler =
422 new EventHandler<EventTapOnCancelShutterButton>() {
424 public Optional<State> processEvent(EventTapOnCancelShutterButton event) {
425 // Cancel in this state means that the countdown was cancelled.
426 mIsCountingDown = false;
427 mResourceCaptureTools.get().getMainThread().execute(new Runnable() {
430 mResourceCaptureTools.get().getModuleUI().cancelCountDown();
431 mResourceCaptureTools.get().getModuleUI().showPictureCaptureUI();
437 setEventHandler(EventTapOnCancelShutterButton.class, tapOnCancelShutterButtonHandler);
441 public Optional<State> onEnter() {
442 // Register various listeners. These will be unregistered in onLeave().
443 final OneCamera camera =
444 mResourceCaptureTools.get().getResourceOpenedCamera().get().getCamera();
445 camera.setFocusDistanceListener(mResourceCaptureTools.get().getFocusController());
446 camera.setFocusStateListener(mFocusStateListener);
447 camera.setReadyStateChangedListener(mReadyStateChangedListener);
448 mResourceCaptureTools.get().getCaptureSessionManager()
449 .addSessionListener(mCaptureSessionListener);
451 // Display capture UI.
452 mResourceCaptureTools.get().getMainThread().execute(new Runnable() {
455 mResourceCaptureTools.get().getModuleUI().cancelCountDown();
456 mResourceCaptureTools.get().getModuleUI().showPictureCaptureUI();
457 mResourceCaptureTools.get().getModuleUI().initializeZoom(
458 mResourceCaptureTools.get().getResourceOpenedCamera().get().getZoomRatio());
465 public void onLeave() {
466 final OneCamera camera =
467 mResourceCaptureTools.get().getResourceOpenedCamera().get().getCamera();
468 camera.setFocusDistanceListener(null);
469 camera.setFocusStateListener(null);
470 camera.setReadyStateChangedListener(null);
472 mResourceCaptureTools.get().getCaptureSessionManager()
473 .removeSessionListener(mCaptureSessionListener);
474 mResourceCaptureTools.close();
477 private void onFocusStateUpdated(OneCamera.AutoFocusState focusState) {
478 final FocusController focusController = mResourceCaptureTools.get().getFocusController();
479 switch (focusState) {
481 focusController.showPassiveFocusAtCenter();
485 case PASSIVE_FOCUSED:
486 case PASSIVE_UNFOCUSED:
487 focusController.clearFocusIndicator();
490 case ACTIVE_UNFOCUSED:
491 focusController.clearFocusIndicator();
496 private final OneCamera.FocusStateListener mFocusStateListener =
497 new OneCamera.FocusStateListener() {
499 public void onFocusStatusUpdate(final OneCamera.AutoFocusState focusState,
500 final long frameNumber) {
501 onFocusStateUpdated(focusState);
505 private final EventHandler<EventCameraBusy> mEventCameraBusyHandler =
506 new EventHandler<EventCameraBusy>() {
508 public Optional<State> processEvent(EventCameraBusy event) {
509 mResourceCaptureTools.get().getMainThread().execute(new Runnable() {
512 mResourceCaptureTools.get().getModuleUI().setShutterButtonEnabled(
520 private final EventHandler<EventCameraReady> mEventCameraReadyHandler =
521 new EventHandler<EventCameraReady>() {
523 public Optional<State> processEvent(EventCameraReady event) {
524 mResourceCaptureTools.get().getMainThread().execute(new Runnable() {
527 mResourceCaptureTools.get().getModuleUI().setShutterButtonEnabled(true);
534 private final OneCamera.ReadyStateChangedListener mReadyStateChangedListener =
535 new OneCamera.ReadyStateChangedListener() {
537 * Called when the camera is either ready or not ready to take a picture
541 public void onReadyStateChanged(final boolean readyForCapture) {
542 if (readyForCapture) {
543 getStateMachine().processEvent(new EventCameraReady());
545 getStateMachine().processEvent(new EventCameraBusy());
550 private final OneCamera.PictureCallback mPictureCallback = new OneCamera.PictureCallback() {
552 public void onQuickExpose() {
553 getStateMachine().processEvent(new EventCameraQuickExpose());
557 public void onThumbnailResult(byte[] jpegData) {
561 public void onPictureTaken(CaptureSession session) {
565 public void onPictureSaved(Uri uri) {
569 public void onPictureTakingFailed() {
573 public void onTakePictureProgress(float progress) {
577 private final CaptureSessionManager.SessionListener mCaptureSessionListener =
578 new CaptureSessionManager.SessionListener() {
580 public void onSessionThumbnailUpdate(Bitmap thumbnailBitmap) {
581 getStateMachine().processEvent(
582 new EventFastPictureBitmapAvailable(thumbnailBitmap));
586 public void onSessionPictureDataUpdate(byte[] pictureData, int orientation) {
587 getStateMachine().processEvent(
588 new EventPictureCompressed(pictureData, orientation));
592 public void onSessionQueued(Uri sessionUri) {
596 public void onSessionUpdated(Uri sessionUri) {
600 public void onSessionCaptureIndicatorUpdate(Bitmap bitmap, int rotationDegrees) {
604 public void onSessionDone(Uri sessionUri) {
608 public void onSessionFailed(Uri sessionUri, int failureMessageId,
609 boolean removeFromFilmstrip) {
613 public void onSessionCanceled(Uri mediaUri) {
617 public void onSessionProgress(Uri sessionUri, int progress) {
621 public void onSessionProgressText(Uri sessionUri, int messageId) {