2 * Copyright (C) 2012 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;
19 import android.app.Dialog;
20 import android.content.DialogInterface;
21 import android.graphics.Bitmap;
22 import android.graphics.RectF;
23 import android.graphics.SurfaceTexture;
24 import android.hardware.Camera.Face;
25 import android.os.AsyncTask;
26 import android.view.GestureDetector;
27 import android.view.MotionEvent;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.widget.FrameLayout;
31 import android.widget.ImageView;
33 import com.android.camera.captureintent.PictureDecoder;
34 import com.android.camera.debug.DebugPropertyHelper;
35 import com.android.camera.debug.Log;
36 import com.android.camera.ui.CountDownView;
37 import com.android.camera.ui.FaceView;
38 import com.android.camera.ui.PreviewOverlay;
39 import com.android.camera.ui.PreviewStatusListener;
40 import com.android.camera.ui.focus.FocusRing;
41 import com.android.camera2.R;
42 import com.android.ex.camera2.portability.CameraAgent;
43 import com.android.ex.camera2.portability.CameraCapabilities;
44 import com.android.ex.camera2.portability.CameraSettings;
46 public class PhotoUI implements PreviewStatusListener,
47 CameraAgent.CameraFaceDetectionCallback, PreviewStatusListener.PreviewAreaChangedListener {
49 private static final Log.Tag TAG = new Log.Tag("PhotoUI");
50 private static final int DOWN_SAMPLE_FACTOR = 4;
51 private static final float UNSET = 0f;
53 private final PreviewOverlay mPreviewOverlay;
54 private final FocusRing mFocusRing;
55 private final CameraActivity mActivity;
56 private final PhotoController mController;
58 private final View mRootView;
59 private Dialog mDialog = null;
61 // TODO: Remove face view logic if UX does not bring it back within a month.
62 private final FaceView mFaceView;
63 private DecodeImageForReview mDecodeTaskForReview = null;
65 private float mZoomMax;
67 private int mPreviewWidth = 0;
68 private int mPreviewHeight = 0;
69 private float mAspectRatio = UNSET;
71 private ImageView mIntentReviewImageView;
73 private final GestureDetector.OnGestureListener mPreviewGestureListener
74 = new GestureDetector.SimpleOnGestureListener() {
76 public boolean onSingleTapUp(MotionEvent ev) {
77 mController.onSingleTapUp(null, (int) ev.getX(), (int) ev.getY());
81 private final DialogInterface.OnDismissListener mOnDismissListener
82 = new DialogInterface.OnDismissListener() {
84 public void onDismiss(DialogInterface dialog) {
88 private final CountDownView mCountdownView;
91 public GestureDetector.OnGestureListener getGestureListener() {
92 return mPreviewGestureListener;
96 public View.OnTouchListener getTouchListener() {
101 public void onPreviewLayoutChanged(View v, int left, int top, int right,
102 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
103 int width = right - left;
104 int height = bottom - top;
105 if (mPreviewWidth != width || mPreviewHeight != height) {
106 mPreviewWidth = width;
107 mPreviewHeight = height;
112 public boolean shouldAutoAdjustTransformMatrixOnLayout() {
117 public void onPreviewFlipped() {
118 mController.updateCameraOrientation();
122 * Starts the countdown timer.
124 * @param sec seconds to countdown
126 public void startCountdown(int sec) {
127 mCountdownView.startCountDown(sec);
131 * Sets a listener that gets notified when the countdown is finished.
133 public void setCountdownFinishedListener(CountDownView.OnCountDownStatusListener listener) {
134 mCountdownView.setCountDownStatusListener(listener);
138 * Returns whether the countdown is on-going.
140 public boolean isCountingDown() {
141 return mCountdownView.isCountingDown();
145 * Cancels the on-going countdown, if any.
147 public void cancelCountDown() {
148 mCountdownView.cancelCountDown();
152 public void onPreviewAreaChanged(RectF previewArea) {
153 if (mFaceView != null) {
154 mFaceView.onPreviewAreaChanged(previewArea);
156 mCountdownView.onPreviewAreaChanged(previewArea);
159 private class DecodeTask extends AsyncTask<Void, Void, Bitmap> {
160 private final byte [] mData;
161 private final int mOrientation;
162 private final boolean mMirror;
164 public DecodeTask(byte[] data, int orientation, boolean mirror) {
166 mOrientation = orientation;
171 protected Bitmap doInBackground(Void... params) {
172 // Decode image in background.
173 return PictureDecoder.decode(mData, DOWN_SAMPLE_FACTOR, mOrientation, mMirror);
177 private class DecodeImageForReview extends DecodeTask {
178 public DecodeImageForReview(byte[] data, int orientation, boolean mirror) {
179 super(data, orientation, mirror);
183 protected void onPostExecute(Bitmap bitmap) {
188 mIntentReviewImageView.setImageBitmap(bitmap);
189 showIntentReviewImageView();
191 mDecodeTaskForReview = null;
195 public PhotoUI(CameraActivity activity, PhotoController controller, View parent) {
196 mActivity = activity;
197 mController = controller;
200 ViewGroup moduleRoot = (ViewGroup) mRootView.findViewById(R.id.module_layout);
201 mActivity.getLayoutInflater().inflate(R.layout.photo_module,
204 mFocusRing = (FocusRing) mRootView.findViewById(R.id.focus_ring);
205 mPreviewOverlay = (PreviewOverlay) mRootView.findViewById(R.id.preview_overlay);
206 mCountdownView = (CountDownView) mRootView.findViewById(R.id.count_down_view);
207 // Show faces if we are in debug mode.
208 if (DebugPropertyHelper.showCaptureDebugUI()) {
209 mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);
214 if (mController.isImageCaptureIntent()) {
215 initIntentReviewImageView();
219 private void initIntentReviewImageView() {
220 mIntentReviewImageView = (ImageView) mRootView.findViewById(R.id.intent_review_imageview);
221 mActivity.getCameraAppUI().addPreviewAreaChangedListener(
222 new PreviewStatusListener.PreviewAreaChangedListener() {
224 public void onPreviewAreaChanged(RectF previewArea) {
225 FrameLayout.LayoutParams params =
226 (FrameLayout.LayoutParams) mIntentReviewImageView.getLayoutParams();
227 params.width = (int) previewArea.width();
228 params.height = (int) previewArea.height();
229 params.setMargins((int) previewArea.left, (int) previewArea.top, 0, 0);
230 mIntentReviewImageView.setLayoutParams(params);
236 * Show the image review over the live preview for intent captures.
238 public void showIntentReviewImageView() {
239 if (mIntentReviewImageView != null) {
240 mIntentReviewImageView.setVisibility(View.VISIBLE);
245 * Hide the image review over the live preview for intent captures.
247 public void hideIntentReviewImageView() {
248 if (mIntentReviewImageView != null) {
249 mIntentReviewImageView.setVisibility(View.INVISIBLE);
254 public FocusRing getFocusRing() {
258 public void updatePreviewAspectRatio(float aspectRatio) {
259 if (aspectRatio <= 0) {
260 Log.e(TAG, "Invalid aspect ratio: " + aspectRatio);
263 if (aspectRatio < 1f) {
264 aspectRatio = 1f / aspectRatio;
267 if (mAspectRatio != aspectRatio) {
268 mAspectRatio = aspectRatio;
269 // Update transform matrix with the new aspect ratio.
270 mController.updatePreviewAspectRatio(mAspectRatio);
275 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
276 mController.onPreviewUIReady();
280 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
281 // Ignored, Camera does all the work for us
285 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
286 mController.onPreviewUIDestroyed();
291 public void onSurfaceTextureUpdated(SurfaceTexture surface) {
294 private void initIndicators() {
295 // TODO init toggle buttons on bottom bar here
298 public void onCameraOpened(CameraCapabilities capabilities, CameraSettings settings) {
299 initializeZoom(capabilities, settings);
302 public void animateCapture(final byte[] jpegData, int orientation, boolean mirror) {
303 // Decode jpeg byte array and then animate the jpeg
304 DecodeTask task = new DecodeTask(jpegData, orientation, mirror);
308 // called from onResume but only the first time
309 public void initializeFirstTime() {
313 // called from onResume every other time
314 public void initializeSecondTime(CameraCapabilities capabilities, CameraSettings settings) {
315 initializeZoom(capabilities, settings);
316 if (mController.isImageCaptureIntent()) {
317 hidePostCaptureAlert();
321 public void initializeZoom(CameraCapabilities capabilities, CameraSettings settings) {
322 if ((capabilities == null) || settings == null ||
323 !capabilities.supports(CameraCapabilities.Feature.ZOOM)) {
326 mZoomMax = capabilities.getMaxZoomRatio();
327 // Currently we use immediate zoom for fast zooming to get better UX and
328 // there is no plan to take advantage of the smooth zoom.
329 // TODO: Need to setup a path to AppUI to do this
330 mPreviewOverlay.setupZoom(mZoomMax, settings.getCurrentZoomRatio(),
331 new ZoomChangeListener());
334 public void animateFlash() {
335 mController.startPreCaptureAnimation();
338 public boolean onBackPressed() {
339 // In image capture mode, back button should:
340 // 1) if there is any popup, dismiss them, 2) otherwise, get out of
342 if (mController.isImageCaptureIntent()) {
343 mController.onCaptureCancelled();
345 } else if (!mController.isCameraIdle()) {
346 // ignore backs while we're taking a picture
353 protected void showCapturedImageForReview(byte[] jpegData, int orientation, boolean mirror) {
354 mDecodeTaskForReview = new DecodeImageForReview(jpegData, orientation, mirror);
355 mDecodeTaskForReview.execute();
357 mActivity.getCameraAppUI().transitionToIntentReviewLayout();
358 pauseFaceDetection();
361 protected void hidePostCaptureAlert() {
362 if (mDecodeTaskForReview != null) {
363 mDecodeTaskForReview.cancel(true);
365 resumeFaceDetection();
368 public void setDisplayOrientation(int orientation) {
369 if (mFaceView != null) {
370 mFaceView.setDisplayOrientation(orientation);
374 private class ZoomChangeListener implements PreviewOverlay.OnZoomChangedListener {
376 public void onZoomValueChanged(float ratio) {
377 mController.onZoomChanged(ratio);
381 public void onZoomStart() {
385 public void onZoomEnd() {
389 public void setSwipingEnabled(boolean enable) {
390 mActivity.setSwipingEnabled(enable);
393 public void onPause() {
394 if (mFaceView != null) {
397 if (mDialog != null) {
400 // recalculate aspect ratio when restarting.
404 public void clearFaces() {
405 if (mFaceView != null) {
410 public void pauseFaceDetection() {
411 if (mFaceView != null) {
416 public void resumeFaceDetection() {
417 if (mFaceView != null) {
422 public void onStartFaceDetection(int orientation, boolean mirror) {
423 if (mFaceView != null) {
425 mFaceView.setVisibility(View.VISIBLE);
426 mFaceView.setDisplayOrientation(orientation);
427 mFaceView.setMirror(mirror);
433 public void onFaceDetection(Face[] faces, CameraAgent.CameraProxy camera) {
434 if (mFaceView != null) {
435 mFaceView.setFaces(faces);