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