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 
     18 package com.android.camera;
     19 
     20 import android.app.AlertDialog;
     21 import android.content.DialogInterface;
     22 import android.graphics.Bitmap;
     23 import android.graphics.Color;
     24 import android.graphics.Matrix;
     25 import android.graphics.RectF;
     26 import android.graphics.SurfaceTexture;
     27 import android.graphics.drawable.ColorDrawable;
     28 import android.hardware.Camera;
     29 import android.hardware.Camera.Face;
     30 import android.os.AsyncTask;
     31 import android.util.Log;
     32 import android.view.Gravity;
     33 import android.view.TextureView;
     34 import android.view.View;
     35 import android.view.View.OnClickListener;
     36 import android.view.View.OnLayoutChangeListener;
     37 import android.view.ViewGroup;
     38 import android.view.ViewStub;
     39 import android.widget.FrameLayout.LayoutParams;
     40 import android.widget.ImageView;
     41 import android.widget.PopupWindow;
     42 import android.widget.Toast;
     43 
     44 import com.android.camera.CameraPreference.OnPreferenceChangedListener;
     45 import com.android.camera.FocusOverlayManager.FocusUI;
     46 import com.android.camera.ui.AbstractSettingPopup;
     47 import com.android.camera.ui.CameraControls;
     48 import com.android.camera.ui.CameraRootView;
     49 import com.android.camera.ui.CountDownView;
     50 import com.android.camera.ui.CountDownView.OnCountDownFinishedListener;
     51 import com.android.camera.ui.FaceView;
     52 import com.android.camera.ui.FocusIndicator;
     53 import com.android.camera.ui.ModuleSwitcher;
     54 import com.android.camera.ui.PieRenderer;
     55 import com.android.camera.ui.PieRenderer.PieListener;
     56 import com.android.camera.ui.RenderOverlay;
     57 import com.android.camera.ui.ZoomRenderer;
     58 import com.android.camera.util.CameraUtil;
     59 import com.android.camera2.R;
     60 
     61 import java.util.List;
     62 
     63 public class PhotoUI implements PieListener,
     64     PreviewGestures.SingleTapListener,
     65     FocusUI, TextureView.SurfaceTextureListener,
     66     LocationManager.Listener, CameraRootView.MyDisplayListener,
     67     CameraManager.CameraFaceDetectionCallback {
     68 
     69     private static final String TAG = "CAM_UI";
     70     private static final int DOWN_SAMPLE_FACTOR = 4;
     71     private final AnimationManager mAnimationManager;
     72     private CameraActivity mActivity;
     73     private PhotoController mController;
     74     private PreviewGestures mGestures;
     75 
     76     private View mRootView;
     77     private SurfaceTexture mSurfaceTexture;
     78 
     79     private PopupWindow mPopup;
     80     private ShutterButton mShutterButton;
     81     private CountDownView mCountDownView;
     82 
     83     private FaceView mFaceView;
     84     private RenderOverlay mRenderOverlay;
     85     private View mReviewCancelButton;
     86     private View mReviewDoneButton;
     87     private View mReviewRetakeButton;
     88     private ImageView mReviewImage;
     89     private DecodeImageForReview mDecodeTaskForReview = null;
     90 
     91     private View mMenuButton;
     92     private PhotoMenu mMenu;
     93     private ModuleSwitcher mSwitcher;
     94     private CameraControls mCameraControls;
     95     private AlertDialog mLocationDialog;
     96 
     97     // Small indicators which show the camera settings in the viewfinder.
     98     private OnScreenIndicators mOnScreenIndicators;
     99 
    100     private PieRenderer mPieRenderer;
    101     private ZoomRenderer mZoomRenderer;
    102     private Toast mNotSelectableToast;
    103 
    104     private int mZoomMax;
    105     private List<Integer> mZoomRatios;
    106 
    107     private int mPreviewWidth = 0;
    108     private int mPreviewHeight = 0;
    109     private float mSurfaceTextureUncroppedWidth;
    110     private float mSurfaceTextureUncroppedHeight;
    111 
    112     private ImageView mPreviewThumb;
    113     private View mFlashOverlay;
    114 
    115     private SurfaceTextureSizeChangedListener mSurfaceTextureSizeListener;
    116     private TextureView mTextureView;
    117     private Matrix mMatrix = null;
    118     private float mAspectRatio = 4f / 3f;
    119     private View mPreviewCover;
    120     private final Object mSurfaceTextureLock = new Object();
    121 
    122     public interface SurfaceTextureSizeChangedListener {
    123         public void onSurfaceTextureSizeChanged(int uncroppedWidth, int uncroppedHeight);
    124     }
    125 
    126     private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() {
    127         @Override
    128         public void onLayoutChange(View v, int left, int top, int right,
    129                 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
    130             int width = right - left;
    131             int height = bottom - top;
    132             if (mPreviewWidth != width || mPreviewHeight != height) {
    133                 mPreviewWidth = width;
    134                 mPreviewHeight = height;
    135                 setTransformMatrix(width, height);
    136             }
    137         }
    138     };
    139 
    140     private class DecodeTask extends AsyncTask<Void, Void, Bitmap> {
    141         private final byte [] mData;
    142         private int mOrientation;
    143         private boolean mMirror;
    144 
    145         public DecodeTask(byte[] data, int orientation, boolean mirror) {
    146             mData = data;
    147             mOrientation = orientation;
    148             mMirror = mirror;
    149         }
    150 
    151         @Override
    152         protected Bitmap doInBackground(Void... params) {
    153             // Decode image in background.
    154             Bitmap bitmap = CameraUtil.downSample(mData, DOWN_SAMPLE_FACTOR);
    155             if (mOrientation != 0 || mMirror) {
    156                 Matrix m = new Matrix();
    157                 if (mMirror) {
    158                     // Flip horizontally
    159                     m.setScale(-1f, 1f);
    160                 }
    161                 m.preRotate(mOrientation);
    162                 return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m,
    163                         false);
    164             }
    165             return bitmap;
    166         }
    167 
    168         @Override
    169         protected void onPostExecute(Bitmap bitmap) {
    170             mPreviewThumb.setImageBitmap(bitmap);
    171             mAnimationManager.startCaptureAnimation(mPreviewThumb);
    172         }
    173     }
    174 
    175     private class DecodeImageForReview extends DecodeTask {
    176         public DecodeImageForReview(byte[] data, int orientation, boolean mirror) {
    177             super(data, orientation, mirror);
    178         }
    179 
    180         @Override
    181         protected void onPostExecute(Bitmap bitmap) {
    182             if (isCancelled()) {
    183                 return;
    184             }
    185             mReviewImage.setImageBitmap(bitmap);
    186             mReviewImage.setVisibility(View.VISIBLE);
    187             mDecodeTaskForReview = null;
    188         }
    189     }
    190 
    191     public PhotoUI(CameraActivity activity, PhotoController controller, View parent) {
    192         mActivity = activity;
    193         mController = controller;
    194         mRootView = parent;
    195 
    196         mActivity.getLayoutInflater().inflate(R.layout.photo_module,
    197                 (ViewGroup) mRootView, true);
    198         mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
    199         mFlashOverlay = mRootView.findViewById(R.id.flash_overlay);
    200         mPreviewCover = mRootView.findViewById(R.id.preview_cover);
    201         // display the view
    202         mTextureView = (TextureView) mRootView.findViewById(R.id.preview_content);
    203         mTextureView.setSurfaceTextureListener(this);
    204         mTextureView.addOnLayoutChangeListener(mLayoutListener);
    205         initIndicators();
    206 
    207         mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button);
    208         mSwitcher = (ModuleSwitcher) mRootView.findViewById(R.id.camera_switcher);
    209         mSwitcher.setCurrentIndex(ModuleSwitcher.PHOTO_MODULE_INDEX);
    210         mSwitcher.setSwitchListener(mActivity);
    211         mMenuButton = mRootView.findViewById(R.id.menu);
    212         ViewStub faceViewStub = (ViewStub) mRootView
    213                 .findViewById(R.id.face_view_stub);
    214         if (faceViewStub != null) {
    215             faceViewStub.inflate();
    216             mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);
    217             setSurfaceTextureSizeChangedListener(mFaceView);
    218         }
    219         mCameraControls = (CameraControls) mRootView.findViewById(R.id.camera_controls);
    220         mAnimationManager = new AnimationManager();
    221     }
    222 
    223     public void setSurfaceTextureSizeChangedListener(SurfaceTextureSizeChangedListener listener) {
    224         mSurfaceTextureSizeListener = listener;
    225     }
    226 
    227     public void updatePreviewAspectRatio(float aspectRatio) {
    228         if (aspectRatio <= 0) {
    229             Log.e(TAG, "Invalid aspect ratio: " + aspectRatio);
    230             return;
    231         }
    232         if (aspectRatio < 1f) {
    233             aspectRatio = 1f / aspectRatio;
    234         }
    235 
    236         if (mAspectRatio != aspectRatio) {
    237             mAspectRatio = aspectRatio;
    238             // Update transform matrix with the new aspect ratio.
    239             if (mPreviewWidth != 0 && mPreviewHeight != 0) {
    240                 setTransformMatrix(mPreviewWidth, mPreviewHeight);
    241             }
    242         }
    243     }
    244 
    245     private void setTransformMatrix(int width, int height) {
    246         mMatrix = mTextureView.getTransform(mMatrix);
    247         float scaleX = 1f, scaleY = 1f;
    248         float scaledTextureWidth, scaledTextureHeight;
    249         if (width > height) {
    250             scaledTextureWidth = Math.max(width,
    251                     (int) (height * mAspectRatio));
    252             scaledTextureHeight = Math.max(height,
    253                     (int)(width / mAspectRatio));
    254         } else {
    255             scaledTextureWidth = Math.max(width,
    256                     (int) (height / mAspectRatio));
    257             scaledTextureHeight = Math.max(height,
    258                     (int) (width * mAspectRatio));
    259         }
    260 
    261         if (mSurfaceTextureUncroppedWidth != scaledTextureWidth ||
    262                 mSurfaceTextureUncroppedHeight != scaledTextureHeight) {
    263             mSurfaceTextureUncroppedWidth = scaledTextureWidth;
    264             mSurfaceTextureUncroppedHeight = scaledTextureHeight;
    265             if (mSurfaceTextureSizeListener != null) {
    266                 mSurfaceTextureSizeListener.onSurfaceTextureSizeChanged(
    267                         (int) mSurfaceTextureUncroppedWidth, (int) mSurfaceTextureUncroppedHeight);
    268             }
    269         }
    270         scaleX = scaledTextureWidth / width;
    271         scaleY = scaledTextureHeight / height;
    272         mMatrix.setScale(scaleX, scaleY, (float) width / 2, (float) height / 2);
    273         mTextureView.setTransform(mMatrix);
    274 
    275         // Calculate the new preview rectangle.
    276         RectF previewRect = new RectF(0, 0, width, height);
    277         mMatrix.mapRect(previewRect);
    278         mController.onPreviewRectChanged(CameraUtil.rectFToRect(previewRect));
    279     }
    280 
    281     protected Object getSurfaceTextureLock() {
    282         return mSurfaceTextureLock;
    283     }
    284 
    285     @Override
    286     public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
    287         synchronized (mSurfaceTextureLock) {
    288             Log.v(TAG, "SurfaceTexture ready.");
    289             mSurfaceTexture = surface;
    290             mController.onPreviewUIReady();
    291             // Workaround for b/11168275, see b/10981460 for more details
    292             if (mPreviewWidth != 0 && mPreviewHeight != 0) {
    293                 // Re-apply transform matrix for new surface texture
    294                 setTransformMatrix(mPreviewWidth, mPreviewHeight);
    295             }
    296         }
    297     }
    298 
    299     @Override
    300     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
    301         // Ignored, Camera does all the work for us
    302     }
    303 
    304     @Override
    305     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
    306         synchronized (mSurfaceTextureLock) {
    307             mSurfaceTexture = null;
    308             mController.onPreviewUIDestroyed();
    309             Log.w(TAG, "SurfaceTexture destroyed");
    310             return true;
    311         }
    312     }
    313 
    314     @Override
    315     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
    316         // Make sure preview cover is hidden if preview data is available.
    317         if (mPreviewCover.getVisibility() != View.GONE) {
    318             mPreviewCover.setVisibility(View.GONE);
    319         }
    320     }
    321 
    322     public View getRootView() {
    323         return mRootView;
    324     }
    325 
    326     private void initIndicators() {
    327         mOnScreenIndicators = new OnScreenIndicators(mActivity,
    328                 mRootView.findViewById(R.id.on_screen_indicators));
    329     }
    330 
    331     public void onCameraOpened(PreferenceGroup prefGroup, ComboPreferences prefs,
    332             Camera.Parameters params, OnPreferenceChangedListener listener) {
    333         if (mPieRenderer == null) {
    334             mPieRenderer = new PieRenderer(mActivity);
    335             mPieRenderer.setPieListener(this);
    336             mRenderOverlay.addRenderer(mPieRenderer);
    337         }
    338 
    339         if (mMenu == null) {
    340             mMenu = new PhotoMenu(mActivity, this, mPieRenderer);
    341             mMenu.setListener(listener);
    342         }
    343         mMenu.initialize(prefGroup);
    344 
    345         if (mZoomRenderer == null) {
    346             mZoomRenderer = new ZoomRenderer(mActivity);
    347             mRenderOverlay.addRenderer(mZoomRenderer);
    348         }
    349 
    350         if (mGestures == null) {
    351             // this will handle gesture disambiguation and dispatching
    352             mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer);
    353             mRenderOverlay.setGestures(mGestures);
    354         }
    355         mGestures.setZoomEnabled(params.isZoomSupported());
    356         mGestures.setRenderOverlay(mRenderOverlay);
    357         mRenderOverlay.requestLayout();
    358 
    359         initializeZoom(params);
    360         updateOnScreenIndicators(params, prefGroup, prefs);
    361     }
    362 
    363     public void animateCapture(final byte[] jpegData, int orientation, boolean mirror) {
    364         // Decode jpeg byte array and then animate the jpeg
    365         DecodeTask task = new DecodeTask(jpegData, orientation, mirror);
    366         task.execute();
    367     }
    368 
    369     private void openMenu() {
    370         if (mPieRenderer != null) {
    371             // If autofocus is not finished, cancel autofocus so that the
    372             // subsequent touch can be handled by PreviewGestures
    373             if (mController.getCameraState() == PhotoController.FOCUSING) {
    374                     mController.cancelAutoFocus();
    375             }
    376             mPieRenderer.showInCenter();
    377         }
    378     }
    379 
    380     public void initializeControlByIntent() {
    381         mPreviewThumb = (ImageView) mRootView.findViewById(R.id.preview_thumb);
    382         mPreviewThumb.setOnClickListener(new OnClickListener() {
    383             @Override
    384             public void onClick(View v) {
    385                 mActivity.gotoGallery();
    386             }
    387         });
    388         mMenuButton = mRootView.findViewById(R.id.menu);
    389         mMenuButton.setOnClickListener(new OnClickListener() {
    390             @Override
    391             public void onClick(View v) {
    392                 openMenu();
    393             }
    394         });
    395         if (mController.isImageCaptureIntent()) {
    396             hideSwitcher();
    397             ViewGroup cameraControls = (ViewGroup) mRootView.findViewById(R.id.camera_controls);
    398             mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls);
    399 
    400             mReviewDoneButton = mRootView.findViewById(R.id.btn_done);
    401             mReviewCancelButton = mRootView.findViewById(R.id.btn_cancel);
    402             mReviewRetakeButton = mRootView.findViewById(R.id.btn_retake);
    403             mReviewImage = (ImageView) mRootView.findViewById(R.id.review_image);
    404             mReviewCancelButton.setVisibility(View.VISIBLE);
    405 
    406             mReviewDoneButton.setOnClickListener(new OnClickListener() {
    407                 @Override
    408                 public void onClick(View v) {
    409                     mController.onCaptureDone();
    410                 }
    411             });
    412             mReviewCancelButton.setOnClickListener(new OnClickListener() {
    413                 @Override
    414                 public void onClick(View v) {
    415                     mController.onCaptureCancelled();
    416                 }
    417             });
    418 
    419             mReviewRetakeButton.setOnClickListener(new OnClickListener() {
    420                 @Override
    421                 public void onClick(View v) {
    422                     mController.onCaptureRetake();
    423                 }
    424             });
    425         }
    426     }
    427 
    428     public void hideUI() {
    429         mCameraControls.setVisibility(View.INVISIBLE);
    430         mSwitcher.closePopup();
    431     }
    432 
    433     public void showUI() {
    434         mCameraControls.setVisibility(View.VISIBLE);
    435     }
    436 
    437     public boolean arePreviewControlsVisible() {
    438         return (mCameraControls.getVisibility() == View.VISIBLE);
    439     }
    440 
    441     public void hideSwitcher() {
    442         mSwitcher.closePopup();
    443         mSwitcher.setVisibility(View.INVISIBLE);
    444     }
    445 
    446     public void showSwitcher() {
    447         mSwitcher.setVisibility(View.VISIBLE);
    448     }
    449     // called from onResume but only the first time
    450     public  void initializeFirstTime() {
    451         // Initialize shutter button.
    452         mShutterButton.setImageResource(R.drawable.btn_new_shutter);
    453         mShutterButton.setOnShutterButtonListener(mController);
    454         mShutterButton.setVisibility(View.VISIBLE);
    455     }
    456 
    457     // called from onResume every other time
    458     public void initializeSecondTime(Camera.Parameters params) {
    459         initializeZoom(params);
    460         if (mController.isImageCaptureIntent()) {
    461             hidePostCaptureAlert();
    462         }
    463         if (mMenu != null) {
    464             mMenu.reloadPreferences();
    465         }
    466     }
    467 
    468     public void showLocationDialog() {
    469         mLocationDialog = new AlertDialog.Builder(mActivity)
    470                 .setTitle(R.string.remember_location_title)
    471                 .setMessage(R.string.remember_location_prompt)
    472                 .setPositiveButton(R.string.remember_location_yes,
    473                         new DialogInterface.OnClickListener() {
    474                             @Override
    475                             public void onClick(DialogInterface dialog, int arg1) {
    476                                 mController.enableRecordingLocation(true);
    477                                 mLocationDialog = null;
    478                             }
    479                         })
    480                 .setNegativeButton(R.string.remember_location_no,
    481                         new DialogInterface.OnClickListener() {
    482                             @Override
    483                             public void onClick(DialogInterface dialog, int arg1) {
    484                                 dialog.cancel();
    485                             }
    486                         })
    487                 .setOnCancelListener(new DialogInterface.OnCancelListener() {
    488                     @Override
    489                     public void onCancel(DialogInterface dialog) {
    490                         mController.enableRecordingLocation(false);
    491                         mLocationDialog = null;
    492                     }
    493                 })
    494                 .show();
    495     }
    496 
    497     public void initializeZoom(Camera.Parameters params) {
    498         if ((params == null) || !params.isZoomSupported()
    499                 || (mZoomRenderer == null)) return;
    500         mZoomMax = params.getMaxZoom();
    501         mZoomRatios = params.getZoomRatios();
    502         // Currently we use immediate zoom for fast zooming to get better UX and
    503         // there is no plan to take advantage of the smooth zoom.
    504         if (mZoomRenderer != null) {
    505             mZoomRenderer.setZoomMax(mZoomMax);
    506             mZoomRenderer.setZoom(params.getZoom());
    507             mZoomRenderer.setZoomValue(mZoomRatios.get(params.getZoom()));
    508             mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener());
    509         }
    510     }
    511 
    512     @Override
    513     public void showGpsOnScreenIndicator(boolean hasSignal) { }
    514 
    515     @Override
    516     public void hideGpsOnScreenIndicator() { }
    517 
    518     public void overrideSettings(final String ... keyvalues) {
    519         mMenu.overrideSettings(keyvalues);
    520     }
    521 
    522     public void updateOnScreenIndicators(Camera.Parameters params,
    523             PreferenceGroup group, ComboPreferences prefs) {
    524         if (params == null) return;
    525         mOnScreenIndicators.updateSceneOnScreenIndicator(params.getSceneMode());
    526         mOnScreenIndicators.updateExposureOnScreenIndicator(params,
    527                 CameraSettings.readExposure(prefs));
    528         mOnScreenIndicators.updateFlashOnScreenIndicator(params.getFlashMode());
    529         int wbIndex = 2;
    530         ListPreference pref = group.findPreference(CameraSettings.KEY_WHITE_BALANCE);
    531         if (pref != null) {
    532             wbIndex = pref.getCurrentIndex();
    533         }
    534         mOnScreenIndicators.updateWBIndicator(wbIndex);
    535         boolean location = RecordLocationPreference.get(
    536                 prefs, mActivity.getContentResolver());
    537         mOnScreenIndicators.updateLocationIndicator(location);
    538     }
    539 
    540     public void setCameraState(int state) {
    541     }
    542 
    543     public void animateFlash() {
    544         mAnimationManager.startFlashAnimation(mFlashOverlay);
    545     }
    546 
    547     public void enableGestures(boolean enable) {
    548         if (mGestures != null) {
    549             mGestures.setEnabled(enable);
    550         }
    551     }
    552 
    553     // forward from preview gestures to controller
    554     @Override
    555     public void onSingleTapUp(View view, int x, int y) {
    556         mController.onSingleTapUp(view, x, y);
    557     }
    558 
    559     public boolean onBackPressed() {
    560         if (mPieRenderer != null && mPieRenderer.showsItems()) {
    561             mPieRenderer.hide();
    562             return true;
    563         }
    564         // In image capture mode, back button should:
    565         // 1) if there is any popup, dismiss them, 2) otherwise, get out of
    566         // image capture
    567         if (mController.isImageCaptureIntent()) {
    568             mController.onCaptureCancelled();
    569             return true;
    570         } else if (!mController.isCameraIdle()) {
    571             // ignore backs while we're taking a picture
    572             return true;
    573         } else {
    574             return false;
    575         }
    576     }
    577 
    578     public void onPreviewFocusChanged(boolean previewFocused) {
    579         if (previewFocused) {
    580             showUI();
    581         } else {
    582             hideUI();
    583         }
    584         if (mFaceView != null) {
    585             mFaceView.setBlockDraw(!previewFocused);
    586         }
    587         if (mGestures != null) {
    588             mGestures.setEnabled(previewFocused);
    589         }
    590         if (mRenderOverlay != null) {
    591             // this can not happen in capture mode
    592             mRenderOverlay.setVisibility(previewFocused ? View.VISIBLE : View.GONE);
    593         }
    594         if (mPieRenderer != null) {
    595             mPieRenderer.setBlockFocus(!previewFocused);
    596         }
    597         setShowMenu(previewFocused);
    598         if (!previewFocused && mCountDownView != null) mCountDownView.cancelCountDown();
    599     }
    600 
    601     public void showPopup(AbstractSettingPopup popup) {
    602         hideUI();
    603 
    604         if (mPopup == null) {
    605             mPopup = new PopupWindow(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    606             mPopup.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
    607             mPopup.setOutsideTouchable(true);
    608             mPopup.setFocusable(true);
    609             mPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
    610                 @Override
    611                 public void onDismiss() {
    612                     mPopup = null;
    613                     mMenu.popupDismissed();
    614                     showUI();
    615 
    616                     // Switch back into fullscreen/lights-out mode after popup
    617                     // is dimissed.
    618                     mActivity.setSystemBarsVisibility(false);
    619                 }
    620             });
    621         }
    622         popup.setVisibility(View.VISIBLE);
    623         mPopup.setContentView(popup);
    624         mPopup.showAtLocation(mRootView, Gravity.CENTER, 0, 0);
    625     }
    626 
    627     public void dismissPopup() {
    628         if (mPopup != null && mPopup.isShowing()) {
    629             mPopup.dismiss();
    630         }
    631     }
    632 
    633     public void onShowSwitcherPopup() {
    634         if (mPieRenderer != null && mPieRenderer.showsItems()) {
    635             mPieRenderer.hide();
    636         }
    637     }
    638 
    639     private void setShowMenu(boolean show) {
    640         if (mOnScreenIndicators != null) {
    641             mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE);
    642         }
    643         if (mMenuButton != null) {
    644             mMenuButton.setVisibility(show ? View.VISIBLE : View.GONE);
    645         }
    646     }
    647 
    648     public boolean collapseCameraControls() {
    649         // TODO: Mode switcher should behave like a popup and should hide itself when there
    650         // is a touch outside of it.
    651         mSwitcher.closePopup();
    652         // Remove all the popups/dialog boxes
    653         boolean ret = false;
    654         if (mPopup != null) {
    655             dismissPopup();
    656             ret = true;
    657         }
    658         onShowSwitcherPopup();
    659         return ret;
    660     }
    661 
    662     protected void showCapturedImageForReview(byte[] jpegData, int orientation, boolean mirror) {
    663         mDecodeTaskForReview = new DecodeImageForReview(jpegData, orientation, mirror);
    664         mDecodeTaskForReview.execute();
    665         mOnScreenIndicators.setVisibility(View.GONE);
    666         mMenuButton.setVisibility(View.GONE);
    667         CameraUtil.fadeIn(mReviewDoneButton);
    668         mShutterButton.setVisibility(View.INVISIBLE);
    669         CameraUtil.fadeIn(mReviewRetakeButton);
    670         pauseFaceDetection();
    671     }
    672 
    673     protected void hidePostCaptureAlert() {
    674         if (mDecodeTaskForReview != null) {
    675             mDecodeTaskForReview.cancel(true);
    676         }
    677         mReviewImage.setVisibility(View.GONE);
    678         mOnScreenIndicators.setVisibility(View.VISIBLE);
    679         mMenuButton.setVisibility(View.VISIBLE);
    680         CameraUtil.fadeOut(mReviewDoneButton);
    681         mShutterButton.setVisibility(View.VISIBLE);
    682         CameraUtil.fadeOut(mReviewRetakeButton);
    683         resumeFaceDetection();
    684     }
    685 
    686     public void setDisplayOrientation(int orientation) {
    687         if (mFaceView != null) {
    688             mFaceView.setDisplayOrientation(orientation);
    689         }
    690     }
    691 
    692     // shutter button handling
    693 
    694     public boolean isShutterPressed() {
    695         return mShutterButton.isPressed();
    696     }
    697 
    698     /**
    699      * Enables or disables the shutter button.
    700      */
    701     public void enableShutter(boolean enabled) {
    702         if (mShutterButton != null) {
    703             mShutterButton.setEnabled(enabled);
    704         }
    705     }
    706 
    707     public void pressShutterButton() {
    708         if (mShutterButton.isInTouchMode()) {
    709             mShutterButton.requestFocusFromTouch();
    710         } else {
    711             mShutterButton.requestFocus();
    712         }
    713         mShutterButton.setPressed(true);
    714     }
    715 
    716     private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener {
    717         @Override
    718         public void onZoomValueChanged(int index) {
    719             int newZoom = mController.onZoomChanged(index);
    720             if (mZoomRenderer != null) {
    721                 mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom));
    722             }
    723         }
    724 
    725         @Override
    726         public void onZoomStart() {
    727             if (mPieRenderer != null) {
    728                 mPieRenderer.setBlockFocus(true);
    729             }
    730         }
    731 
    732         @Override
    733         public void onZoomEnd() {
    734             if (mPieRenderer != null) {
    735                 mPieRenderer.setBlockFocus(false);
    736             }
    737         }
    738     }
    739 
    740     @Override
    741     public void onPieOpened(int centerX, int centerY) {
    742         setSwipingEnabled(false);
    743         if (mFaceView != null) {
    744             mFaceView.setBlockDraw(true);
    745         }
    746         // Close module selection menu when pie menu is opened.
    747         mSwitcher.closePopup();
    748     }
    749 
    750     @Override
    751     public void onPieClosed() {
    752         setSwipingEnabled(true);
    753         if (mFaceView != null) {
    754             mFaceView.setBlockDraw(false);
    755         }
    756     }
    757 
    758     public void setSwipingEnabled(boolean enable) {
    759         mActivity.setSwipingEnabled(enable);
    760     }
    761 
    762     public SurfaceTexture getSurfaceTexture() {
    763         return mSurfaceTexture;
    764     }
    765 
    766     // Countdown timer
    767 
    768     private void initializeCountDown() {
    769         mActivity.getLayoutInflater().inflate(R.layout.count_down_to_capture,
    770                 (ViewGroup) mRootView, true);
    771         mCountDownView = (CountDownView) (mRootView.findViewById(R.id.count_down_to_capture));
    772         mCountDownView.setCountDownFinishedListener((OnCountDownFinishedListener) mController);
    773     }
    774 
    775     public boolean isCountingDown() {
    776         return mCountDownView != null && mCountDownView.isCountingDown();
    777     }
    778 
    779     public void cancelCountDown() {
    780         if (mCountDownView == null) return;
    781         mCountDownView.cancelCountDown();
    782     }
    783 
    784     public void startCountDown(int sec, boolean playSound) {
    785         if (mCountDownView == null) initializeCountDown();
    786         mCountDownView.startCountDown(sec, playSound);
    787     }
    788 
    789     public void showPreferencesToast() {
    790         if (mNotSelectableToast == null) {
    791             String str = mActivity.getResources().getString(R.string.not_selectable_in_scene_mode);
    792             mNotSelectableToast = Toast.makeText(mActivity, str, Toast.LENGTH_SHORT);
    793         }
    794         mNotSelectableToast.show();
    795     }
    796 
    797     public void showPreviewCover() {
    798         mPreviewCover.setVisibility(View.VISIBLE);
    799     }
    800 
    801     public void onPause() {
    802         cancelCountDown();
    803 
    804         // Clear UI.
    805         collapseCameraControls();
    806         if (mFaceView != null) mFaceView.clear();
    807 
    808         if (mLocationDialog != null && mLocationDialog.isShowing()) {
    809             mLocationDialog.dismiss();
    810         }
    811         mLocationDialog = null;
    812     }
    813 
    814     public void initDisplayChangeListener() {
    815         ((CameraRootView) mRootView).setDisplayChangeListener(this);
    816     }
    817 
    818     public void removeDisplayChangeListener() {
    819         ((CameraRootView) mRootView).removeDisplayChangeListener();
    820     }
    821 
    822     // focus UI implementation
    823 
    824     private FocusIndicator getFocusIndicator() {
    825         return (mFaceView != null && mFaceView.faceExists()) ? mFaceView : mPieRenderer;
    826     }
    827 
    828     @Override
    829     public boolean hasFaces() {
    830         return (mFaceView != null && mFaceView.faceExists());
    831     }
    832 
    833     public void clearFaces() {
    834         if (mFaceView != null) mFaceView.clear();
    835     }
    836 
    837     @Override
    838     public void clearFocus() {
    839         FocusIndicator indicator = getFocusIndicator();
    840         if (indicator != null) indicator.clear();
    841     }
    842 
    843     @Override
    844     public void setFocusPosition(int x, int y) {
    845         mPieRenderer.setFocus(x, y);
    846     }
    847 
    848     @Override
    849     public void onFocusStarted() {
    850         getFocusIndicator().showStart();
    851     }
    852 
    853     @Override
    854     public void onFocusSucceeded(boolean timeout) {
    855         getFocusIndicator().showSuccess(timeout);
    856     }
    857 
    858     @Override
    859     public void onFocusFailed(boolean timeout) {
    860         getFocusIndicator().showFail(timeout);
    861     }
    862 
    863     @Override
    864     public void pauseFaceDetection() {
    865         if (mFaceView != null) mFaceView.pause();
    866     }
    867 
    868     @Override
    869     public void resumeFaceDetection() {
    870         if (mFaceView != null) mFaceView.resume();
    871     }
    872 
    873     public void onStartFaceDetection(int orientation, boolean mirror) {
    874         mFaceView.clear();
    875         mFaceView.setVisibility(View.VISIBLE);
    876         mFaceView.setDisplayOrientation(orientation);
    877         mFaceView.setMirror(mirror);
    878         mFaceView.resume();
    879     }
    880 
    881     @Override
    882     public void onFaceDetection(Face[] faces, CameraManager.CameraProxy camera) {
    883         mFaceView.setFaces(faces);
    884     }
    885 
    886     @Override
    887     public void onDisplayChanged() {
    888         Log.d(TAG, "Device flip detected.");
    889         mCameraControls.checkLayoutFlip();
    890         mController.updateCameraOrientation();
    891     }
    892 
    893 }
    894