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.Matrix;
23 import android.graphics.RectF;
24 import android.graphics.SurfaceTexture;
25 import android.hardware.Camera.Face;
26 import android.os.AsyncTask;
27 import android.os.Build;
28 import android.view.GestureDetector;
29 import android.view.MotionEvent;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.widget.FrameLayout;
33 import android.widget.ImageView;
35 import com.android.camera.FocusOverlayManager.FocusUI;
36 import com.android.camera.debug.DebugPropertyHelper;
37 import com.android.camera.debug.Log;
38 import com.android.camera.ui.CountDownView;
39 import com.android.camera.ui.FaceView;
40 import com.android.camera.ui.PreviewOverlay;
41 import com.android.camera.ui.PreviewStatusListener;
42 import com.android.camera.util.CameraUtil;
43 import com.android.camera.widget.AspectRatioDialogLayout;
44 import com.android.camera.widget.AspectRatioSelector;
45 import com.android.camera.widget.LocationDialogLayout;
46 import com.android.camera2.R;
47 import com.android.ex.camera2.portability.CameraAgent;
48 import com.android.ex.camera2.portability.CameraCapabilities;
49 import com.android.ex.camera2.portability.CameraSettings;
51 public class PhotoUI implements PreviewStatusListener,
52 CameraAgent.CameraFaceDetectionCallback, PreviewStatusListener.PreviewAreaChangedListener {
54 private static final Log.Tag TAG = new Log.Tag("PhotoUI");
55 private static final int DOWN_SAMPLE_FACTOR = 4;
56 private static final float UNSET = 0f;
58 private final PreviewOverlay mPreviewOverlay;
59 private final FocusUI mFocusUI;
60 private final CameraActivity mActivity;
61 private final PhotoController mController;
63 private final View mRootView;
64 private Dialog mDialog = null;
66 // TODO: Remove face view logic if UX does not bring it back within a month.
67 private final FaceView mFaceView;
68 private DecodeImageForReview mDecodeTaskForReview = null;
70 private float mZoomMax;
72 private int mPreviewWidth = 0;
73 private int mPreviewHeight = 0;
74 private float mAspectRatio = UNSET;
76 private ImageView mIntentReviewImageView;
78 private final GestureDetector.OnGestureListener mPreviewGestureListener
79 = new GestureDetector.SimpleOnGestureListener() {
81 public boolean onSingleTapUp(MotionEvent ev) {
82 mController.onSingleTapUp(null, (int) ev.getX(), (int) ev.getY());
86 private final DialogInterface.OnDismissListener mOnDismissListener
87 = new DialogInterface.OnDismissListener() {
89 public void onDismiss(DialogInterface dialog) {
93 private Runnable mRunnableForNextFrame = null;
94 private final CountDownView mCountdownView;
97 public GestureDetector.OnGestureListener getGestureListener() {
98 return mPreviewGestureListener;
102 public View.OnTouchListener getTouchListener() {
107 public void onPreviewLayoutChanged(View v, int left, int top, int right,
108 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
109 int width = right - left;
110 int height = bottom - top;
111 if (mPreviewWidth != width || mPreviewHeight != height) {
112 mPreviewWidth = width;
113 mPreviewHeight = height;
118 public boolean shouldAutoAdjustTransformMatrixOnLayout() {
123 public boolean shouldAutoAdjustBottomBar() {
128 public void onPreviewFlipped() {
129 mController.updateCameraOrientation();
133 * Sets the runnable to run when the next frame comes in.
135 public void setRunnableForNextFrame(Runnable runnable) {
136 mRunnableForNextFrame = runnable;
140 * Starts the countdown timer.
142 * @param sec seconds to countdown
144 public void startCountdown(int sec) {
145 mCountdownView.startCountDown(sec);
149 * Sets a listener that gets notified when the countdown is finished.
151 public void setCountdownFinishedListener(CountDownView.OnCountDownStatusListener listener) {
152 mCountdownView.setCountDownStatusListener(listener);
156 * Returns whether the countdown is on-going.
158 public boolean isCountingDown() {
159 return mCountdownView.isCountingDown();
163 * Cancels the on-going countdown, if any.
165 public void cancelCountDown() {
166 mCountdownView.cancelCountDown();
170 public void onPreviewAreaChanged(RectF previewArea) {
171 if (mFaceView != null) {
172 mFaceView.onPreviewAreaChanged(previewArea);
174 mCountdownView.onPreviewAreaChanged(previewArea);
177 private class DecodeTask extends AsyncTask<Void, Void, Bitmap> {
178 private final byte [] mData;
179 private final int mOrientation;
180 private final boolean mMirror;
182 public DecodeTask(byte[] data, int orientation, boolean mirror) {
184 mOrientation = orientation;
189 protected Bitmap doInBackground(Void... params) {
190 // Decode image in background.
191 Bitmap bitmap = CameraUtil.downSample(mData, DOWN_SAMPLE_FACTOR);
192 if (mOrientation != 0 || mMirror) {
193 Matrix m = new Matrix();
198 m.preRotate(mOrientation);
199 return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m,
206 private class DecodeImageForReview extends DecodeTask {
207 public DecodeImageForReview(byte[] data, int orientation, boolean mirror) {
208 super(data, orientation, mirror);
212 protected void onPostExecute(Bitmap bitmap) {
217 mIntentReviewImageView.setImageBitmap(bitmap);
218 showIntentReviewImageView();
220 mDecodeTaskForReview = null;
224 public PhotoUI(CameraActivity activity, PhotoController controller, View parent) {
225 mActivity = activity;
226 mController = controller;
229 ViewGroup moduleRoot = (ViewGroup) mRootView.findViewById(R.id.module_layout);
230 mActivity.getLayoutInflater().inflate(R.layout.photo_module,
233 mFocusUI = (FocusUI) mRootView.findViewById(R.id.focus_overlay);
234 mPreviewOverlay = (PreviewOverlay) mRootView.findViewById(R.id.preview_overlay);
235 mCountdownView = (CountDownView) mRootView.findViewById(R.id.count_down_view);
236 // Show faces if we are in debug mode.
237 if (DebugPropertyHelper.showCaptureDebugUI()) {
238 mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);
243 if (mController.isImageCaptureIntent()) {
244 initIntentReviewImageView();
248 private void initIntentReviewImageView() {
249 mIntentReviewImageView = (ImageView) mRootView.findViewById(R.id.intent_review_imageview);
250 mActivity.getCameraAppUI().addPreviewAreaChangedListener(
251 new PreviewStatusListener.PreviewAreaChangedListener() {
253 public void onPreviewAreaChanged(RectF previewArea) {
254 FrameLayout.LayoutParams params =
255 (FrameLayout.LayoutParams) mIntentReviewImageView.getLayoutParams();
256 params.width = (int) previewArea.width();
257 params.height = (int) previewArea.height();
258 params.setMargins((int) previewArea.left, (int) previewArea.top, 0, 0);
259 mIntentReviewImageView.setLayoutParams(params);
265 * Show the image review over the live preview for intent captures.
267 public void showIntentReviewImageView() {
268 if (mIntentReviewImageView != null) {
269 mIntentReviewImageView.setVisibility(View.VISIBLE);
274 * Hide the image review over the live preview for intent captures.
276 public void hideIntentReviewImageView() {
277 if (mIntentReviewImageView != null) {
278 mIntentReviewImageView.setVisibility(View.INVISIBLE);
283 public FocusUI getFocusUI() {
287 public void updatePreviewAspectRatio(float aspectRatio) {
288 if (aspectRatio <= 0) {
289 Log.e(TAG, "Invalid aspect ratio: " + aspectRatio);
292 if (aspectRatio < 1f) {
293 aspectRatio = 1f / aspectRatio;
296 if (mAspectRatio != aspectRatio) {
297 mAspectRatio = aspectRatio;
298 // Update transform matrix with the new aspect ratio.
299 mController.updatePreviewAspectRatio(mAspectRatio);
304 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
305 mController.onPreviewUIReady();
309 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
310 // Ignored, Camera does all the work for us
314 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
315 mController.onPreviewUIDestroyed();
320 public void onSurfaceTextureUpdated(SurfaceTexture surface) {
321 if (mRunnableForNextFrame != null) {
322 mRootView.post(mRunnableForNextFrame);
323 mRunnableForNextFrame = null;
327 public View getRootView() {
331 private void initIndicators() {
332 // TODO init toggle buttons on bottom bar here
335 public void onCameraOpened(CameraCapabilities capabilities, CameraSettings settings) {
336 initializeZoom(capabilities, settings);
339 public void animateCapture(final byte[] jpegData, int orientation, boolean mirror) {
340 // Decode jpeg byte array and then animate the jpeg
341 DecodeTask task = new DecodeTask(jpegData, orientation, mirror);
345 // called from onResume but only the first time
346 public void initializeFirstTime() {
350 // called from onResume every other time
351 public void initializeSecondTime(CameraCapabilities capabilities, CameraSettings settings) {
352 initializeZoom(capabilities, settings);
353 if (mController.isImageCaptureIntent()) {
354 hidePostCaptureAlert();
358 public void showLocationAndAspectRatioDialog(
359 final PhotoModule.LocationDialogCallback locationCallback,
360 final PhotoModule.AspectRatioDialogCallback aspectRatioDialogCallback) {
361 setDialog(new Dialog(mActivity,
362 android.R.style.Theme_Black_NoTitleBar_Fullscreen));
363 final LocationDialogLayout locationDialogLayout = (LocationDialogLayout) mActivity
364 .getLayoutInflater().inflate(R.layout.location_dialog_layout, null);
365 locationDialogLayout.setLocationTaggingSelectionListener(
366 new LocationDialogLayout.LocationTaggingSelectionListener() {
368 public void onLocationTaggingSelected(boolean selected) {
370 locationCallback.onLocationTaggingSelected(selected);
372 if (showAspectRatioDialogOnThisDevice()) {
374 showAspectRatioDialog(aspectRatioDialogCallback, mDialog);
376 // If we don't want to show the aspect ratio dialog,
377 // dismiss the dialog right after the user chose the
379 if (mDialog != null) {
385 mDialog.setContentView(locationDialogLayout, new ViewGroup.LayoutParams(
386 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
391 * Dismisses previous dialog if any, sets current dialog to the given dialog,
392 * and set the on dismiss listener for the given dialog.
393 * @param dialog dialog to show
395 private void setDialog(Dialog dialog) {
396 if (mDialog != null) {
397 mDialog.setOnDismissListener(null);
401 if (mDialog != null) {
402 mDialog.setOnDismissListener(mOnDismissListener);
407 * @return Whether the dialog was shown.
409 public boolean showAspectRatioDialog(final PhotoModule.AspectRatioDialogCallback callback) {
410 if (showAspectRatioDialogOnThisDevice()) {
411 setDialog(new Dialog(mActivity, android.R.style.Theme_Black_NoTitleBar_Fullscreen));
412 showAspectRatioDialog(callback, mDialog);
419 private boolean showAspectRatioDialog(final PhotoModule.AspectRatioDialogCallback callback,
420 final Dialog aspectRatioDialog) {
421 if (aspectRatioDialog == null) {
422 Log.e(TAG, "Dialog for aspect ratio is null.");
425 final AspectRatioDialogLayout aspectRatioDialogLayout =
426 (AspectRatioDialogLayout) mActivity
427 .getLayoutInflater().inflate(R.layout.aspect_ratio_dialog_layout, null);
428 aspectRatioDialogLayout.initialize(
429 new AspectRatioDialogLayout.AspectRatioChangedListener() {
431 public void onAspectRatioChanged(AspectRatioSelector.AspectRatio aspectRatio) {
432 // callback to set picture size.
433 callback.onAspectRatioSelected(aspectRatio, new Runnable() {
436 if (mDialog != null) {
442 }, callback.getCurrentAspectRatio());
443 aspectRatioDialog.setContentView(aspectRatioDialogLayout, new ViewGroup.LayoutParams(
444 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
445 aspectRatioDialog.show();
450 * @return Whether this is a device that we should show the aspect ratio
453 private boolean showAspectRatioDialogOnThisDevice() {
454 // We only want to show that dialog on N4 and N5
455 return "hammerhead".equals(Build.DEVICE) || "mako".equals(Build.DEVICE);
458 public void initializeZoom(CameraCapabilities capabilities, CameraSettings settings) {
459 if ((capabilities == null) || settings == null ||
460 !capabilities.supports(CameraCapabilities.Feature.ZOOM)) {
463 mZoomMax = capabilities.getMaxZoomRatio();
464 // Currently we use immediate zoom for fast zooming to get better UX and
465 // there is no plan to take advantage of the smooth zoom.
466 // TODO: Need to setup a path to AppUI to do this
467 mPreviewOverlay.setupZoom(mZoomMax, settings.getCurrentZoomRatio(),
468 new ZoomChangeListener());
471 public void animateFlash() {
472 mController.startPreCaptureAnimation();
475 public boolean onBackPressed() {
476 // In image capture mode, back button should:
477 // 1) if there is any popup, dismiss them, 2) otherwise, get out of
479 if (mController.isImageCaptureIntent()) {
480 mController.onCaptureCancelled();
482 } else if (!mController.isCameraIdle()) {
483 // ignore backs while we're taking a picture
490 protected void showCapturedImageForReview(byte[] jpegData, int orientation, boolean mirror) {
491 mDecodeTaskForReview = new DecodeImageForReview(jpegData, orientation, mirror);
492 mDecodeTaskForReview.execute();
494 mActivity.getCameraAppUI().transitionToIntentReviewLayout();
495 pauseFaceDetection();
498 protected void hidePostCaptureAlert() {
499 if (mDecodeTaskForReview != null) {
500 mDecodeTaskForReview.cancel(true);
502 resumeFaceDetection();
505 public void setDisplayOrientation(int orientation) {
506 if (mFaceView != null) {
507 mFaceView.setDisplayOrientation(orientation);
511 private class ZoomChangeListener implements PreviewOverlay.OnZoomChangedListener {
513 public void onZoomValueChanged(float ratio) {
514 mController.onZoomChanged(ratio);
518 public void onZoomStart() {
522 public void onZoomEnd() {
526 public void setSwipingEnabled(boolean enable) {
527 mActivity.setSwipingEnabled(enable);
530 public void onPause() {
531 if (mFaceView != null) {
534 if (mDialog != null) {
537 // recalculate aspect ratio when restarting.
541 public void clearFaces() {
542 if (mFaceView != null) {
547 public void pauseFaceDetection() {
548 if (mFaceView != null) {
553 public void resumeFaceDetection() {
554 if (mFaceView != null) {
559 public void onStartFaceDetection(int orientation, boolean mirror) {
560 if (mFaceView != null) {
562 mFaceView.setVisibility(View.VISIBLE);
563 mFaceView.setDisplayOrientation(orientation);
564 mFaceView.setMirror(mirror);
570 public void onFaceDetection(Face[] faces, CameraAgent.CameraProxy camera) {
571 if (mFaceView != null) {
572 mFaceView.setFaces(faces);