| /* |
| * 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); |
| } |
| } |
| |
| } |