PhotoModule.java revision 53b11253fde509b01851ba022d9178c7c6f13dde
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
17package com.android.camera;
18
19import android.annotation.TargetApi;
20import android.app.Activity;
21import android.content.BroadcastReceiver;
22import android.content.ContentProviderClient;
23import android.content.ContentResolver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.SharedPreferences.Editor;
28import android.content.res.Configuration;
29import android.graphics.Bitmap;
30import android.graphics.SurfaceTexture;
31import android.hardware.Camera.CameraInfo;
32import android.hardware.Camera.Face;
33import android.hardware.Camera.FaceDetectionListener;
34import android.hardware.Camera.Parameters;
35import android.hardware.Camera.PictureCallback;
36import android.hardware.Camera.Size;
37import android.location.Location;
38import android.media.CameraProfile;
39import android.net.Uri;
40import android.os.Bundle;
41import android.os.ConditionVariable;
42import android.os.Handler;
43import android.os.Looper;
44import android.os.Message;
45import android.os.MessageQueue;
46import android.os.SystemClock;
47import android.provider.MediaStore;
48import android.util.Log;
49import android.view.Gravity;
50import android.view.KeyEvent;
51import android.view.LayoutInflater;
52import android.view.MotionEvent;
53import android.view.OrientationEventListener;
54import android.view.SurfaceHolder;
55import android.view.View;
56import android.view.View.OnClickListener;
57import android.view.ViewGroup;
58import android.view.WindowManager;
59import android.widget.FrameLayout;
60import android.widget.FrameLayout.LayoutParams;
61import android.widget.ImageView;
62import android.widget.TextView;
63import android.widget.Toast;
64
65import com.android.camera.CameraManager.CameraProxy;
66import com.android.camera.ui.AbstractSettingPopup;
67import com.android.camera.ui.FaceView;
68import com.android.camera.ui.FocusRenderer;
69import com.android.camera.ui.PieRenderer;
70import com.android.camera.ui.PopupManager;
71import com.android.camera.ui.PreviewSurfaceView;
72import com.android.camera.ui.RenderOverlay;
73import com.android.camera.ui.Rotatable;
74import com.android.camera.ui.RotateImageView;
75import com.android.camera.ui.RotateLayout;
76import com.android.camera.ui.RotateTextToast;
77import com.android.camera.ui.TwoStateImageView;
78import com.android.camera.ui.ZoomRenderer;
79import com.android.gallery3d.app.CropImage;
80import com.android.gallery3d.common.ApiHelper;
81
82import java.io.File;
83import java.io.FileNotFoundException;
84import java.io.FileOutputStream;
85import java.io.IOException;
86import java.io.OutputStream;
87import java.util.ArrayList;
88import java.util.Collections;
89import java.util.Formatter;
90import java.util.List;
91
92public class PhotoModule
93    implements CameraModule,
94    FocusOverlayManager.Listener,
95    CameraPreference.OnPreferenceChangedListener,
96    LocationManager.Listener,
97    PreviewFrameLayout.OnSizeChangedListener,
98    ShutterButton.OnShutterButtonListener,
99    SurfaceHolder.Callback,
100    PieRenderer.PieListener {
101
102    private static final String TAG = "CAM_PhotoModule";
103
104    // We number the request code from 1000 to avoid collision with Gallery.
105    private static final int REQUEST_CROP = 1000;
106
107    private static final int SETUP_PREVIEW = 1;
108    private static final int FIRST_TIME_INIT = 2;
109    private static final int CLEAR_SCREEN_DELAY = 3;
110    private static final int SET_CAMERA_PARAMETERS_WHEN_IDLE = 4;
111    private static final int CHECK_DISPLAY_ROTATION = 5;
112    private static final int SHOW_TAP_TO_FOCUS_TOAST = 6;
113    private static final int UPDATE_THUMBNAIL = 7;
114    private static final int SWITCH_CAMERA = 8;
115    private static final int SWITCH_CAMERA_START_ANIMATION = 9;
116    private static final int CAMERA_OPEN_DONE = 10;
117    private static final int START_PREVIEW_DONE = 11;
118    private static final int OPEN_CAMERA_FAIL = 12;
119    private static final int CAMERA_DISABLED = 13;
120
121    // The subset of parameters we need to update in setCameraParameters().
122    private static final int UPDATE_PARAM_INITIALIZE = 1;
123    private static final int UPDATE_PARAM_ZOOM = 2;
124    private static final int UPDATE_PARAM_PREFERENCE = 4;
125    private static final int UPDATE_PARAM_ALL = -1;
126
127    // This is the timeout to keep the camera in onPause for the first time
128    // after screen on if the activity is started from secure lock screen.
129    private static final int KEEP_CAMERA_TIMEOUT = 1000; // ms
130
131    // copied from Camera hierarchy
132    private CameraActivity mActivity;
133    private View mRootView;
134    private CameraProxy mCameraDevice;
135    private int mCameraId;
136    private Parameters mParameters;
137    private boolean mPaused;
138    private AbstractSettingPopup mPopup;
139
140    // these are only used by Camera
141
142    // The activity is going to switch to the specified camera id. This is
143    // needed because texture copy is done in GL thread. -1 means camera is not
144    // switching.
145    protected int mPendingSwitchCameraId = -1;
146    private boolean mOpenCameraFail;
147    private boolean mCameraDisabled;
148
149    // When setCameraParametersWhenIdle() is called, we accumulate the subsets
150    // needed to be updated in mUpdateSet.
151    private int mUpdateSet;
152
153    private static final int SCREEN_DELAY = 2 * 60 * 1000;
154
155    private int mZoomValue;  // The current zoom value.
156    private int mZoomMax;
157
158    private Parameters mInitialParams;
159    private boolean mFocusAreaSupported;
160    private boolean mMeteringAreaSupported;
161    private boolean mAeLockSupported;
162    private boolean mAwbLockSupported;
163    private boolean mContinousFocusSupported;
164
165    private MyOrientationEventListener mOrientationListener;
166    // The degrees of the device rotated clockwise from its natural orientation.
167    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
168    // The orientation compensation for icons and thumbnails. Ex: if the value
169    // is 90, the UI components should be rotated 90 degrees counter-clockwise.
170    private int mOrientationCompensation = 0;
171    private ComboPreferences mPreferences;
172
173    private static final String sTempCropFilename = "crop-temp";
174
175    private ContentProviderClient mMediaProviderClient;
176    private ShutterButton mShutterButton;
177    private boolean mFaceDetectionStarted = false;
178
179    private PreviewFrameLayout mPreviewFrameLayout;
180    private Object mSurfaceTexture;
181
182    // for API level 10
183    private PreviewSurfaceView mPreviewSurfaceView;
184    private volatile SurfaceHolder mCameraSurfaceHolder;
185
186    private RotateDialogController mRotateDialog;
187    private FaceView mFaceView;
188    private RenderOverlay mRenderOverlay;
189    private Rotatable mReviewCancelButton;
190    private Rotatable mReviewDoneButton;
191//    private View mReviewRetakeButton;
192
193    // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
194    private String mCropValue;
195    private Uri mSaveUri;
196
197    // Small indicators which show the camera settings in the viewfinder.
198    private TextView mExposureIndicator;
199    private ImageView mGpsIndicator;
200    private ImageView mFlashIndicator;
201    private ImageView mSceneIndicator;
202    private ImageView mWhiteBalanceIndicator;
203    private ImageView mFocusIndicator;
204    // A view group that contains all the small indicators.
205    private Rotatable mOnScreenIndicators;
206
207    // We use a thread in ImageSaver to do the work of saving images and
208    // generating thumbnails. This reduces the shot-to-shot time.
209    private ImageSaver mImageSaver;
210    // Similarly, we use a thread to generate the name of the picture and insert
211    // it into MediaStore while picture taking is still in progress.
212    private ImageNamer mImageNamer;
213
214    private SoundClips.Player mSoundPlayer;
215
216    private Runnable mDoSnapRunnable = new Runnable() {
217        @Override
218        public void run() {
219            onShutterButtonClick();
220        }
221    };
222
223    private final StringBuilder mBuilder = new StringBuilder();
224    private final Formatter mFormatter = new Formatter(mBuilder);
225    private final Object[] mFormatterArgs = new Object[1];
226
227    /**
228     * An unpublished intent flag requesting to return as soon as capturing
229     * is completed.
230     *
231     * TODO: consider publishing by moving into MediaStore.
232     */
233    private static final String EXTRA_QUICK_CAPTURE =
234            "android.intent.extra.quickCapture";
235
236    // The display rotation in degrees. This is only valid when mCameraState is
237    // not PREVIEW_STOPPED.
238    private int mDisplayRotation;
239    // The value for android.hardware.Camera.setDisplayOrientation.
240    private int mCameraDisplayOrientation;
241    // The value for UI components like indicators.
242    private int mDisplayOrientation;
243    // The value for android.hardware.Camera.Parameters.setRotation.
244    private int mJpegRotation;
245    private boolean mFirstTimeInitialized;
246    private boolean mIsImageCaptureIntent;
247
248    private static final int PREVIEW_STOPPED = 0;
249    private static final int IDLE = 1;  // preview is active
250    // Focus is in progress. The exact focus state is in Focus.java.
251    private static final int FOCUSING = 2;
252    private static final int SNAPSHOT_IN_PROGRESS = 3;
253    // Switching between cameras.
254    private static final int SWITCHING_CAMERA = 4;
255    private int mCameraState = PREVIEW_STOPPED;
256    private boolean mSnapshotOnIdle = false;
257
258    private ContentResolver mContentResolver;
259    private boolean mDidRegister = false;
260
261    private LocationManager mLocationManager;
262
263    private final ShutterCallback mShutterCallback = new ShutterCallback();
264    private final PostViewPictureCallback mPostViewPictureCallback =
265            new PostViewPictureCallback();
266    private final RawPictureCallback mRawPictureCallback =
267            new RawPictureCallback();
268    private final AutoFocusCallback mAutoFocusCallback =
269            new AutoFocusCallback();
270    private final Object mAutoFocusMoveCallback =
271            ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
272            ? new AutoFocusMoveCallback()
273            : null;
274
275    private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
276
277    private long mFocusStartTime;
278    private long mShutterCallbackTime;
279    private long mPostViewPictureCallbackTime;
280    private long mRawPictureCallbackTime;
281    private long mJpegPictureCallbackTime;
282    private long mOnResumeTime;
283    private byte[] mJpegImageData;
284
285    // These latency time are for the CameraLatency test.
286    public long mAutoFocusTime;
287    public long mShutterLag;
288    public long mShutterToPictureDisplayedTime;
289    public long mPictureDisplayedToJpegCallbackTime;
290    public long mJpegCallbackFinishTime;
291    public long mCaptureStartTime;
292
293    // This handles everything about focus.
294    private FocusOverlayManager mFocusManager;
295    private FocusRenderer mFocusRenderer;
296
297    private PieRenderer mPieRenderer;
298    private PhotoController mPhotoControl;
299
300    private ZoomRenderer mZoomRenderer;
301
302    private String mSceneMode;
303    private Toast mNotSelectableToast;
304
305    private final Handler mHandler = new MainHandler();
306    private PreferenceGroup mPreferenceGroup;
307
308    private boolean mQuickCapture;
309
310    CameraStartUpThread mCameraStartUpThread;
311    ConditionVariable mStartPreviewPrerequisiteReady = new ConditionVariable();
312
313    private PreviewGestures mGestures;
314
315    protected class CameraOpenThread extends Thread {
316        @Override
317        public void run() {
318            try {
319                mCameraDevice = Util.openCamera(mActivity, mCameraId);
320                mParameters = mCameraDevice.getParameters();
321            } catch (CameraHardwareException e) {
322                mOpenCameraFail = true;
323            } catch (CameraDisabledException e) {
324                mCameraDisabled = true;
325            }
326        }
327    }
328
329    // The purpose is not to block the main thread in onCreate and onResume.
330    private class CameraStartUpThread extends Thread {
331        private volatile boolean mCancelled;
332
333        public void cancel() {
334            mCancelled = true;
335        }
336
337        @Override
338        public void run() {
339            try {
340                // We need to check whether the activity is paused before long
341                // operations to ensure that onPause() can be done ASAP.
342                if (mCancelled) return;
343                mCameraDevice = Util.openCamera(mActivity, mCameraId);
344                mParameters = mCameraDevice.getParameters();
345                // Wait until all the initialization needed by startPreview are
346                // done.
347                mStartPreviewPrerequisiteReady.block();
348
349                initializeCapabilities();
350                if (mFocusManager == null) initializeFocusManager();
351                if (mCancelled) return;
352                setCameraParameters(UPDATE_PARAM_ALL);
353                mHandler.sendEmptyMessage(CAMERA_OPEN_DONE);
354                if (mCancelled) return;
355                startPreview();
356                mHandler.sendEmptyMessage(START_PREVIEW_DONE);
357                mOnResumeTime = SystemClock.uptimeMillis();
358                mHandler.sendEmptyMessage(CHECK_DISPLAY_ROTATION);
359            } catch (CameraHardwareException e) {
360                mHandler.sendEmptyMessage(OPEN_CAMERA_FAIL);
361            } catch (CameraDisabledException e) {
362                mHandler.sendEmptyMessage(CAMERA_DISABLED);
363            }
364        }
365    }
366
367    /**
368     * This Handler is used to post message back onto the main thread of the
369     * application
370     */
371    private class MainHandler extends Handler {
372        @Override
373        public void handleMessage(Message msg) {
374            switch (msg.what) {
375                case SETUP_PREVIEW: {
376                    setupPreview();
377                    break;
378                }
379
380                case CLEAR_SCREEN_DELAY: {
381                    mActivity.getWindow().clearFlags(
382                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
383                    break;
384                }
385
386                case FIRST_TIME_INIT: {
387                    initializeFirstTime();
388                    break;
389                }
390
391                case SET_CAMERA_PARAMETERS_WHEN_IDLE: {
392                    setCameraParametersWhenIdle(0);
393                    break;
394                }
395
396                case CHECK_DISPLAY_ROTATION: {
397                    // Set the display orientation if display rotation has changed.
398                    // Sometimes this happens when the device is held upside
399                    // down and camera app is opened. Rotation animation will
400                    // take some time and the rotation value we have got may be
401                    // wrong. Framework does not have a callback for this now.
402                    if (Util.getDisplayRotation(mActivity) != mDisplayRotation) {
403                        setDisplayOrientation();
404                    }
405                    if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
406                        mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
407                    }
408                    break;
409                }
410
411                case SHOW_TAP_TO_FOCUS_TOAST: {
412                    showTapToFocusToast();
413                    break;
414                }
415
416                case UPDATE_THUMBNAIL: {
417                    mImageSaver.updateThumbnail();
418                    break;
419                }
420
421                case SWITCH_CAMERA: {
422                    switchCamera();
423                    break;
424                }
425
426                case SWITCH_CAMERA_START_ANIMATION: {
427                    ((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
428                    break;
429                }
430
431                case CAMERA_OPEN_DONE: {
432                    initializeAfterCameraOpen();
433                    break;
434                }
435
436                case START_PREVIEW_DONE: {
437                    mCameraStartUpThread = null;
438                    setCameraState(IDLE);
439                    if (!ApiHelper.HAS_SURFACE_TEXTURE) {
440                        // This may happen if surfaceCreated has arrived.
441                        mCameraDevice.setPreviewDisplayAsync(mCameraSurfaceHolder);
442                    }
443                    startFaceDetection();
444                    break;
445                }
446
447                case OPEN_CAMERA_FAIL: {
448                    mCameraStartUpThread = null;
449                    mOpenCameraFail = true;
450                    Util.showErrorAndFinish(mActivity,
451                            R.string.cannot_connect_camera);
452                    break;
453                }
454
455                case CAMERA_DISABLED: {
456                    mCameraStartUpThread = null;
457                    mCameraDisabled = true;
458                    Util.showErrorAndFinish(mActivity,
459                            R.string.camera_disabled);
460                    break;
461                }
462            }
463        }
464    }
465
466    @Override
467    public void init(CameraActivity activity, View parent, boolean reuseNail) {
468        mActivity = activity;
469        mRootView = parent;
470        mPreferences = new ComboPreferences(mActivity);
471        CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
472        mCameraId = getPreferredCameraId(mPreferences);
473
474        mContentResolver = mActivity.getContentResolver();
475
476        // To reduce startup time, open the camera and start the preview in
477        // another thread.
478        mCameraStartUpThread = new CameraStartUpThread();
479        mCameraStartUpThread.start();
480
481        mActivity.getLayoutInflater().inflate(R.layout.photo_module, (ViewGroup) mRootView);
482
483        // Surface texture is from camera screen nail and startPreview needs it.
484        // This must be done before startPreview.
485        mIsImageCaptureIntent = isImageCaptureIntent();
486        if (reuseNail) {
487            mActivity.reuseCameraScreenNail(!mIsImageCaptureIntent);
488        } else {
489            mActivity.createCameraScreenNail(!mIsImageCaptureIntent);
490        }
491
492        mPreferences.setLocalId(mActivity, mCameraId);
493        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
494        // we need to reset exposure for the preview
495        resetExposureCompensation();
496        // Starting the preview needs preferences, camera screen nail, and
497        // focus area indicator.
498        mStartPreviewPrerequisiteReady.open();
499
500        initializeControlByIntent();
501        mRotateDialog = new RotateDialogController(mActivity, R.layout.rotate_dialog);
502        mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
503        initializeMiscControls();
504        mLocationManager = new LocationManager(mActivity, this);
505        initOnScreenIndicator();
506        // Make sure all views are disabled before camera is open.
507//        enableCameraControls(false);
508    }
509
510    private void initializeAfterCameraOpen() {
511        if (mFocusRenderer == null) {
512            mFocusRenderer = new FocusRenderer(mActivity);
513            mRenderOverlay.addRenderer(mFocusRenderer);
514            mFocusManager.setFocusRenderer(mFocusRenderer);
515        }
516        if (mPieRenderer == null) {
517            mPieRenderer = new PieRenderer(mActivity);
518            mRenderOverlay.addRenderer(mPieRenderer);
519            mPhotoControl = new PhotoController(mActivity, this, mPieRenderer);
520            mPhotoControl.setListener(this);
521            mPieRenderer.setPieListener(this);
522        }
523        if (mZoomRenderer == null) {
524            mZoomRenderer = new ZoomRenderer(mActivity);
525            mRenderOverlay.addRenderer(mZoomRenderer);
526        }
527        if (mGestures == null) {
528            // this will handle gesture disambiguation and dispatching
529            mGestures = new PreviewGestures(mActivity, mRenderOverlay,
530                    mZoomRenderer, mPieRenderer, mFocusManager);
531        }
532        initializePhotoControl();
533        mRenderOverlay.requestLayout();
534
535        // These depend on camera parameters.
536        setPreviewFrameLayoutAspectRatio();
537        mFocusManager.setPreviewSize(mPreviewFrameLayout.getWidth(),
538                mPreviewFrameLayout.getHeight());
539        loadCameraPreferences();
540        initializeZoom();
541        updateOnScreenIndicators();
542        showTapToFocusToastIfNeeded();
543    }
544
545    private void initializePhotoControl() {
546        loadCameraPreferences();
547        mPhotoControl.initialize(mPreferenceGroup);
548        updateSceneModeUI();
549//        mIndicatorControlContainer.setListener(this);
550    }
551
552
553    private void resetExposureCompensation() {
554        String value = mPreferences.getString(CameraSettings.KEY_EXPOSURE,
555                CameraSettings.EXPOSURE_DEFAULT_VALUE);
556        if (!CameraSettings.EXPOSURE_DEFAULT_VALUE.equals(value)) {
557            Editor editor = mPreferences.edit();
558            editor.putString(CameraSettings.KEY_EXPOSURE, "0");
559            editor.apply();
560        }
561    }
562
563    private void keepMediaProviderInstance() {
564        // We want to keep a reference to MediaProvider in camera's lifecycle.
565        // TODO: Utilize mMediaProviderClient instance to replace
566        // ContentResolver calls.
567        if (mMediaProviderClient == null) {
568            mMediaProviderClient = mContentResolver
569                    .acquireContentProviderClient(MediaStore.AUTHORITY);
570        }
571    }
572
573    // Snapshots can only be taken after this is called. It should be called
574    // once only. We could have done these things in onCreate() but we want to
575    // make preview screen appear as soon as possible.
576    private void initializeFirstTime() {
577        if (mFirstTimeInitialized) return;
578
579        // Create orientation listener. This should be done first because it
580        // takes some time to get first orientation.
581        mOrientationListener = new MyOrientationEventListener(mActivity);
582        mOrientationListener.enable();
583
584        // Initialize location service.
585        boolean recordLocation = RecordLocationPreference.get(
586                mPreferences, mContentResolver);
587        mLocationManager.recordLocation(recordLocation);
588
589        keepMediaProviderInstance();
590
591        // Initialize shutter button.
592        mShutterButton = mActivity.getShutterButton();
593        mShutterButton.setImageResource(R.drawable.btn_new_shutter);
594        mShutterButton.setOnShutterButtonListener(this);
595        mShutterButton.setVisibility(View.VISIBLE);
596
597        mImageSaver = new ImageSaver();
598        mImageNamer = new ImageNamer();
599
600        mFirstTimeInitialized = true;
601        addIdleHandler();
602
603        mActivity.updateStorageSpaceAndHint();
604    }
605
606    private void showTapToFocusToastIfNeeded() {
607        // Show the tap to focus toast if this is the first start.
608        if (mFocusAreaSupported &&
609                mPreferences.getBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, true)) {
610            // Delay the toast for one second to wait for orientation.
611            mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_FOCUS_TOAST, 1000);
612        }
613    }
614
615    private void addIdleHandler() {
616        MessageQueue queue = Looper.myQueue();
617        queue.addIdleHandler(new MessageQueue.IdleHandler() {
618            @Override
619            public boolean queueIdle() {
620                Storage.ensureOSXCompatible();
621                return false;
622            }
623        });
624    }
625
626    // If the activity is paused and resumed, this method will be called in
627    // onResume.
628    private void initializeSecondTime() {
629        // Start orientation listener as soon as possible because it takes
630        // some time to get first orientation.
631        mOrientationListener.enable();
632
633        // Start location update if needed.
634        boolean recordLocation = RecordLocationPreference.get(
635                mPreferences, mContentResolver);
636        mLocationManager.recordLocation(recordLocation);
637
638        mImageSaver = new ImageSaver();
639        mImageNamer = new ImageNamer();
640        initializeZoom();
641        keepMediaProviderInstance();
642        hidePostCaptureAlert();
643
644        if (mPhotoControl != null) {
645            mPhotoControl.reloadPreferences();
646        }
647    }
648
649    private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener {
650        @Override
651        public void onZoomValueChanged(int index) {
652            // Not useful to change zoom value when the activity is paused.
653            if (mPaused) return;
654            mZoomValue = index;
655
656            // Set zoom parameters asynchronously
657            mParameters.setZoom(mZoomValue);
658            mCameraDevice.setParametersAsync(mParameters);
659        }
660
661        @Override
662        public void onZoomStart() {
663            if (mFocusManager != null) {
664                mFocusManager.setEnabled(false);
665            }
666        }
667
668        @Override
669        public void onZoomEnd() {
670            if (mFocusManager != null) {
671                mFocusManager.setEnabled(true);
672            }
673        }
674    }
675
676    private void initializeZoom() {
677        if (!mParameters.isZoomSupported()) return;
678        mZoomMax = mParameters.getMaxZoom();
679        // Currently we use immediate zoom for fast zooming to get better UX and
680        // there is no plan to take advantage of the smooth zoom.
681        mZoomRenderer.setZoomMax(mZoomMax);
682        mZoomRenderer.setZoomIndex(mParameters.getZoom());
683        mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener());
684    }
685
686    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
687    @Override
688    public void startFaceDetection() {
689        if (!ApiHelper.HAS_FACE_DETECTION) return;
690        if (mFaceDetectionStarted) return;
691        if (mParameters.getMaxNumDetectedFaces() > 0) {
692            mFaceDetectionStarted = true;
693            mFaceView.clear();
694            mFaceView.setVisibility(View.VISIBLE);
695            mFaceView.setDisplayOrientation(mDisplayOrientation);
696            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
697            mFaceView.setMirror(info.facing == CameraInfo.CAMERA_FACING_FRONT);
698            mFaceView.resume();
699            mFocusManager.setFaceView(mFaceView);
700            mCameraDevice.setFaceDetectionListener(new FaceDetectionListener() {
701                @Override
702                public void onFaceDetection(Face[] faces, android.hardware.Camera camera) {
703                    mFaceView.setFaces(faces);
704                }
705            });
706            mCameraDevice.startFaceDetection();
707        }
708    }
709
710    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
711    @Override
712    public void stopFaceDetection() {
713        if (!ApiHelper.HAS_FACE_DETECTION) return;
714        if (!mFaceDetectionStarted) return;
715        if (mParameters.getMaxNumDetectedFaces() > 0) {
716            mFaceDetectionStarted = false;
717            mCameraDevice.setFaceDetectionListener(null);
718            mCameraDevice.stopFaceDetection();
719            if (mFaceView != null) mFaceView.clear();
720        }
721    }
722
723    @Override
724    public boolean dispatchTouchEvent(MotionEvent m) {
725        if (mCameraState == SWITCHING_CAMERA) return true;
726        if (mPopup != null) {
727            return mActivity.superDispatchTouchEvent(m);
728        } else if (mGestures != null && mRenderOverlay != null) {
729            return mGestures.dispatchTouch(m);
730        }
731        return false;
732    }
733
734    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
735        @Override
736        public void onReceive(Context context, Intent intent) {
737            String action = intent.getAction();
738            if (!mIsImageCaptureIntent && action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
739                mActivity.getLastThumbnail();
740            }
741        }
742    };
743
744    private void initOnScreenIndicator() {
745        mGpsIndicator = (ImageView) mRootView.findViewById(R.id.onscreen_gps_indicator);
746        mExposureIndicator = (TextView) mRootView.findViewById(R.id.onscreen_exposure_indicator);
747        mFlashIndicator = (ImageView) mRootView.findViewById(R.id.onscreen_flash_indicator);
748        mSceneIndicator = (ImageView) mRootView.findViewById(R.id.onscreen_scene_indicator);
749        mWhiteBalanceIndicator =
750                (ImageView) mRootView.findViewById(R.id.onscreen_white_balance_indicator);
751        mFocusIndicator = (ImageView) mRootView.findViewById(R.id.onscreen_focus_indicator);
752    }
753
754    @Override
755    public void showGpsOnScreenIndicator(boolean hasSignal) {
756        if (mGpsIndicator == null) {
757            return;
758        }
759        if (hasSignal) {
760            mGpsIndicator.setImageResource(R.drawable.ic_viewfinder_gps_on);
761        } else {
762            mGpsIndicator.setImageResource(R.drawable.ic_viewfinder_gps_no_signal);
763        }
764        mGpsIndicator.setVisibility(View.VISIBLE);
765    }
766
767    @Override
768    public void hideGpsOnScreenIndicator() {
769        if (mGpsIndicator == null) {
770            return;
771        }
772        mGpsIndicator.setVisibility(View.GONE);
773    }
774
775    private void updateExposureOnScreenIndicator(int value) {
776        if (mExposureIndicator == null) {
777            return;
778        }
779        if (value == 0) {
780            mExposureIndicator.setText("");
781            mExposureIndicator.setVisibility(View.GONE);
782        } else {
783            float step = mParameters.getExposureCompensationStep();
784            mFormatterArgs[0] = value * step;
785            mBuilder.delete(0, mBuilder.length());
786            mFormatter.format("%+1.1f", mFormatterArgs);
787            String exposure = mFormatter.toString();
788            mExposureIndicator.setText(exposure);
789            mExposureIndicator.setVisibility(View.VISIBLE);
790        }
791    }
792
793    private void updateFlashOnScreenIndicator(String value) {
794        if (mFlashIndicator == null) {
795            return;
796        }
797        if (value == null || Parameters.FLASH_MODE_OFF.equals(value)) {
798            mFlashIndicator.setVisibility(View.GONE);
799        } else {
800            mFlashIndicator.setVisibility(View.VISIBLE);
801            if (Parameters.FLASH_MODE_AUTO.equals(value)) {
802                mFlashIndicator.setImageResource(R.drawable.ic_indicators_landscape_flash_auto);
803            } else if (Parameters.FLASH_MODE_ON.equals(value)) {
804                mFlashIndicator.setImageResource(R.drawable.ic_indicators_landscape_flash_on);
805            } else {
806                // Should not happen.
807                mFlashIndicator.setVisibility(View.GONE);
808            }
809        }
810    }
811
812    private void updateSceneOnScreenIndicator(String value) {
813        if (mSceneIndicator == null) {
814            return;
815        }
816        boolean isGone = (value == null) || (Parameters.SCENE_MODE_AUTO.equals(value));
817        mSceneIndicator.setVisibility(isGone ? View.GONE : View.VISIBLE);
818    }
819
820    private void updateWhiteBalanceOnScreenIndicator(String value) {
821        if (mWhiteBalanceIndicator == null) {
822            return;
823        }
824        if (value == null || Parameters.WHITE_BALANCE_AUTO.equals(value)) {
825            mWhiteBalanceIndicator.setVisibility(View.GONE);
826        } else {
827            mWhiteBalanceIndicator.setVisibility(View.VISIBLE);
828            if (Parameters.WHITE_BALANCE_FLUORESCENT.equals(value)) {
829                mWhiteBalanceIndicator.setImageResource(R.drawable.ic_indicators_fluorescent);
830            } else if (Parameters.WHITE_BALANCE_INCANDESCENT.equals(value)) {
831                mWhiteBalanceIndicator.setImageResource(R.drawable.ic_indicators_incandescent);
832            } else if (Parameters.WHITE_BALANCE_DAYLIGHT.equals(value)) {
833                mWhiteBalanceIndicator.setImageResource(R.drawable.ic_indicators_sunlight);
834            } else if (Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT.equals(value)) {
835                mWhiteBalanceIndicator.setImageResource(R.drawable.ic_indicators_cloudy);
836            } else {
837                // Should not happen.
838                mWhiteBalanceIndicator.setVisibility(View.GONE);
839            }
840        }
841    }
842
843    private void updateFocusOnScreenIndicator(String value) {
844        if (mFocusIndicator == null) {
845            return;
846        }
847        // Do not show the indicator if users cannot choose.
848        if (mPreferenceGroup.findPreference(CameraSettings.KEY_FOCUS_MODE) == null) {
849            mFocusIndicator.setVisibility(View.GONE);
850        } else {
851            mFocusIndicator.setVisibility(View.VISIBLE);
852            if (Parameters.FOCUS_MODE_INFINITY.equals(value)) {
853                mFocusIndicator.setImageResource(R.drawable.ic_indicators_landscape);
854            } else if (Parameters.FOCUS_MODE_MACRO.equals(value)) {
855                mFocusIndicator.setImageResource(R.drawable.ic_indicators_macro);
856            } else {
857                // Should not happen.
858                mFocusIndicator.setVisibility(View.GONE);
859            }
860        }
861    }
862
863    private void updateOnScreenIndicators() {
864        updateSceneOnScreenIndicator(mParameters.getSceneMode());
865        updateExposureOnScreenIndicator(CameraSettings.readExposure(mPreferences));
866        updateFlashOnScreenIndicator(mParameters.getFlashMode());
867        updateWhiteBalanceOnScreenIndicator(mParameters.getWhiteBalance());
868        updateFocusOnScreenIndicator(mParameters.getFocusMode());
869    }
870
871    private final class ShutterCallback
872            implements android.hardware.Camera.ShutterCallback {
873        @Override
874        public void onShutter() {
875            mShutterCallbackTime = System.currentTimeMillis();
876            mShutterLag = mShutterCallbackTime - mCaptureStartTime;
877            Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
878        }
879    }
880
881    private final class PostViewPictureCallback implements PictureCallback {
882        @Override
883        public void onPictureTaken(
884                byte [] data, android.hardware.Camera camera) {
885            mPostViewPictureCallbackTime = System.currentTimeMillis();
886            Log.v(TAG, "mShutterToPostViewCallbackTime = "
887                    + (mPostViewPictureCallbackTime - mShutterCallbackTime)
888                    + "ms");
889        }
890    }
891
892    private final class RawPictureCallback implements PictureCallback {
893        @Override
894        public void onPictureTaken(
895                byte [] rawData, android.hardware.Camera camera) {
896            mRawPictureCallbackTime = System.currentTimeMillis();
897            Log.v(TAG, "mShutterToRawCallbackTime = "
898                    + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
899        }
900    }
901
902    private final class JpegPictureCallback implements PictureCallback {
903        Location mLocation;
904
905        public JpegPictureCallback(Location loc) {
906            mLocation = loc;
907        }
908
909        @Override
910        public void onPictureTaken(
911                final byte [] jpegData, final android.hardware.Camera camera) {
912            if (mPaused) {
913                return;
914            }
915
916            mJpegPictureCallbackTime = System.currentTimeMillis();
917            // If postview callback has arrived, the captured image is displayed
918            // in postview callback. If not, the captured image is displayed in
919            // raw picture callback.
920            if (mPostViewPictureCallbackTime != 0) {
921                mShutterToPictureDisplayedTime =
922                        mPostViewPictureCallbackTime - mShutterCallbackTime;
923                mPictureDisplayedToJpegCallbackTime =
924                        mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
925            } else {
926                mShutterToPictureDisplayedTime =
927                        mRawPictureCallbackTime - mShutterCallbackTime;
928                mPictureDisplayedToJpegCallbackTime =
929                        mJpegPictureCallbackTime - mRawPictureCallbackTime;
930            }
931            Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
932                    + mPictureDisplayedToJpegCallbackTime + "ms");
933
934            mFocusManager.updateFocusUI(); // Ensure focus indicator is hidden.
935            if (!mIsImageCaptureIntent) {
936                if (ApiHelper.CAN_START_PREVIEW_IN_JPEG_CALLBACK) {
937                    setupPreview();
938                } else {
939                    // Camera HAL of some devices have a bug. Starting preview
940                    // immediately after taking a picture will fail. Wait some
941                    // time before starting the preview.
942                    mHandler.sendEmptyMessageDelayed(SETUP_PREVIEW, 300);
943                }
944            }
945
946            if (!mIsImageCaptureIntent) {
947                // Calculate the width and the height of the jpeg.
948                Size s = mParameters.getPictureSize();
949                int orientation = Exif.getOrientation(jpegData);
950                int width, height;
951                if ((mJpegRotation + orientation) % 180 == 0) {
952                    width = s.width;
953                    height = s.height;
954                } else {
955                    width = s.height;
956                    height = s.width;
957                }
958                Uri uri = mImageNamer.getUri();
959                mActivity.addSecureAlbumItemIfNeeded(false, uri);
960                String title = mImageNamer.getTitle();
961                mImageSaver.addImage(jpegData, uri, title, mLocation,
962                        width, height, mActivity.mThumbnailViewWidth, orientation);
963            } else {
964                mJpegImageData = jpegData;
965                if (!mQuickCapture) {
966                    showPostCaptureAlert();
967                } else {
968                    doAttach();
969                }
970            }
971
972            // Check this in advance of each shot so we don't add to shutter
973            // latency. It's true that someone else could write to the SD card in
974            // the mean time and fill it, but that could have happened between the
975            // shutter press and saving the JPEG too.
976            mActivity.updateStorageSpaceAndHint();
977
978            long now = System.currentTimeMillis();
979            mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
980            Log.v(TAG, "mJpegCallbackFinishTime = "
981                    + mJpegCallbackFinishTime + "ms");
982            mJpegPictureCallbackTime = 0;
983        }
984    }
985
986    private final class AutoFocusCallback
987            implements android.hardware.Camera.AutoFocusCallback {
988        @Override
989        public void onAutoFocus(
990                boolean focused, android.hardware.Camera camera) {
991            if (mPaused) return;
992
993            mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;
994            Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms");
995            setCameraState(IDLE);
996            mFocusManager.onAutoFocus(focused);
997        }
998    }
999
1000    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
1001    private final class AutoFocusMoveCallback
1002            implements android.hardware.Camera.AutoFocusMoveCallback {
1003        @Override
1004        public void onAutoFocusMoving(
1005            boolean moving, android.hardware.Camera camera) {
1006                mFocusManager.onAutoFocusMoving(moving);
1007        }
1008    }
1009
1010    // Each SaveRequest remembers the data needed to save an image.
1011    private static class SaveRequest {
1012        byte[] data;
1013        Uri uri;
1014        String title;
1015        Location loc;
1016        int width, height;
1017        int thumbnailWidth;
1018        int orientation;
1019    }
1020
1021    // We use a queue to store the SaveRequests that have not been completed
1022    // yet. The main thread puts the request into the queue. The saver thread
1023    // gets it from the queue, does the work, and removes it from the queue.
1024    //
1025    // The main thread needs to wait for the saver thread to finish all the work
1026    // in the queue, when the activity's onPause() is called, we need to finish
1027    // all the work, so other programs (like Gallery) can see all the images.
1028    //
1029    // If the queue becomes too long, adding a new request will block the main
1030    // thread until the queue length drops below the threshold (QUEUE_LIMIT).
1031    // If we don't do this, we may face several problems: (1) We may OOM
1032    // because we are holding all the jpeg data in memory. (2) We may ANR
1033    // when we need to wait for saver thread finishing all the work (in
1034    // onPause() or gotoGallery()) because the time to finishing a long queue
1035    // of work may be too long.
1036    private class ImageSaver extends Thread {
1037        private static final int QUEUE_LIMIT = 3;
1038
1039        private ArrayList<SaveRequest> mQueue;
1040        private Thumbnail mPendingThumbnail;
1041        private Object mUpdateThumbnailLock = new Object();
1042        private boolean mStop;
1043
1044        // Runs in main thread
1045        public ImageSaver() {
1046            mQueue = new ArrayList<SaveRequest>();
1047            start();
1048        }
1049
1050        // Runs in main thread
1051        public void addImage(final byte[] data, Uri uri, String title,
1052                Location loc, int width, int height, int thumbnailWidth,
1053                int orientation) {
1054            SaveRequest r = new SaveRequest();
1055            r.data = data;
1056            r.uri = uri;
1057            r.title = title;
1058            r.loc = (loc == null) ? null : new Location(loc);  // make a copy
1059            r.width = width;
1060            r.height = height;
1061            r.thumbnailWidth = thumbnailWidth;
1062            r.orientation = orientation;
1063            synchronized (this) {
1064                while (mQueue.size() >= QUEUE_LIMIT) {
1065                    try {
1066                        wait();
1067                    } catch (InterruptedException ex) {
1068                        // ignore.
1069                    }
1070                }
1071                mQueue.add(r);
1072                notifyAll();  // Tell saver thread there is new work to do.
1073            }
1074        }
1075
1076        // Runs in saver thread
1077        @Override
1078        public void run() {
1079            while (true) {
1080                SaveRequest r;
1081                synchronized (this) {
1082                    if (mQueue.isEmpty()) {
1083                        notifyAll();  // notify main thread in waitDone
1084
1085                        // Note that we can only stop after we saved all images
1086                        // in the queue.
1087                        if (mStop) break;
1088
1089                        try {
1090                            wait();
1091                        } catch (InterruptedException ex) {
1092                            // ignore.
1093                        }
1094                        continue;
1095                    }
1096                    r = mQueue.get(0);
1097                }
1098                storeImage(r.data, r.uri, r.title, r.loc, r.width, r.height,
1099                        r.thumbnailWidth, r.orientation);
1100                synchronized (this) {
1101                    mQueue.remove(0);
1102                    notifyAll();  // the main thread may wait in addImage
1103                }
1104            }
1105        }
1106
1107        // Runs in main thread
1108        public void waitDone() {
1109            synchronized (this) {
1110                while (!mQueue.isEmpty()) {
1111                    try {
1112                        wait();
1113                    } catch (InterruptedException ex) {
1114                        // ignore.
1115                    }
1116                }
1117            }
1118            updateThumbnail();
1119        }
1120
1121        // Runs in main thread
1122        public void finish() {
1123            waitDone();
1124            synchronized (this) {
1125                mStop = true;
1126                notifyAll();
1127            }
1128            try {
1129                join();
1130            } catch (InterruptedException ex) {
1131                // ignore.
1132            }
1133        }
1134
1135        // Runs in main thread (because we need to update mThumbnailView in the
1136        // main thread)
1137        public void updateThumbnail() {
1138            Thumbnail t;
1139            synchronized (mUpdateThumbnailLock) {
1140                mHandler.removeMessages(UPDATE_THUMBNAIL);
1141                t = mPendingThumbnail;
1142                mPendingThumbnail = null;
1143            }
1144
1145            if (t != null) {
1146                mActivity.mThumbnail = t;
1147                if (mActivity.mThumbnailView != null) {
1148                    mActivity.mThumbnailView.setBitmap(mActivity.mThumbnail.getBitmap());
1149                }
1150            }
1151        }
1152
1153        // Runs in saver thread
1154        private void storeImage(final byte[] data, Uri uri, String title,
1155                Location loc, int width, int height, int thumbnailWidth,
1156                int orientation) {
1157            boolean ok = Storage.updateImage(mContentResolver, uri, title, loc,
1158                    orientation, data, width, height);
1159            if (ok) {
1160                boolean needThumbnail;
1161                synchronized (this) {
1162                    // If the number of requests in the queue (include the
1163                    // current one) is greater than 1, we don't need to generate
1164                    // thumbnail for this image. Because we'll soon replace it
1165                    // with the thumbnail for some image later in the queue.
1166                    needThumbnail = (mQueue.size() <= 1);
1167                }
1168                if (needThumbnail) {
1169                    // Create a thumbnail whose width is equal or bigger than
1170                    // that of the thumbnail view.
1171                    int ratio = (int) Math.ceil((double) width / thumbnailWidth);
1172                    int inSampleSize = Integer.highestOneBit(ratio);
1173                    Thumbnail t = Thumbnail.createThumbnail(
1174                                data, orientation, inSampleSize, uri);
1175                    synchronized (mUpdateThumbnailLock) {
1176                        // We need to update the thumbnail in the main thread,
1177                        // so send a message to run updateThumbnail().
1178                        mPendingThumbnail = t;
1179                        mHandler.sendEmptyMessage(UPDATE_THUMBNAIL);
1180                    }
1181                }
1182                Util.broadcastNewPicture(mActivity, uri);
1183            }
1184        }
1185    }
1186
1187    private static class ImageNamer extends Thread {
1188        private boolean mRequestPending;
1189        private ContentResolver mResolver;
1190        private long mDateTaken;
1191        private int mWidth, mHeight;
1192        private boolean mStop;
1193        private Uri mUri;
1194        private String mTitle;
1195
1196        // Runs in main thread
1197        public ImageNamer() {
1198            start();
1199        }
1200
1201        // Runs in main thread
1202        public synchronized void prepareUri(ContentResolver resolver,
1203                long dateTaken, int width, int height, int rotation) {
1204            if (rotation % 180 != 0) {
1205                int tmp = width;
1206                width = height;
1207                height = tmp;
1208            }
1209            mRequestPending = true;
1210            mResolver = resolver;
1211            mDateTaken = dateTaken;
1212            mWidth = width;
1213            mHeight = height;
1214            notifyAll();
1215        }
1216
1217        // Runs in main thread
1218        public synchronized Uri getUri() {
1219            // wait until the request is done.
1220            while (mRequestPending) {
1221                try {
1222                    wait();
1223                } catch (InterruptedException ex) {
1224                    // ignore.
1225                }
1226            }
1227
1228            // return the uri generated
1229            Uri uri = mUri;
1230            mUri = null;
1231            return uri;
1232        }
1233
1234        // Runs in main thread, should be called after getUri().
1235        public synchronized String getTitle() {
1236            return mTitle;
1237        }
1238
1239        // Runs in namer thread
1240        @Override
1241        public synchronized void run() {
1242            while (true) {
1243                if (mStop) break;
1244                if (!mRequestPending) {
1245                    try {
1246                        wait();
1247                    } catch (InterruptedException ex) {
1248                        // ignore.
1249                    }
1250                    continue;
1251                }
1252                cleanOldUri();
1253                generateUri();
1254                mRequestPending = false;
1255                notifyAll();
1256            }
1257            cleanOldUri();
1258        }
1259
1260        // Runs in main thread
1261        public synchronized void finish() {
1262            mStop = true;
1263            notifyAll();
1264        }
1265
1266        // Runs in namer thread
1267        private void generateUri() {
1268            mTitle = Util.createJpegName(mDateTaken);
1269            mUri = Storage.newImage(mResolver, mTitle, mDateTaken, mWidth, mHeight);
1270        }
1271
1272        // Runs in namer thread
1273        private void cleanOldUri() {
1274            if (mUri == null) return;
1275            Storage.deleteImage(mResolver, mUri);
1276            mUri = null;
1277        }
1278    }
1279
1280    private void setCameraState(int state) {
1281        mCameraState = state;
1282        switch (state) {
1283            case PREVIEW_STOPPED:
1284            case SNAPSHOT_IN_PROGRESS:
1285            case FOCUSING:
1286            case SWITCHING_CAMERA:
1287//                enableCameraControls(false);
1288                break;
1289            case IDLE:
1290//                enableCameraControls(true);
1291                break;
1292        }
1293    }
1294
1295    @Override
1296    public boolean capture() {
1297        // If we are already in the middle of taking a snapshot then ignore.
1298        if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS
1299                || mCameraState == SWITCHING_CAMERA) {
1300            return false;
1301        }
1302        mCaptureStartTime = System.currentTimeMillis();
1303        mPostViewPictureCallbackTime = 0;
1304        mJpegImageData = null;
1305
1306        // Set rotation and gps data.
1307        mJpegRotation = Util.getJpegRotation(mCameraId, mOrientation);
1308        mParameters.setRotation(mJpegRotation);
1309        Location loc = mLocationManager.getCurrentLocation();
1310        Util.setGpsParameters(mParameters, loc);
1311        mCameraDevice.setParameters(mParameters);
1312
1313        mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback,
1314                mPostViewPictureCallback, new JpegPictureCallback(loc));
1315
1316        Size size = mParameters.getPictureSize();
1317        mImageNamer.prepareUri(mContentResolver, mCaptureStartTime,
1318                size.width, size.height, mJpegRotation);
1319
1320        if (ApiHelper.HAS_SURFACE_TEXTURE && !mIsImageCaptureIntent) {
1321            // Start capture animation.
1322            ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(getCameraRotation());
1323        }
1324        mFaceDetectionStarted = false;
1325        setCameraState(SNAPSHOT_IN_PROGRESS);
1326        return true;
1327    }
1328
1329    private int getCameraRotation() {
1330        return (mOrientationCompensation - mDisplayRotation + 360) % 360;
1331    }
1332
1333    @Override
1334    public void setFocusParameters() {
1335        setCameraParameters(UPDATE_PARAM_PREFERENCE);
1336    }
1337
1338    @Override
1339    public void playSound(int soundId) {
1340        mSoundPlayer.play(soundId);
1341    }
1342
1343    private int getPreferredCameraId(ComboPreferences preferences) {
1344        int intentCameraId = Util.getCameraFacingIntentExtras(mActivity);
1345        if (intentCameraId != -1) {
1346            // Testing purpose. Launch a specific camera through the intent
1347            // extras.
1348            return intentCameraId;
1349        } else {
1350            return CameraSettings.readPreferredCameraId(preferences);
1351        }
1352    }
1353
1354
1355    @Override
1356    public void onFullScreenChanged(boolean full) {
1357        if (mPopup != null) {
1358            dismissPopup();
1359        }
1360        if (mGestures != null) {
1361            mGestures.setEnabled(full);
1362        }
1363        if (ApiHelper.HAS_SURFACE_TEXTURE) return;
1364        if (full) {
1365            mPreviewSurfaceView.expand();
1366        } else {
1367            mPreviewSurfaceView.shrink();
1368        }
1369    }
1370
1371    @Override
1372    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
1373        Log.v(TAG, "surfaceChanged:" + holder + " width=" + width + ". height="
1374                + height);
1375    }
1376
1377    @Override
1378    public void surfaceCreated(SurfaceHolder holder) {
1379        Log.v(TAG, "surfaceCreated: " + holder);
1380        mCameraSurfaceHolder = holder;
1381        // Do not access the camera if camera start up thread is not finished.
1382        if (mCameraDevice == null || mCameraStartUpThread != null) return;
1383
1384        mCameraDevice.setPreviewDisplayAsync(holder);
1385        // This happens when onConfigurationChanged arrives, surface has been
1386        // destroyed, and there is no onFullScreenChanged.
1387        if (mCameraState == PREVIEW_STOPPED) {
1388            setupPreview();
1389        }
1390    }
1391
1392    @Override
1393    public void surfaceDestroyed(SurfaceHolder holder) {
1394        Log.v(TAG, "surfaceDestroyed: " + holder);
1395        mCameraSurfaceHolder = null;
1396        stopPreview();
1397    }
1398
1399    private void updateSceneModeUI() {
1400        // If scene mode is set, we cannot set flash mode, white balance, and
1401        // focus mode, instead, we read it from driver
1402        if (!Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
1403            overrideCameraSettings(mParameters.getFlashMode(),
1404                    mParameters.getWhiteBalance(), mParameters.getFocusMode());
1405        } else {
1406            overrideCameraSettings(null, null, null);
1407        }
1408    }
1409
1410    private void overrideCameraSettings(final String flashMode,
1411            final String whiteBalance, final String focusMode) {
1412        if (mPhotoControl != null) {
1413//            mPieControl.enableFilter(true);
1414            mPhotoControl.overrideSettings(
1415                    CameraSettings.KEY_FLASH_MODE, flashMode,
1416                    CameraSettings.KEY_WHITE_BALANCE, whiteBalance,
1417                    CameraSettings.KEY_FOCUS_MODE, focusMode);
1418//            mPieControl.enableFilter(false);
1419        }
1420    }
1421
1422    private void loadCameraPreferences() {
1423        CameraSettings settings = new CameraSettings(mActivity, mInitialParams,
1424                mCameraId, CameraHolder.instance().getCameraInfo());
1425        mPreferenceGroup = settings.getPreferenceGroup(R.xml.camera_preferences);
1426    }
1427
1428    @Override
1429    public boolean collapseCameraControls() {
1430        if (mPopup != null) {
1431            dismissPopup();
1432            return true;
1433        }
1434        return false;
1435    }
1436
1437    private class MyOrientationEventListener
1438            extends OrientationEventListener {
1439        public MyOrientationEventListener(Context context) {
1440            super(context);
1441        }
1442
1443        @Override
1444        public void onOrientationChanged(int orientation) {
1445            // We keep the last known orientation. So if the user first orient
1446            // the camera then point the camera to floor or sky, we still have
1447            // the correct orientation.
1448            if (orientation == ORIENTATION_UNKNOWN) return;
1449            mOrientation = Util.roundOrientation(orientation, mOrientation);
1450            // When the screen is unlocked, display rotation may change. Always
1451            // calculate the up-to-date orientationCompensation.
1452            int orientationCompensation =
1453                    (mOrientation + Util.getDisplayRotation(mActivity)) % 360;
1454            if (mOrientationCompensation != orientationCompensation) {
1455                mOrientationCompensation = orientationCompensation;
1456                setOrientationIndicator(mOrientationCompensation, true);
1457            }
1458
1459            // Show the toast after getting the first orientation changed.
1460            if (mHandler.hasMessages(SHOW_TAP_TO_FOCUS_TOAST)) {
1461                mHandler.removeMessages(SHOW_TAP_TO_FOCUS_TOAST);
1462                showTapToFocusToast();
1463            }
1464        }
1465    }
1466
1467    private void setOrientationIndicator(int orientation, boolean animation) {
1468        Rotatable[] indicators = {
1469                mRenderOverlay, mFaceView,
1470                mReviewDoneButton, mRotateDialog, mOnScreenIndicators};
1471        for (Rotatable indicator : indicators) {
1472            if (indicator != null) indicator.setOrientation(orientation, animation);
1473        }
1474
1475        // We change the orientation of the review cancel button only for tablet
1476        // UI because there's a label along with the X icon. For phone UI, we
1477        // don't change the orientation because there's only a symmetrical X
1478        // icon.
1479        if (mReviewCancelButton instanceof RotateLayout) {
1480            mReviewCancelButton.setOrientation(orientation, animation);
1481        }
1482        if (mPopup != null) {
1483            mPopup.setOrientation(orientation, animation);
1484        }
1485    }
1486
1487    @Override
1488    public void onStop() {
1489        if (mMediaProviderClient != null) {
1490            mMediaProviderClient.release();
1491            mMediaProviderClient = null;
1492        }
1493    }
1494
1495    @OnClickAttr
1496    public void onThumbnailClicked(View v) {
1497        if (isCameraIdle() && mActivity.mThumbnail != null) {
1498            if (mImageSaver != null) mImageSaver.waitDone();
1499            mActivity.gotoGallery();
1500        }
1501    }
1502
1503    // onClick handler for R.id.btn_retake
1504    @OnClickAttr
1505    public void onReviewRetakeClicked(View v) {
1506        if (mPaused) return;
1507
1508        hidePostCaptureAlert();
1509        setupPreview();
1510    }
1511
1512    // onClick handler for R.id.btn_done
1513    @OnClickAttr
1514    public void onReviewDoneClicked(View v) {
1515        doAttach();
1516    }
1517
1518    // onClick handler for R.id.btn_cancel
1519    @OnClickAttr
1520    public void onReviewCancelClicked(View v) {
1521        doCancel();
1522    }
1523
1524    private void doAttach() {
1525        if (mPaused) {
1526            return;
1527        }
1528
1529        byte[] data = mJpegImageData;
1530
1531        if (mCropValue == null) {
1532            // First handle the no crop case -- just return the value.  If the
1533            // caller specifies a "save uri" then write the data to its
1534            // stream. Otherwise, pass back a scaled down version of the bitmap
1535            // directly in the extras.
1536            if (mSaveUri != null) {
1537                OutputStream outputStream = null;
1538                try {
1539                    outputStream = mContentResolver.openOutputStream(mSaveUri);
1540                    outputStream.write(data);
1541                    outputStream.close();
1542
1543                    mActivity.setResultEx(Activity.RESULT_OK);
1544                    mActivity.finish();
1545                } catch (IOException ex) {
1546                    // ignore exception
1547                } finally {
1548                    Util.closeSilently(outputStream);
1549                }
1550            } else {
1551                int orientation = Exif.getOrientation(data);
1552                Bitmap bitmap = Util.makeBitmap(data, 50 * 1024);
1553                bitmap = Util.rotate(bitmap, orientation);
1554                mActivity.setResultEx(Activity.RESULT_OK,
1555                        new Intent("inline-data").putExtra("data", bitmap));
1556                mActivity.finish();
1557            }
1558        } else {
1559            // Save the image to a temp file and invoke the cropper
1560            Uri tempUri = null;
1561            FileOutputStream tempStream = null;
1562            try {
1563                File path = mActivity.getFileStreamPath(sTempCropFilename);
1564                path.delete();
1565                tempStream = mActivity.openFileOutput(sTempCropFilename, 0);
1566                tempStream.write(data);
1567                tempStream.close();
1568                tempUri = Uri.fromFile(path);
1569            } catch (FileNotFoundException ex) {
1570                mActivity.setResultEx(Activity.RESULT_CANCELED);
1571                mActivity.finish();
1572                return;
1573            } catch (IOException ex) {
1574                mActivity.setResultEx(Activity.RESULT_CANCELED);
1575                mActivity.finish();
1576                return;
1577            } finally {
1578                Util.closeSilently(tempStream);
1579            }
1580
1581            Bundle newExtras = new Bundle();
1582            if (mCropValue.equals("circle")) {
1583                newExtras.putString("circleCrop", "true");
1584            }
1585            if (mSaveUri != null) {
1586                newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
1587            } else {
1588                newExtras.putBoolean("return-data", true);
1589            }
1590            if (mActivity.isSecureCamera()) {
1591                newExtras.putBoolean(CropImage.KEY_SHOW_WHEN_LOCKED, true);
1592            }
1593
1594            Intent cropIntent = new Intent("com.android.camera.action.CROP");
1595
1596            cropIntent.setData(tempUri);
1597            cropIntent.putExtras(newExtras);
1598
1599            mActivity.startActivityForResult(cropIntent, REQUEST_CROP);
1600        }
1601    }
1602
1603    private void doCancel() {
1604        mActivity.setResultEx(Activity.RESULT_CANCELED, new Intent());
1605        mActivity.finish();
1606    }
1607
1608    @Override
1609    public void onShutterButtonFocus(boolean pressed) {
1610        if (mPaused || collapseCameraControls()
1611                || (mCameraState == SNAPSHOT_IN_PROGRESS)
1612                || (mCameraState == PREVIEW_STOPPED)) return;
1613
1614        // Do not do focus if there is not enough storage.
1615        if (pressed && !canTakePicture()) return;
1616
1617        if (pressed) {
1618            mFocusManager.onShutterDown();
1619        } else {
1620            mFocusManager.onShutterUp();
1621        }
1622    }
1623
1624    @Override
1625    public void onShutterButtonClick() {
1626        if (mPaused || collapseCameraControls()
1627                || (mCameraState == SWITCHING_CAMERA)
1628                || (mCameraState == PREVIEW_STOPPED)) return;
1629
1630        // Do not take the picture if there is not enough storage.
1631        if (mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) {
1632            Log.i(TAG, "Not enough space or storage not ready. remaining="
1633                    + mActivity.getStorageSpace());
1634            return;
1635        }
1636        Log.v(TAG, "onShutterButtonClick: mCameraState=" + mCameraState);
1637
1638        // If the user wants to do a snapshot while the previous one is still
1639        // in progress, remember the fact and do it after we finish the previous
1640        // one and re-start the preview. Snapshot in progress also includes the
1641        // state that autofocus is focusing and a picture will be taken when
1642        // focus callback arrives.
1643        if ((mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS)
1644                && !mIsImageCaptureIntent) {
1645            mSnapshotOnIdle = true;
1646            return;
1647        }
1648
1649        mSnapshotOnIdle = false;
1650        mFocusManager.doSnap();
1651    }
1652
1653    @Override
1654    public void installIntentFilter() {
1655        // install an intent filter to receive SD card related events.
1656        IntentFilter intentFilter =
1657                new IntentFilter(Intent.ACTION_MEDIA_SCANNER_FINISHED);
1658        intentFilter.addDataScheme("file");
1659        mActivity.registerReceiver(mReceiver, intentFilter);
1660        mDidRegister = true;
1661    }
1662
1663    @Override
1664    public boolean updateStorageHintOnResume() {
1665        return mFirstTimeInitialized;
1666    }
1667
1668    @Override
1669    public void updateCameraAppView() {
1670    }
1671
1672    @Override
1673    public void onResumeBeforeSuper() {
1674        mPaused = false;
1675    }
1676
1677    @Override
1678    public void onResumeAfterSuper() {
1679        if (mOpenCameraFail || mCameraDisabled) return;
1680
1681        mJpegPictureCallbackTime = 0;
1682        mZoomValue = 0;
1683
1684        // Start the preview if it is not started.
1685        if (mCameraState == PREVIEW_STOPPED && mCameraStartUpThread == null) {
1686            resetExposureCompensation();
1687            mCameraStartUpThread = new CameraStartUpThread();
1688            mCameraStartUpThread.start();
1689        }
1690
1691        if (!mIsImageCaptureIntent) mActivity.getLastThumbnail();
1692
1693        // If first time initialization is not finished, put it in the
1694        // message queue.
1695        if (!mFirstTimeInitialized) {
1696            mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1697        } else {
1698            initializeSecondTime();
1699        }
1700        keepScreenOnAwhile();
1701
1702        // Dismiss open menu if exists.
1703        PopupManager.getInstance(mActivity).notifyShowPopup(null);
1704
1705        mSoundPlayer = SoundClips.getPlayer(mActivity);
1706    }
1707
1708    void waitCameraStartUpThread() {
1709        try {
1710            if (mCameraStartUpThread != null) {
1711                mCameraStartUpThread.cancel();
1712                mCameraStartUpThread.join();
1713                mCameraStartUpThread = null;
1714                setCameraState(IDLE);
1715            }
1716        } catch (InterruptedException e) {
1717            // ignore
1718        }
1719    }
1720
1721    @Override
1722    public void onPauseBeforeSuper() {
1723        mPaused = true;
1724    }
1725
1726    @Override
1727    public void onPauseAfterSuper() {
1728        // Wait the camera start up thread to finish.
1729        waitCameraStartUpThread();
1730
1731        // When camera is started from secure lock screen for the first time
1732        // after screen on, the activity gets onCreate->onResume->onPause->onResume.
1733        // To reduce the latency, keep the camera for a short time so it does
1734        // not need to be opened again.
1735        if (mCameraDevice != null && mActivity.isSecureCamera()
1736                && ActivityBase.isFirstStartAfterScreenOn()) {
1737            ActivityBase.resetFirstStartAfterScreenOn();
1738            CameraHolder.instance().keep(KEEP_CAMERA_TIMEOUT);
1739        }
1740        stopPreview();
1741        // Close the camera now because other activities may need to use it.
1742        closeCamera();
1743        if (mSurfaceTexture != null) {
1744            ((CameraScreenNail) mActivity.mCameraScreenNail).releaseSurfaceTexture();
1745            mSurfaceTexture = null;
1746        }
1747        if (mSoundPlayer != null) {
1748            mSoundPlayer.release();
1749            mSoundPlayer = null;
1750        }
1751        resetScreenOn();
1752
1753        // Clear UI.
1754        collapseCameraControls();
1755        if (mFaceView != null) mFaceView.clear();
1756
1757        if (mFirstTimeInitialized) {
1758            mOrientationListener.disable();
1759            if (mImageSaver != null) {
1760                mImageSaver.finish();
1761                mImageSaver = null;
1762                mImageNamer.finish();
1763                mImageNamer = null;
1764            }
1765        }
1766
1767        if (mDidRegister) {
1768            mActivity.unregisterReceiver(mReceiver);
1769            mDidRegister = false;
1770        }
1771        if (mLocationManager != null) mLocationManager.recordLocation(false);
1772        updateExposureOnScreenIndicator(0);
1773
1774        // If we are in an image capture intent and has taken
1775        // a picture, we just clear it in onPause.
1776        mJpegImageData = null;
1777
1778        // Remove the messages in the event queue.
1779        mHandler.removeMessages(SETUP_PREVIEW);
1780        mHandler.removeMessages(FIRST_TIME_INIT);
1781        mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
1782        mHandler.removeMessages(SWITCH_CAMERA);
1783        mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION);
1784        mHandler.removeMessages(CAMERA_OPEN_DONE);
1785        mHandler.removeMessages(START_PREVIEW_DONE);
1786        mHandler.removeMessages(OPEN_CAMERA_FAIL);
1787        mHandler.removeMessages(CAMERA_DISABLED);
1788
1789        mPendingSwitchCameraId = -1;
1790        if (mFocusManager != null) mFocusManager.removeMessages();
1791    }
1792
1793    private void initializeControlByIntent() {
1794        if (mIsImageCaptureIntent) {
1795
1796            mActivity.hideSwitcher();
1797            // Cannot use RotateImageView for "done" and "cancel" button because
1798            // the tablet layout uses RotateLayout, which cannot be cast to
1799            // RotateImageView.
1800            mReviewDoneButton = (Rotatable) mRootView.findViewById(R.id.btn_done);
1801            mReviewCancelButton = (Rotatable) mRootView.findViewById(R.id.btn_cancel);
1802
1803            ((View) mReviewCancelButton).setVisibility(View.VISIBLE);
1804
1805            ((View) mReviewDoneButton).setOnClickListener(new OnClickListener() {
1806                @Override
1807                public void onClick(View v) {
1808                    onReviewDoneClicked(v);
1809                }
1810            });
1811            ((View) mReviewCancelButton).setOnClickListener(new OnClickListener() {
1812                @Override
1813                public void onClick(View v) {
1814                    onReviewCancelClicked(v);
1815                }
1816            });
1817
1818            // Not grayed out upon disabled, to make the follow-up fade-out
1819            // effect look smooth. Note that the review done button in tablet
1820            // layout is not a TwoStateImageView.
1821            if (mReviewDoneButton instanceof TwoStateImageView) {
1822                ((TwoStateImageView) mReviewDoneButton).enableFilter(false);
1823            }
1824
1825            setupCaptureParams();
1826        } else {
1827            mActivity.mThumbnailView = ((RotateImageView) mRootView.findViewById(R.id.thumbnail));
1828            if (mActivity.mThumbnailView != null) {
1829                mActivity.mThumbnailView.enableFilter(false);
1830                mActivity.mThumbnailView.setVisibility(View.VISIBLE);
1831                mActivity.mThumbnailViewWidth = mActivity.mThumbnailView.getLayoutParams().width;
1832            }
1833        }
1834    }
1835
1836    /**
1837     * The focus manager is the first UI related element to get initialized,
1838     * and it requires the RenderOverlay, so initialize it here
1839     */
1840    private void initializeFocusManager() {
1841        // Create FocusManager object. startPreview needs it.
1842        mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
1843        CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1844        boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
1845        String[] defaultFocusModes = mActivity.getResources().getStringArray(
1846                R.array.pref_camera_focusmode_default_array);
1847        mFocusManager = new FocusOverlayManager(mPreferences, defaultFocusModes,
1848                mInitialParams, this, mirror,
1849                mActivity.getMainLooper());
1850    }
1851
1852    private void initializeMiscControls() {
1853        // startPreview needs this.
1854        mPreviewFrameLayout = (PreviewFrameLayout) mRootView.findViewById(R.id.frame);
1855        // Set touch focus listener.
1856        mActivity.setSingleTapUpListener(mPreviewFrameLayout);
1857
1858        mOnScreenIndicators = (Rotatable) mRootView.findViewById(R.id.on_screen_indicators);
1859        mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);
1860        mPreviewFrameLayout.setOnSizeChangedListener(this);
1861        mPreviewFrameLayout.setOnLayoutChangeListener(mActivity);
1862        if (!ApiHelper.HAS_SURFACE_TEXTURE) {
1863            mPreviewSurfaceView =
1864                    (PreviewSurfaceView) mRootView.findViewById(R.id.preview_surface_view);
1865            mPreviewSurfaceView.setVisibility(View.VISIBLE);
1866            mPreviewSurfaceView.getHolder().addCallback(this);
1867        }
1868    }
1869
1870    @Override
1871    public void onConfigurationChanged(Configuration newConfig) {
1872        Log.v(TAG, "onConfigurationChanged");
1873        setDisplayOrientation();
1874
1875        ((ViewGroup) mRootView).removeAllViews();
1876        LayoutInflater inflater = mActivity.getLayoutInflater();
1877        inflater.inflate(R.layout.photo_module, (ViewGroup) mRootView);
1878
1879        // from onCreate()
1880        initializeControlByIntent();
1881        if (mFocusManager != null) mFocusManager.removeMessages();
1882        initializeFocusManager();
1883        initializeMiscControls();
1884        loadCameraPreferences();
1885        initializePhotoControl();
1886
1887        // from onResume()
1888        if (!mIsImageCaptureIntent) mActivity.updateThumbnailView();
1889
1890        // from initializeFirstTime()
1891        mShutterButton = mActivity.getShutterButton();
1892        mShutterButton.setOnShutterButtonListener(this);
1893        initializeZoom();
1894        initOnScreenIndicator();
1895        updateOnScreenIndicators();
1896        if (mFaceView != null) {
1897            mFaceView.clear();
1898            mFaceView.setVisibility(View.VISIBLE);
1899            mFaceView.setDisplayOrientation(mDisplayOrientation);
1900            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1901            mFaceView.setMirror(info.facing == CameraInfo.CAMERA_FACING_FRONT);
1902            mFaceView.resume();
1903            mFocusManager.setFaceView(mFaceView);
1904        }
1905    }
1906
1907    @Override
1908    public void onActivityResult(
1909            int requestCode, int resultCode, Intent data) {
1910        switch (requestCode) {
1911            case REQUEST_CROP: {
1912                Intent intent = new Intent();
1913                if (data != null) {
1914                    Bundle extras = data.getExtras();
1915                    if (extras != null) {
1916                        intent.putExtras(extras);
1917                    }
1918                }
1919                mActivity.setResultEx(resultCode, intent);
1920                mActivity.finish();
1921
1922                File path = mActivity.getFileStreamPath(sTempCropFilename);
1923                path.delete();
1924
1925                break;
1926            }
1927        }
1928    }
1929
1930    private boolean canTakePicture() {
1931        return isCameraIdle() && (mActivity.getStorageSpace() > Storage.LOW_STORAGE_THRESHOLD);
1932    }
1933
1934    @Override
1935    public void autoFocus() {
1936        mFocusStartTime = System.currentTimeMillis();
1937        mCameraDevice.autoFocus(mAutoFocusCallback);
1938        setCameraState(FOCUSING);
1939    }
1940
1941    @Override
1942    public void cancelAutoFocus() {
1943        mCameraDevice.cancelAutoFocus();
1944        setCameraState(IDLE);
1945        setCameraParameters(UPDATE_PARAM_PREFERENCE);
1946    }
1947
1948    // Preview area is touched. Handle touch focus.
1949    @Override
1950    public void onSingleTapUp(View view, int x, int y) {
1951        if (mPaused || mCameraDevice == null || !mFirstTimeInitialized
1952                || mCameraState == SNAPSHOT_IN_PROGRESS
1953                || mCameraState == SWITCHING_CAMERA
1954                || mCameraState == PREVIEW_STOPPED) {
1955            return;
1956        }
1957
1958        // Do not trigger touch focus if popup window is opened.
1959        if (collapseCameraControls()) return;
1960
1961        // Check if metering area or focus area is supported.
1962        if (!mFocusAreaSupported && !mMeteringAreaSupported) return;
1963
1964        mFocusManager.onSingleTapUp(x, y);
1965    }
1966
1967    @Override
1968    public boolean onBackPressed() {
1969        // In image capture mode, back button should:
1970        // 1) if there is any popup, dismiss them, 2) otherwise, get out of image capture
1971        if (mIsImageCaptureIntent) {
1972            if (!collapseCameraControls()) {
1973                // no popup to dismiss, cancel image capture
1974                doCancel();
1975            }
1976            return true;
1977        } else if (!isCameraIdle()) {
1978            // ignore backs while we're taking a picture
1979            return true;
1980        } else {
1981            return collapseCameraControls();
1982        }
1983    }
1984
1985    @Override
1986    public boolean onKeyDown(int keyCode, KeyEvent event) {
1987        switch (keyCode) {
1988            case KeyEvent.KEYCODE_FOCUS:
1989                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1990                    onShutterButtonFocus(true);
1991                }
1992                return true;
1993            case KeyEvent.KEYCODE_CAMERA:
1994                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1995                    onShutterButtonClick();
1996                }
1997                return true;
1998            case KeyEvent.KEYCODE_DPAD_CENTER:
1999                // If we get a dpad center event without any focused view, move
2000                // the focus to the shutter button and press it.
2001                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
2002                    // Start auto-focus immediately to reduce shutter lag. After
2003                    // the shutter button gets the focus, onShutterButtonFocus()
2004                    // will be called again but it is fine.
2005                    if (collapseCameraControls()) return true;
2006                    onShutterButtonFocus(true);
2007                    if (mShutterButton.isInTouchMode()) {
2008                        mShutterButton.requestFocusFromTouch();
2009                    } else {
2010                        mShutterButton.requestFocus();
2011                    }
2012                    mShutterButton.setPressed(true);
2013                }
2014                return true;
2015        }
2016        return false;
2017    }
2018
2019    @Override
2020    public boolean onKeyUp(int keyCode, KeyEvent event) {
2021        switch (keyCode) {
2022            case KeyEvent.KEYCODE_FOCUS:
2023                if (mFirstTimeInitialized) {
2024                    onShutterButtonFocus(false);
2025                }
2026                return true;
2027        }
2028        return false;
2029    }
2030
2031    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
2032    private void closeCamera() {
2033        if (mCameraDevice != null) {
2034            mCameraDevice.setZoomChangeListener(null);
2035            if(ApiHelper.HAS_FACE_DETECTION) {
2036                mCameraDevice.setFaceDetectionListener(null);
2037            }
2038            mCameraDevice.setErrorCallback(null);
2039            CameraHolder.instance().release();
2040            mFaceDetectionStarted = false;
2041            mCameraDevice = null;
2042            setCameraState(PREVIEW_STOPPED);
2043            mFocusManager.onCameraReleased();
2044        }
2045    }
2046
2047    private void setDisplayOrientation() {
2048        mDisplayRotation = Util.getDisplayRotation(mActivity);
2049        mDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
2050        mCameraDisplayOrientation = Util.getDisplayOrientation(0, mCameraId);
2051        if (mFaceView != null) {
2052            mFaceView.setDisplayOrientation(mDisplayOrientation);
2053        }
2054        mFocusManager.setDisplayOrientation(mDisplayOrientation);
2055    }
2056
2057    // Only called by UI thread.
2058    private void setupPreview() {
2059        mFocusManager.resetTouchFocus();
2060        startPreview();
2061        setCameraState(IDLE);
2062        startFaceDetection();
2063    }
2064
2065    // This can be called by UI Thread or CameraStartUpThread. So this should
2066    // not modify the views.
2067    private void startPreview() {
2068        mCameraDevice.setErrorCallback(mErrorCallback);
2069
2070        // If we're previewing already, stop the preview first (this will blank
2071        // the screen).
2072        if (mCameraState != PREVIEW_STOPPED) stopPreview();
2073
2074        setDisplayOrientation();
2075
2076        if (!mSnapshotOnIdle) {
2077            // If the focus mode is continuous autofocus, call cancelAutoFocus to
2078            // resume it because it may have been paused by autoFocus call.
2079            if (Util.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusManager.getFocusMode())) {
2080                mCameraDevice.cancelAutoFocus();
2081            }
2082            mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
2083        }
2084        setCameraParameters(UPDATE_PARAM_ALL);
2085
2086        if (ApiHelper.HAS_SURFACE_TEXTURE) {
2087            CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
2088            if (mSurfaceTexture == null) {
2089                Size size = mParameters.getPreviewSize();
2090                if (mCameraDisplayOrientation % 180 == 0) {
2091                    screenNail.setSize(size.width, size.height);
2092                } else {
2093                    screenNail.setSize(size.height, size.width);
2094                }
2095                mActivity.notifyScreenNailChanged();
2096                screenNail.acquireSurfaceTexture();
2097                mSurfaceTexture = screenNail.getSurfaceTexture();
2098            }
2099            mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
2100            mCameraDevice.setPreviewTextureAsync((SurfaceTexture) mSurfaceTexture);
2101        } else {
2102            mCameraDevice.setDisplayOrientation(mDisplayOrientation);
2103            mCameraDevice.setPreviewDisplayAsync(mCameraSurfaceHolder);
2104        }
2105
2106        Log.v(TAG, "startPreview");
2107        mCameraDevice.startPreviewAsync();
2108
2109        mFocusManager.onPreviewStarted();
2110
2111        if (mSnapshotOnIdle) {
2112            mHandler.post(mDoSnapRunnable);
2113        }
2114    }
2115
2116    private void stopPreview() {
2117        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
2118            Log.v(TAG, "stopPreview");
2119            mCameraDevice.cancelAutoFocus(); // Reset the focus.
2120            mCameraDevice.stopPreview();
2121            mFaceDetectionStarted = false;
2122        }
2123        setCameraState(PREVIEW_STOPPED);
2124        if (mFocusManager != null) mFocusManager.onPreviewStopped();
2125    }
2126
2127    @SuppressWarnings("deprecation")
2128    private void updateCameraParametersInitialize() {
2129        // Reset preview frame rate to the maximum because it may be lowered by
2130        // video camera application.
2131        List<Integer> frameRates = mParameters.getSupportedPreviewFrameRates();
2132        if (frameRates != null) {
2133            Integer max = Collections.max(frameRates);
2134            mParameters.setPreviewFrameRate(max);
2135        }
2136
2137        mParameters.set(Util.RECORDING_HINT, Util.FALSE);
2138
2139        // Disable video stabilization. Convenience methods not available in API
2140        // level <= 14
2141        String vstabSupported = mParameters.get("video-stabilization-supported");
2142        if ("true".equals(vstabSupported)) {
2143            mParameters.set("video-stabilization", "false");
2144        }
2145    }
2146
2147    private void updateCameraParametersZoom() {
2148        // Set zoom.
2149        if (mParameters.isZoomSupported()) {
2150            mParameters.setZoom(mZoomValue);
2151        }
2152    }
2153
2154    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
2155    private void setAutoExposureLockIfSupported() {
2156        if (mAeLockSupported) {
2157            mParameters.setAutoExposureLock(mFocusManager.getAeAwbLock());
2158        }
2159    }
2160
2161    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
2162    private void setAutoWhiteBalanceLockIfSupported() {
2163        if (mAwbLockSupported) {
2164            mParameters.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
2165        }
2166    }
2167
2168    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
2169    private void setFocusAreasIfSupported() {
2170        if (mFocusAreaSupported) {
2171            mParameters.setFocusAreas(mFocusManager.getFocusAreas());
2172        }
2173    }
2174
2175    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
2176    private void setMeteringAreasIfSupported() {
2177        if (mMeteringAreaSupported) {
2178            // Use the same area for focus and metering.
2179            mParameters.setMeteringAreas(mFocusManager.getMeteringAreas());
2180        }
2181    }
2182
2183    private void updateCameraParametersPreference() {
2184        setAutoExposureLockIfSupported();
2185        setAutoWhiteBalanceLockIfSupported();
2186        setFocusAreasIfSupported();
2187        setMeteringAreasIfSupported();
2188
2189        // Set picture size.
2190        String pictureSize = mPreferences.getString(
2191                CameraSettings.KEY_PICTURE_SIZE, null);
2192        if (pictureSize == null) {
2193            CameraSettings.initialCameraPictureSize(mActivity, mParameters);
2194        } else {
2195            List<Size> supported = mParameters.getSupportedPictureSizes();
2196            CameraSettings.setCameraPictureSize(
2197                    pictureSize, supported, mParameters);
2198        }
2199        Size size = mParameters.getPictureSize();
2200
2201        // Set a preview size that is closest to the viewfinder height and has
2202        // the right aspect ratio.
2203        List<Size> sizes = mParameters.getSupportedPreviewSizes();
2204        Size optimalSize = Util.getOptimalPreviewSize(mActivity, sizes,
2205                (double) size.width / size.height);
2206        Size original = mParameters.getPreviewSize();
2207        if (!original.equals(optimalSize)) {
2208            mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
2209
2210            // Zoom related settings will be changed for different preview
2211            // sizes, so set and read the parameters to get latest values
2212            mCameraDevice.setParameters(mParameters);
2213            mParameters = mCameraDevice.getParameters();
2214        }
2215        Log.v(TAG, "Preview size is " + optimalSize.width + "x" + optimalSize.height);
2216
2217        // Since change scene mode may change supported values,
2218        // Set scene mode first,
2219        mSceneMode = mPreferences.getString(
2220                CameraSettings.KEY_SCENE_MODE,
2221                mActivity.getString(R.string.pref_camera_scenemode_default));
2222        if (Util.isSupported(mSceneMode, mParameters.getSupportedSceneModes())) {
2223            if (!mParameters.getSceneMode().equals(mSceneMode)) {
2224                mParameters.setSceneMode(mSceneMode);
2225
2226                // Setting scene mode will change the settings of flash mode,
2227                // white balance, and focus mode. Here we read back the
2228                // parameters, so we can know those settings.
2229                mCameraDevice.setParameters(mParameters);
2230                mParameters = mCameraDevice.getParameters();
2231            }
2232        } else {
2233            mSceneMode = mParameters.getSceneMode();
2234            if (mSceneMode == null) {
2235                mSceneMode = Parameters.SCENE_MODE_AUTO;
2236            }
2237        }
2238
2239        // Set JPEG quality.
2240        int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
2241                CameraProfile.QUALITY_HIGH);
2242        mParameters.setJpegQuality(jpegQuality);
2243
2244        // For the following settings, we need to check if the settings are
2245        // still supported by latest driver, if not, ignore the settings.
2246
2247        // Set exposure compensation
2248        int value = CameraSettings.readExposure(mPreferences);
2249        int max = mParameters.getMaxExposureCompensation();
2250        int min = mParameters.getMinExposureCompensation();
2251        if (value >= min && value <= max) {
2252            mParameters.setExposureCompensation(value);
2253        } else {
2254            Log.w(TAG, "invalid exposure range: " + value);
2255        }
2256
2257        if (Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
2258            // Set flash mode.
2259            String flashMode = mPreferences.getString(
2260                    CameraSettings.KEY_FLASH_MODE,
2261                    mActivity.getString(R.string.pref_camera_flashmode_default));
2262            List<String> supportedFlash = mParameters.getSupportedFlashModes();
2263            if (Util.isSupported(flashMode, supportedFlash)) {
2264                mParameters.setFlashMode(flashMode);
2265            } else {
2266                flashMode = mParameters.getFlashMode();
2267                if (flashMode == null) {
2268                    flashMode = mActivity.getString(
2269                            R.string.pref_camera_flashmode_no_flash);
2270                }
2271            }
2272
2273            // Set white balance parameter.
2274            String whiteBalance = mPreferences.getString(
2275                    CameraSettings.KEY_WHITE_BALANCE,
2276                    mActivity.getString(R.string.pref_camera_whitebalance_default));
2277            if (Util.isSupported(whiteBalance,
2278                    mParameters.getSupportedWhiteBalance())) {
2279                mParameters.setWhiteBalance(whiteBalance);
2280            } else {
2281                whiteBalance = mParameters.getWhiteBalance();
2282                if (whiteBalance == null) {
2283                    whiteBalance = Parameters.WHITE_BALANCE_AUTO;
2284                }
2285            }
2286
2287            // Set focus mode.
2288            mFocusManager.overrideFocusMode(null);
2289            mParameters.setFocusMode(mFocusManager.getFocusMode());
2290        } else {
2291            mFocusManager.overrideFocusMode(mParameters.getFocusMode());
2292        }
2293
2294        if (mContinousFocusSupported && ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK) {
2295            updateAutoFocusMoveCallback();
2296        }
2297    }
2298
2299    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
2300    private void updateAutoFocusMoveCallback() {
2301        if (mParameters.getFocusMode().equals(Util.FOCUS_MODE_CONTINUOUS_PICTURE)) {
2302            mCameraDevice.setAutoFocusMoveCallback(
2303                (AutoFocusMoveCallback) mAutoFocusMoveCallback);
2304        } else {
2305            mCameraDevice.setAutoFocusMoveCallback(null);
2306        }
2307    }
2308
2309    // We separate the parameters into several subsets, so we can update only
2310    // the subsets actually need updating. The PREFERENCE set needs extra
2311    // locking because the preference can be changed from GLThread as well.
2312    private void setCameraParameters(int updateSet) {
2313        if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
2314            updateCameraParametersInitialize();
2315        }
2316
2317        if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
2318            updateCameraParametersZoom();
2319        }
2320
2321        if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
2322            updateCameraParametersPreference();
2323        }
2324
2325        mCameraDevice.setParameters(mParameters);
2326    }
2327
2328    // If the Camera is idle, update the parameters immediately, otherwise
2329    // accumulate them in mUpdateSet and update later.
2330    private void setCameraParametersWhenIdle(int additionalUpdateSet) {
2331        mUpdateSet |= additionalUpdateSet;
2332        if (mCameraDevice == null) {
2333            // We will update all the parameters when we open the device, so
2334            // we don't need to do anything now.
2335            mUpdateSet = 0;
2336            return;
2337        } else if (isCameraIdle()) {
2338            setCameraParameters(mUpdateSet);
2339            updateSceneModeUI();
2340            mUpdateSet = 0;
2341        } else {
2342            if (!mHandler.hasMessages(SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
2343                mHandler.sendEmptyMessageDelayed(
2344                        SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
2345            }
2346        }
2347    }
2348
2349    private boolean isCameraIdle() {
2350        return (mCameraState == IDLE) ||
2351                ((mFocusManager != null) && mFocusManager.isFocusCompleted()
2352                        && (mCameraState != SWITCHING_CAMERA));
2353    }
2354
2355    private boolean isImageCaptureIntent() {
2356        String action = mActivity.getIntent().getAction();
2357        return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
2358                || ActivityBase.ACTION_IMAGE_CAPTURE_SECURE.equals(action));
2359    }
2360
2361    private void setupCaptureParams() {
2362        Bundle myExtras = mActivity.getIntent().getExtras();
2363        if (myExtras != null) {
2364            mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
2365            mCropValue = myExtras.getString("crop");
2366        }
2367    }
2368
2369    private void showPostCaptureAlert() {
2370        if (mIsImageCaptureIntent) {
2371            Util.fadeOut(mShutterButton);
2372
2373//            Util.fadeIn(mReviewRetakeButton);
2374            Util.fadeIn((View) mReviewDoneButton);
2375        }
2376    }
2377
2378    private void hidePostCaptureAlert() {
2379        if (mIsImageCaptureIntent) {
2380//            Util.fadeOut(mReviewRetakeButton);
2381            Util.fadeOut((View) mReviewDoneButton);
2382
2383            Util.fadeIn(mShutterButton);
2384        }
2385    }
2386
2387    @Override
2388    public void onSharedPreferenceChanged() {
2389        // ignore the events after "onPause()"
2390        if (mPaused) return;
2391
2392        boolean recordLocation = RecordLocationPreference.get(
2393                mPreferences, mContentResolver);
2394        mLocationManager.recordLocation(recordLocation);
2395
2396        setCameraParametersWhenIdle(UPDATE_PARAM_PREFERENCE);
2397        setPreviewFrameLayoutAspectRatio();
2398        updateOnScreenIndicators();
2399    }
2400
2401    @Override
2402    public void onCameraPickerClicked(int cameraId) {
2403        if (mPaused || mPendingSwitchCameraId != -1) return;
2404
2405        mPendingSwitchCameraId = cameraId;
2406        if (ApiHelper.HAS_SURFACE_TEXTURE) {
2407            Log.v(TAG, "Start to copy texture. cameraId=" + cameraId);
2408            // We need to keep a preview frame for the animation before
2409            // releasing the camera. This will trigger onPreviewTextureCopied.
2410            ((CameraScreenNail) mActivity.mCameraScreenNail).copyTexture();
2411            // Disable all camera controls.
2412            setCameraState(SWITCHING_CAMERA);
2413        } else {
2414            switchCamera();
2415        }
2416    }
2417
2418    private void switchCamera() {
2419        if (mPaused) return;
2420
2421        Log.v(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId);
2422        mCameraId = mPendingSwitchCameraId;
2423        mPendingSwitchCameraId = -1;
2424        mPhotoControl.setCameraId(mCameraId);
2425
2426        // from onPause
2427        closeCamera();
2428        collapseCameraControls();
2429        if (mFaceView != null) mFaceView.clear();
2430        if (mFocusManager != null) mFocusManager.removeMessages();
2431
2432        // Restart the camera and initialize the UI. From onCreate.
2433        mPreferences.setLocalId(mActivity, mCameraId);
2434        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
2435        CameraOpenThread cameraOpenThread = new CameraOpenThread();
2436        cameraOpenThread.start();
2437        try {
2438            cameraOpenThread.join();
2439        } catch (InterruptedException ex) {
2440            // ignore
2441        }
2442        initializeCapabilities();
2443        CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
2444        boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
2445        mFocusManager.setMirror(mirror);
2446        mFocusManager.setParameters(mInitialParams);
2447        setupPreview();
2448        loadCameraPreferences();
2449        initializePhotoControl();
2450
2451        // from onResume
2452        setOrientationIndicator(mOrientationCompensation, false);
2453        // from initializeFirstTime
2454        initializeZoom();
2455        updateOnScreenIndicators();
2456        showTapToFocusToastIfNeeded();
2457
2458        if (ApiHelper.HAS_SURFACE_TEXTURE) {
2459            // Start switch camera animation. Post a message because
2460            // onFrameAvailable from the old camera may already exist.
2461            mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
2462        }
2463    }
2464
2465    @Override
2466    public void onPieOpened(int centerX, int centerY) {
2467        mActivity.cancelActivityTouchHandling();
2468        mActivity.setSwipingEnabled(false);
2469        if (mFocusManager != null) {
2470            mFocusManager.setEnabled(false);
2471            mFocusRenderer.showFocus(centerX, centerY);
2472        }
2473    }
2474
2475    @Override
2476    public void onPieClosed() {
2477        mActivity.setSwipingEnabled(true);
2478        if (mFocusManager != null) {
2479            mFocusRenderer.setVisible(false);
2480            mFocusManager.setEnabled(true);
2481        }
2482    }
2483
2484    // Preview texture has been copied. Now camera can be released and the
2485    // animation can be started.
2486    @Override
2487    public void onPreviewTextureCopied() {
2488        mHandler.sendEmptyMessage(SWITCH_CAMERA);
2489    }
2490
2491    @Override
2492    public void onUserInteraction() {
2493        if (!mActivity.isFinishing()) keepScreenOnAwhile();
2494    }
2495
2496    private void resetScreenOn() {
2497        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
2498        mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2499    }
2500
2501    private void keepScreenOnAwhile() {
2502        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
2503        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2504        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
2505    }
2506
2507    @Override
2508    public void onRestorePreferencesClicked() {
2509        if (mPaused) return;
2510        Runnable runnable = new Runnable() {
2511            @Override
2512            public void run() {
2513                restorePreferences();
2514            }
2515        };
2516        mRotateDialog.showAlertDialog(
2517                null,
2518                mActivity.getString(R.string.confirm_restore_message),
2519                mActivity.getString(android.R.string.ok), runnable,
2520                mActivity.getString(android.R.string.cancel), null);
2521    }
2522
2523    private void restorePreferences() {
2524        // Reset the zoom. Zoom value is not stored in preference.
2525        if (mParameters.isZoomSupported()) {
2526            mZoomValue = 0;
2527            setCameraParametersWhenIdle(UPDATE_PARAM_ZOOM);
2528            // TODO: reset zoom
2529        }
2530        dismissPopup();
2531        CameraSettings.restorePreferences(mActivity, mPreferences,
2532                mParameters);
2533        mPhotoControl.reloadPreferences();
2534        onSharedPreferenceChanged();
2535    }
2536
2537    @Override
2538    public void onOverriddenPreferencesClicked() {
2539        if (mPaused) return;
2540        if (mNotSelectableToast == null) {
2541            String str = mActivity.getResources().getString(R.string.not_selectable_in_scene_mode);
2542            mNotSelectableToast = Toast.makeText(mActivity, str, Toast.LENGTH_SHORT);
2543        }
2544        mNotSelectableToast.show();
2545    }
2546
2547    private void showTapToFocusToast() {
2548        new RotateTextToast(mActivity, R.string.tap_to_focus, mOrientationCompensation).show();
2549        // Clear the preference.
2550        Editor editor = mPreferences.edit();
2551        editor.putBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, false);
2552        editor.apply();
2553    }
2554
2555    private void initializeCapabilities() {
2556        mInitialParams = mCameraDevice.getParameters();
2557        mFocusAreaSupported = Util.isFocusAreaSupported(mInitialParams);
2558        mMeteringAreaSupported = Util.isMeteringAreaSupported(mInitialParams);
2559        mAeLockSupported = Util.isAutoExposureLockSupported(mInitialParams);
2560        mAwbLockSupported = Util.isAutoWhiteBalanceLockSupported(mInitialParams);
2561        mContinousFocusSupported = mInitialParams.getSupportedFocusModes().contains(
2562                Util.FOCUS_MODE_CONTINUOUS_PICTURE);
2563    }
2564
2565    // PreviewFrameLayout size has changed.
2566    @Override
2567    public void onSizeChanged(int width, int height) {
2568        if (mFocusManager != null) mFocusManager.setPreviewSize(width, height);
2569    }
2570
2571    void setPreviewFrameLayoutAspectRatio() {
2572        // Set the preview frame aspect ratio according to the picture size.
2573        Size size = mParameters.getPictureSize();
2574        mPreviewFrameLayout.setAspectRatio((double) size.width / size.height);
2575    }
2576
2577    @Override
2578    public boolean needsSwitcher() {
2579        return !mIsImageCaptureIntent;
2580    }
2581
2582    public void showPopup(AbstractSettingPopup popup) {
2583        mActivity.hideUI();
2584        mPopup = popup;
2585        mPopup.setVisibility(View.VISIBLE);
2586        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
2587                LayoutParams.WRAP_CONTENT);
2588        lp.gravity = Gravity.CENTER;
2589        ((FrameLayout) mRootView).addView(mPopup, lp);
2590    }
2591
2592    public void dismissPopup() {
2593        mActivity.showUI();
2594        if (mPopup != null) {
2595            ((FrameLayout) mRootView).removeView(mPopup);
2596            mPopup = null;
2597        }
2598    }
2599
2600}
2601