blob: 5e5d353c46b80509e698f95c661ff8227b3e7144 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.camera;
import android.app.Dialog;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.Camera.Face;
import android.os.AsyncTask;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.camera.captureintent.PictureDecoder;
import com.android.camera.debug.DebugPropertyHelper;
import com.android.camera.debug.Log;
import com.android.camera.ui.CountDownView;
import com.android.camera.ui.FaceView;
import com.android.camera.ui.PreviewOverlay;
import com.android.camera.ui.PreviewStatusListener;
import com.android.camera.ui.focus.FocusRing;
import com.android.camera2.R;
import com.android.ex.camera2.portability.CameraAgent;
import com.android.ex.camera2.portability.CameraCapabilities;
import com.android.ex.camera2.portability.CameraSettings;
public class PhotoUI implements PreviewStatusListener,
CameraAgent.CameraFaceDetectionCallback, PreviewStatusListener.PreviewAreaChangedListener {
private static final Log.Tag TAG = new Log.Tag("PhotoUI");
private static final int DOWN_SAMPLE_FACTOR = 4;
private static final float UNSET = 0f;
private final PreviewOverlay mPreviewOverlay;
private final FocusRing mFocusRing;
private final CameraActivity mActivity;
private final PhotoController mController;
private final View mRootView;
private Dialog mDialog = null;
// TODO: Remove face view logic if UX does not bring it back within a month.
private final FaceView mFaceView;
private DecodeImageForReview mDecodeTaskForReview = null;
private float mZoomMax;
private int mPreviewWidth = 0;
private int mPreviewHeight = 0;
private float mAspectRatio = UNSET;
private ImageView mIntentReviewImageView;
private final GestureDetector.OnGestureListener mPreviewGestureListener
= new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent ev) {
mController.onSingleTapUp(null, (int) ev.getX(), (int) ev.getY());
return true;
}
};
private final DialogInterface.OnDismissListener mOnDismissListener
= new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
mDialog = null;
}
};
private final CountDownView mCountdownView;
@Override
public GestureDetector.OnGestureListener getGestureListener() {
return mPreviewGestureListener;
}
@Override
public View.OnTouchListener getTouchListener() {
return null;
}
@Override
public void onPreviewLayoutChanged(View v, int left, int top, int right,
int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
int width = right - left;
int height = bottom - top;
if (mPreviewWidth != width || mPreviewHeight != height) {
mPreviewWidth = width;
mPreviewHeight = height;
}
}
@Override
public boolean shouldAutoAdjustTransformMatrixOnLayout() {
return true;
}
@Override
public void onPreviewFlipped() {
mController.updateCameraOrientation();
}
/**
* Starts the countdown timer.
*
* @param sec seconds to countdown
*/
public void startCountdown(int sec) {
mCountdownView.startCountDown(sec);
}
/**
* Sets a listener that gets notified when the countdown is finished.
*/
public void setCountdownFinishedListener(CountDownView.OnCountDownStatusListener listener) {
mCountdownView.setCountDownStatusListener(listener);
}
/**
* Returns whether the countdown is on-going.
*/
public boolean isCountingDown() {
return mCountdownView.isCountingDown();
}
/**
* Cancels the on-going countdown, if any.
*/
public void cancelCountDown() {
mCountdownView.cancelCountDown();
}
@Override
public void onPreviewAreaChanged(RectF previewArea) {
if (mFaceView != null) {
mFaceView.onPreviewAreaChanged(previewArea);
}
mCountdownView.onPreviewAreaChanged(previewArea);
}
private class DecodeTask extends AsyncTask<Void, Void, Bitmap> {
private final byte [] mData;
private final int mOrientation;
private final boolean mMirror;
public DecodeTask(byte[] data, int orientation, boolean mirror) {
mData = data;
mOrientation = orientation;
mMirror = mirror;
}
@Override
protected Bitmap doInBackground(Void... params) {
// Decode image in background.
return PictureDecoder.decode(mData, DOWN_SAMPLE_FACTOR, mOrientation, mMirror);
}
}
private class DecodeImageForReview extends DecodeTask {
public DecodeImageForReview(byte[] data, int orientation, boolean mirror) {
super(data, orientation, mirror);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
return;
}
mIntentReviewImageView.setImageBitmap(bitmap);
showIntentReviewImageView();
mDecodeTaskForReview = null;
}
}
public PhotoUI(CameraActivity activity, PhotoController controller, View parent) {
mActivity = activity;
mController = controller;
mRootView = parent;
ViewGroup moduleRoot = (ViewGroup) mRootView.findViewById(R.id.module_layout);
mActivity.getLayoutInflater().inflate(R.layout.photo_module,
moduleRoot, true);
initIndicators();
mFocusRing = (FocusRing) mRootView.findViewById(R.id.focus_ring);
mPreviewOverlay = (PreviewOverlay) mRootView.findViewById(R.id.preview_overlay);
mCountdownView = (CountDownView) mRootView.findViewById(R.id.count_down_view);
// Show faces if we are in debug mode.
if (DebugPropertyHelper.showCaptureDebugUI()) {
mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);
} else {
mFaceView = null;
}
if (mController.isImageCaptureIntent()) {
initIntentReviewImageView();
}
}
private void initIntentReviewImageView() {
mIntentReviewImageView = (ImageView) mRootView.findViewById(R.id.intent_review_imageview);
mActivity.getCameraAppUI().addPreviewAreaChangedListener(
new PreviewStatusListener.PreviewAreaChangedListener() {
@Override
public void onPreviewAreaChanged(RectF previewArea) {
FrameLayout.LayoutParams params =
(FrameLayout.LayoutParams) mIntentReviewImageView.getLayoutParams();
params.width = (int) previewArea.width();
params.height = (int) previewArea.height();
params.setMargins((int) previewArea.left, (int) previewArea.top, 0, 0);
mIntentReviewImageView.setLayoutParams(params);
}
});
}
/**
* Show the image review over the live preview for intent captures.
*/
public void showIntentReviewImageView() {
if (mIntentReviewImageView != null) {
mIntentReviewImageView.setVisibility(View.VISIBLE);
}
}
/**
* Hide the image review over the live preview for intent captures.
*/
public void hideIntentReviewImageView() {
if (mIntentReviewImageView != null) {
mIntentReviewImageView.setVisibility(View.INVISIBLE);
}
}
public FocusRing getFocusRing() {
return mFocusRing;
}
public void updatePreviewAspectRatio(float aspectRatio) {
if (aspectRatio <= 0) {
Log.e(TAG, "Invalid aspect ratio: " + aspectRatio);
return;
}
if (aspectRatio < 1f) {
aspectRatio = 1f / aspectRatio;
}
if (mAspectRatio != aspectRatio) {
mAspectRatio = aspectRatio;
// Update transform matrix with the new aspect ratio.
mController.updatePreviewAspectRatio(mAspectRatio);
}
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
mController.onPreviewUIReady();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
// Ignored, Camera does all the work for us
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
mController.onPreviewUIDestroyed();
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
private void initIndicators() {
// TODO init toggle buttons on bottom bar here
}
public void onCameraOpened(CameraCapabilities capabilities, CameraSettings settings) {
initializeZoom(capabilities, settings);
}
public void animateCapture(final byte[] jpegData, int orientation, boolean mirror) {
// Decode jpeg byte array and then animate the jpeg
DecodeTask task = new DecodeTask(jpegData, orientation, mirror);
task.execute();
}
// called from onResume but only the first time
public void initializeFirstTime() {
}
// called from onResume every other time
public void initializeSecondTime(CameraCapabilities capabilities, CameraSettings settings) {
initializeZoom(capabilities, settings);
if (mController.isImageCaptureIntent()) {
hidePostCaptureAlert();
}
}
public void initializeZoom(CameraCapabilities capabilities, CameraSettings settings) {
if ((capabilities == null) || settings == null ||
!capabilities.supports(CameraCapabilities.Feature.ZOOM)) {
return;
}
mZoomMax = capabilities.getMaxZoomRatio();
// Currently we use immediate zoom for fast zooming to get better UX and
// there is no plan to take advantage of the smooth zoom.
// TODO: Need to setup a path to AppUI to do this
mPreviewOverlay.setupZoom(mZoomMax, settings.getCurrentZoomRatio(),
new ZoomChangeListener());
}
public void animateFlash() {
mController.startPreCaptureAnimation();
}
public boolean onBackPressed() {
// In image capture mode, back button should:
// 1) if there is any popup, dismiss them, 2) otherwise, get out of
// image capture
if (mController.isImageCaptureIntent()) {
mController.onCaptureCancelled();
return true;
} else if (!mController.isCameraIdle()) {
// ignore backs while we're taking a picture
return true;
} else {
return false;
}
}
protected void showCapturedImageForReview(byte[] jpegData, int orientation, boolean mirror) {
mDecodeTaskForReview = new DecodeImageForReview(jpegData, orientation, mirror);
mDecodeTaskForReview.execute();
mActivity.getCameraAppUI().transitionToIntentReviewLayout();
pauseFaceDetection();
}
protected void hidePostCaptureAlert() {
if (mDecodeTaskForReview != null) {
mDecodeTaskForReview.cancel(true);
}
resumeFaceDetection();
}
public void setDisplayOrientation(int orientation) {
if (mFaceView != null) {
mFaceView.setDisplayOrientation(orientation);
}
}
private class ZoomChangeListener implements PreviewOverlay.OnZoomChangedListener {
@Override
public void onZoomValueChanged(float ratio) {
mController.onZoomChanged(ratio);
}
@Override
public void onZoomStart() {
}
@Override
public void onZoomEnd() {
}
}
public void setSwipingEnabled(boolean enable) {
mActivity.setSwipingEnabled(enable);
}
public void onPause() {
if (mFaceView != null) {
mFaceView.clear();
}
if (mDialog != null) {
mDialog.dismiss();
}
// recalculate aspect ratio when restarting.
mAspectRatio = 0.0f;
}
public void clearFaces() {
if (mFaceView != null) {
mFaceView.clear();
}
}
public void pauseFaceDetection() {
if (mFaceView != null) {
mFaceView.pause();
}
}
public void resumeFaceDetection() {
if (mFaceView != null) {
mFaceView.resume();
}
}
public void onStartFaceDetection(int orientation, boolean mirror) {
if (mFaceView != null) {
mFaceView.clear();
mFaceView.setVisibility(View.VISIBLE);
mFaceView.setDisplayOrientation(orientation);
mFaceView.setMirror(mirror);
mFaceView.resume();
}
}
@Override
public void onFaceDetection(Face[] faces, CameraAgent.CameraProxy camera) {
if (mFaceView != null) {
mFaceView.setFaces(faces);
}
}
}