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