Home | History | Annotate | Download | only in camera
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      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
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     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.
     15  */
     16 
     17 package com.android.camera;
     18 
     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;
     32 
     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;
     45 
     46 public class PhotoUI implements PreviewStatusListener,
     47     CameraAgent.CameraFaceDetectionCallback, PreviewStatusListener.PreviewAreaChangedListener {
     48 
     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;
     52 
     53     private final PreviewOverlay mPreviewOverlay;
     54     private final FocusRing mFocusRing;
     55     private final CameraActivity mActivity;
     56     private final PhotoController mController;
     57 
     58     private final View mRootView;
     59     private Dialog mDialog = null;
     60 
     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;
     64 
     65     private float mZoomMax;
     66 
     67     private int mPreviewWidth = 0;
     68     private int mPreviewHeight = 0;
     69     private float mAspectRatio = UNSET;
     70 
     71     private ImageView mIntentReviewImageView;
     72 
     73     private final GestureDetector.OnGestureListener mPreviewGestureListener
     74             = new GestureDetector.SimpleOnGestureListener() {
     75         @Override
     76         public boolean onSingleTapUp(MotionEvent ev) {
     77             mController.onSingleTapUp(null, (int) ev.getX(), (int) ev.getY());
     78             return true;
     79         }
     80     };
     81     private final DialogInterface.OnDismissListener mOnDismissListener
     82             = new DialogInterface.OnDismissListener() {
     83         @Override
     84         public void onDismiss(DialogInterface dialog) {
     85             mDialog = null;
     86         }
     87     };
     88     private final CountDownView mCountdownView;
     89 
     90     @Override
     91     public GestureDetector.OnGestureListener getGestureListener() {
     92         return mPreviewGestureListener;
     93     }
     94 
     95     @Override
     96     public View.OnTouchListener getTouchListener() {
     97         return null;
     98     }
     99 
    100     @Override
    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;
    108         }
    109     }
    110 
    111     @Override
    112     public boolean shouldAutoAdjustTransformMatrixOnLayout() {
    113         return true;
    114     }
    115 
    116     @Override
    117     public void onPreviewFlipped() {
    118         mController.updateCameraOrientation();
    119     }
    120 
    121     /**
    122      * Starts the countdown timer.
    123      *
    124      * @param sec seconds to countdown
    125      */
    126     public void startCountdown(int sec) {
    127         mCountdownView.startCountDown(sec);
    128     }
    129 
    130     /**
    131      * Sets a listener that gets notified when the countdown is finished.
    132      */
    133     public void setCountdownFinishedListener(CountDownView.OnCountDownStatusListener listener) {
    134         mCountdownView.setCountDownStatusListener(listener);
    135     }
    136 
    137     /**
    138      * Returns whether the countdown is on-going.
    139      */
    140     public boolean isCountingDown() {
    141         return mCountdownView.isCountingDown();
    142     }
    143 
    144     /**
    145      * Cancels the on-going countdown, if any.
    146      */
    147     public void cancelCountDown() {
    148         mCountdownView.cancelCountDown();
    149     }
    150 
    151     @Override
    152     public void onPreviewAreaChanged(RectF previewArea) {
    153         if (mFaceView != null) {
    154             mFaceView.onPreviewAreaChanged(previewArea);
    155         }
    156         mCountdownView.onPreviewAreaChanged(previewArea);
    157     }
    158 
    159     private class DecodeTask extends AsyncTask<Void, Void, Bitmap> {
    160         private final byte [] mData;
    161         private final int mOrientation;
    162         private final boolean mMirror;
    163 
    164         public DecodeTask(byte[] data, int orientation, boolean mirror) {
    165             mData = data;
    166             mOrientation = orientation;
    167             mMirror = mirror;
    168         }
    169 
    170         @Override
    171         protected Bitmap doInBackground(Void... params) {
    172             // Decode image in background.
    173             return PictureDecoder.decode(mData, DOWN_SAMPLE_FACTOR, mOrientation, mMirror);
    174         }
    175     }
    176 
    177     private class DecodeImageForReview extends DecodeTask {
    178         public DecodeImageForReview(byte[] data, int orientation, boolean mirror) {
    179             super(data, orientation, mirror);
    180         }
    181 
    182         @Override
    183         protected void onPostExecute(Bitmap bitmap) {
    184             if (isCancelled()) {
    185                 return;
    186             }
    187 
    188             mIntentReviewImageView.setImageBitmap(bitmap);
    189             showIntentReviewImageView();
    190 
    191             mDecodeTaskForReview = null;
    192         }
    193     }
    194 
    195     public PhotoUI(CameraActivity activity, PhotoController controller, View parent) {
    196         mActivity = activity;
    197         mController = controller;
    198         mRootView = parent;
    199 
    200         ViewGroup moduleRoot = (ViewGroup) mRootView.findViewById(R.id.module_layout);
    201         mActivity.getLayoutInflater().inflate(R.layout.photo_module,
    202                  moduleRoot, true);
    203         initIndicators();
    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);
    210         } else {
    211             mFaceView = null;
    212         }
    213 
    214         if (mController.isImageCaptureIntent()) {
    215             initIntentReviewImageView();
    216         }
    217     }
    218 
    219     private void initIntentReviewImageView() {
    220         mIntentReviewImageView = (ImageView) mRootView.findViewById(R.id.intent_review_imageview);
    221         mActivity.getCameraAppUI().addPreviewAreaChangedListener(
    222                 new PreviewStatusListener.PreviewAreaChangedListener() {
    223                     @Override
    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);
    231                     }
    232                 });
    233     }
    234 
    235     /**
    236      * Show the image review over the live preview for intent captures.
    237      */
    238     public void showIntentReviewImageView() {
    239         if (mIntentReviewImageView != null) {
    240             mIntentReviewImageView.setVisibility(View.VISIBLE);
    241         }
    242     }
    243 
    244     /**
    245      * Hide the image review over the live preview for intent captures.
    246      */
    247     public void hideIntentReviewImageView() {
    248         if (mIntentReviewImageView != null) {
    249             mIntentReviewImageView.setVisibility(View.INVISIBLE);
    250         }
    251     }
    252 
    253 
    254     public FocusRing getFocusRing() {
    255         return mFocusRing;
    256     }
    257 
    258     public void updatePreviewAspectRatio(float aspectRatio) {
    259         if (aspectRatio <= 0) {
    260             Log.e(TAG, "Invalid aspect ratio: " + aspectRatio);
    261             return;
    262         }
    263         if (aspectRatio < 1f) {
    264             aspectRatio = 1f / aspectRatio;
    265         }
    266 
    267         if (mAspectRatio != aspectRatio) {
    268             mAspectRatio = aspectRatio;
    269             // Update transform matrix with the new aspect ratio.
    270             mController.updatePreviewAspectRatio(mAspectRatio);
    271         }
    272     }
    273 
    274     @Override
    275     public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
    276         mController.onPreviewUIReady();
    277     }
    278 
    279     @Override
    280     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
    281         // Ignored, Camera does all the work for us
    282     }
    283 
    284     @Override
    285     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
    286         mController.onPreviewUIDestroyed();
    287         return true;
    288     }
    289 
    290     @Override
    291     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
    292     }
    293 
    294     private void initIndicators() {
    295         // TODO init toggle buttons on bottom bar here
    296     }
    297 
    298     public void onCameraOpened(CameraCapabilities capabilities, CameraSettings settings) {
    299         initializeZoom(capabilities, settings);
    300     }
    301 
    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);
    305         task.execute();
    306     }
    307 
    308     // called from onResume but only the first time
    309     public void initializeFirstTime() {
    310 
    311     }
    312 
    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();
    318         }
    319     }
    320 
    321     public void initializeZoom(CameraCapabilities capabilities, CameraSettings settings) {
    322         if ((capabilities == null) || settings == null ||
    323                 !capabilities.supports(CameraCapabilities.Feature.ZOOM)) {
    324             return;
    325         }
    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());
    332     }
    333 
    334     public void animateFlash() {
    335         mController.startPreCaptureAnimation();
    336     }
    337 
    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
    341         // image capture
    342         if (mController.isImageCaptureIntent()) {
    343             mController.onCaptureCancelled();
    344             return true;
    345         } else if (!mController.isCameraIdle()) {
    346             // ignore backs while we're taking a picture
    347             return true;
    348         } else {
    349             return false;
    350         }
    351     }
    352 
    353     protected void showCapturedImageForReview(byte[] jpegData, int orientation, boolean mirror) {
    354         mDecodeTaskForReview = new DecodeImageForReview(jpegData, orientation, mirror);
    355         mDecodeTaskForReview.execute();
    356 
    357         mActivity.getCameraAppUI().transitionToIntentReviewLayout();
    358         pauseFaceDetection();
    359     }
    360 
    361     protected void hidePostCaptureAlert() {
    362         if (mDecodeTaskForReview != null) {
    363             mDecodeTaskForReview.cancel(true);
    364         }
    365         resumeFaceDetection();
    366     }
    367 
    368     public void setDisplayOrientation(int orientation) {
    369         if (mFaceView != null) {
    370             mFaceView.setDisplayOrientation(orientation);
    371         }
    372     }
    373 
    374     private class ZoomChangeListener implements PreviewOverlay.OnZoomChangedListener {
    375         @Override
    376         public void onZoomValueChanged(float ratio) {
    377             mController.onZoomChanged(ratio);
    378         }
    379 
    380         @Override
    381         public void onZoomStart() {
    382         }
    383 
    384         @Override
    385         public void onZoomEnd() {
    386         }
    387     }
    388 
    389     public void setSwipingEnabled(boolean enable) {
    390         mActivity.setSwipingEnabled(enable);
    391     }
    392 
    393     public void onPause() {
    394         if (mFaceView != null) {
    395             mFaceView.clear();
    396         }
    397         if (mDialog != null) {
    398             mDialog.dismiss();
    399         }
    400         // recalculate aspect ratio when restarting.
    401         mAspectRatio = 0.0f;
    402     }
    403 
    404     public void clearFaces() {
    405         if (mFaceView != null) {
    406             mFaceView.clear();
    407         }
    408     }
    409 
    410     public void pauseFaceDetection() {
    411         if (mFaceView != null) {
    412             mFaceView.pause();
    413         }
    414     }
    415 
    416     public void resumeFaceDetection() {
    417         if (mFaceView != null) {
    418             mFaceView.resume();
    419         }
    420     }
    421 
    422     public void onStartFaceDetection(int orientation, boolean mirror) {
    423         if (mFaceView != null) {
    424             mFaceView.clear();
    425             mFaceView.setVisibility(View.VISIBLE);
    426             mFaceView.setDisplayOrientation(orientation);
    427             mFaceView.setMirror(mirror);
    428             mFaceView.resume();
    429         }
    430     }
    431 
    432     @Override
    433     public void onFaceDetection(Face[] faces, CameraAgent.CameraProxy camera) {
    434         if (mFaceView != null) {
    435             mFaceView.setFaces(faces);
    436         }
    437     }
    438 
    439 }
    440