Move Camera Java/Native source into Gallery2

Change-Id: I968efe4d656e88a7760d3c0044f65b4adac2ddd1
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
new file mode 100644
index 0000000..a283e59
--- /dev/null
+++ b/src/com/android/camera/PhotoModule.java
@@ -0,0 +1,2481 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences.Editor;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera.CameraInfo;
+import android.hardware.Camera.Face;
+import android.hardware.Camera.FaceDetectionListener;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.PictureCallback;
+import android.hardware.Camera.Size;
+import android.location.Location;
+import android.media.CameraProfile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.SystemClock;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.android.camera.CameraManager.CameraProxy;
+import com.android.camera.ui.AbstractSettingPopup;
+import com.android.camera.ui.CountDownView;
+import com.android.camera.ui.FaceView;
+import com.android.camera.ui.PieRenderer;
+import com.android.camera.ui.PopupManager;
+import com.android.camera.ui.PreviewSurfaceView;
+import com.android.camera.ui.RenderOverlay;
+import com.android.camera.ui.Rotatable;
+import com.android.camera.ui.RotateTextToast;
+import com.android.camera.ui.TwoStateImageView;
+import com.android.camera.ui.ZoomRenderer;
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.filtershow.CropExtras;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Formatter;
+import java.util.List;
+
+public class PhotoModule
+    implements CameraModule,
+    FocusOverlayManager.Listener,
+    CameraPreference.OnPreferenceChangedListener,
+    LocationManager.Listener,
+    PreviewFrameLayout.OnSizeChangedListener,
+    ShutterButton.OnShutterButtonListener,
+    SurfaceHolder.Callback,
+    PieRenderer.PieListener,
+    CountDownView.OnCountDownFinishedListener {
+
+    private static final String TAG = "CAM_PhotoModule";
+
+    // We number the request code from 1000 to avoid collision with Gallery.
+    private static final int REQUEST_CROP = 1000;
+
+    private static final int SETUP_PREVIEW = 1;
+    private static final int FIRST_TIME_INIT = 2;
+    private static final int CLEAR_SCREEN_DELAY = 3;
+    private static final int SET_CAMERA_PARAMETERS_WHEN_IDLE = 4;
+    private static final int CHECK_DISPLAY_ROTATION = 5;
+    private static final int SHOW_TAP_TO_FOCUS_TOAST = 6;
+    private static final int SWITCH_CAMERA = 7;
+    private static final int SWITCH_CAMERA_START_ANIMATION = 8;
+    private static final int CAMERA_OPEN_DONE = 9;
+    private static final int START_PREVIEW_DONE = 10;
+    private static final int OPEN_CAMERA_FAIL = 11;
+    private static final int CAMERA_DISABLED = 12;
+    private static final int UPDATE_SECURE_ALBUM_ITEM = 13;
+
+    // The subset of parameters we need to update in setCameraParameters().
+    private static final int UPDATE_PARAM_INITIALIZE = 1;
+    private static final int UPDATE_PARAM_ZOOM = 2;
+    private static final int UPDATE_PARAM_PREFERENCE = 4;
+    private static final int UPDATE_PARAM_ALL = -1;
+
+    // This is the timeout to keep the camera in onPause for the first time
+    // after screen on if the activity is started from secure lock screen.
+    private static final int KEEP_CAMERA_TIMEOUT = 1000; // ms
+
+    // copied from Camera hierarchy
+    private CameraActivity mActivity;
+    private View mRootView;
+    private CameraProxy mCameraDevice;
+    private int mCameraId;
+    private Parameters mParameters;
+    private boolean mPaused;
+    private AbstractSettingPopup mPopup;
+
+    // these are only used by Camera
+
+    // The activity is going to switch to the specified camera id. This is
+    // needed because texture copy is done in GL thread. -1 means camera is not
+    // switching.
+    protected int mPendingSwitchCameraId = -1;
+    private boolean mOpenCameraFail;
+    private boolean mCameraDisabled;
+
+    // When setCameraParametersWhenIdle() is called, we accumulate the subsets
+    // needed to be updated in mUpdateSet.
+    private int mUpdateSet;
+
+    private static final int SCREEN_DELAY = 2 * 60 * 1000;
+
+    private int mZoomValue;  // The current zoom value.
+    private int mZoomMax;
+    private List<Integer> mZoomRatios;
+
+    private Parameters mInitialParams;
+    private boolean mFocusAreaSupported;
+    private boolean mMeteringAreaSupported;
+    private boolean mAeLockSupported;
+    private boolean mAwbLockSupported;
+    private boolean mContinousFocusSupported;
+
+    // The degrees of the device rotated clockwise from its natural orientation.
+    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
+    private ComboPreferences mPreferences;
+
+    private static final String sTempCropFilename = "crop-temp";
+
+    private ContentProviderClient mMediaProviderClient;
+    private ShutterButton mShutterButton;
+    private boolean mFaceDetectionStarted = false;
+
+    private PreviewFrameLayout mPreviewFrameLayout;
+    private Object mSurfaceTexture;
+    private CountDownView mCountDownView;
+
+    // for API level 10
+    private PreviewSurfaceView mPreviewSurfaceView;
+    private volatile SurfaceHolder mCameraSurfaceHolder;
+
+    private FaceView mFaceView;
+    private RenderOverlay mRenderOverlay;
+    private Rotatable mReviewCancelButton;
+    private Rotatable mReviewDoneButton;
+    private View mReviewRetakeButton;
+
+    // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
+    private String mCropValue;
+    private Uri mSaveUri;
+
+    private View mMenu;
+    private View mBlocker;
+
+    // Small indicators which show the camera settings in the viewfinder.
+    private ImageView mExposureIndicator;
+    private ImageView mFlashIndicator;
+    private ImageView mSceneIndicator;
+    private ImageView mHdrIndicator;
+    // A view group that contains all the small indicators.
+    private View mOnScreenIndicators;
+
+    // We use a thread in MediaSaver to do the work of saving images. This
+    // reduces the shot-to-shot time.
+    private MediaSaver mMediaSaver;
+    // Similarly, we use a thread to generate the name of the picture and insert
+    // it into MediaStore while picture taking is still in progress.
+    private NamedImages mNamedImages;
+
+    private Runnable mDoSnapRunnable = new Runnable() {
+        @Override
+        public void run() {
+            onShutterButtonClick();
+        }
+    };
+
+    private final StringBuilder mBuilder = new StringBuilder();
+    private final Formatter mFormatter = new Formatter(mBuilder);
+    private final Object[] mFormatterArgs = new Object[1];
+
+    /**
+     * An unpublished intent flag requesting to return as soon as capturing
+     * is completed.
+     *
+     * TODO: consider publishing by moving into MediaStore.
+     */
+    private static final String EXTRA_QUICK_CAPTURE =
+            "android.intent.extra.quickCapture";
+
+    // The display rotation in degrees. This is only valid when mCameraState is
+    // not PREVIEW_STOPPED.
+    private int mDisplayRotation;
+    // The value for android.hardware.Camera.setDisplayOrientation.
+    private int mCameraDisplayOrientation;
+    // The value for UI components like indicators.
+    private int mDisplayOrientation;
+    // The value for android.hardware.Camera.Parameters.setRotation.
+    private int mJpegRotation;
+    private boolean mFirstTimeInitialized;
+    private boolean mIsImageCaptureIntent;
+
+    private static final int PREVIEW_STOPPED = 0;
+    private static final int IDLE = 1;  // preview is active
+    // Focus is in progress. The exact focus state is in Focus.java.
+    private static final int FOCUSING = 2;
+    private static final int SNAPSHOT_IN_PROGRESS = 3;
+    // Switching between cameras.
+    private static final int SWITCHING_CAMERA = 4;
+    private int mCameraState = PREVIEW_STOPPED;
+    private boolean mSnapshotOnIdle = false;
+
+    private ContentResolver mContentResolver;
+
+    private LocationManager mLocationManager;
+
+    private final ShutterCallback mShutterCallback = new ShutterCallback();
+    private final PostViewPictureCallback mPostViewPictureCallback =
+            new PostViewPictureCallback();
+    private final RawPictureCallback mRawPictureCallback =
+            new RawPictureCallback();
+    private final AutoFocusCallback mAutoFocusCallback =
+            new AutoFocusCallback();
+    private final Object mAutoFocusMoveCallback =
+            ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
+            ? new AutoFocusMoveCallback()
+            : null;
+
+    private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
+
+    private long mFocusStartTime;
+    private long mShutterCallbackTime;
+    private long mPostViewPictureCallbackTime;
+    private long mRawPictureCallbackTime;
+    private long mJpegPictureCallbackTime;
+    private long mOnResumeTime;
+    private byte[] mJpegImageData;
+
+    // These latency time are for the CameraLatency test.
+    public long mAutoFocusTime;
+    public long mShutterLag;
+    public long mShutterToPictureDisplayedTime;
+    public long mPictureDisplayedToJpegCallbackTime;
+    public long mJpegCallbackFinishTime;
+    public long mCaptureStartTime;
+
+    // This handles everything about focus.
+    private FocusOverlayManager mFocusManager;
+
+    private PieRenderer mPieRenderer;
+    private PhotoController mPhotoControl;
+
+    private ZoomRenderer mZoomRenderer;
+
+    private String mSceneMode;
+    private Toast mNotSelectableToast;
+
+    private final Handler mHandler = new MainHandler();
+    private PreferenceGroup mPreferenceGroup;
+
+    private boolean mQuickCapture;
+
+    CameraStartUpThread mCameraStartUpThread;
+    ConditionVariable mStartPreviewPrerequisiteReady = new ConditionVariable();
+
+    private PreviewGestures mGestures;
+
+    private MediaSaver.OnMediaSavedListener mOnMediaSavedListener = new MediaSaver.OnMediaSavedListener() {
+        @Override
+
+        public void onMediaSaved(Uri uri) {
+            if (uri != null) {
+                mHandler.obtainMessage(UPDATE_SECURE_ALBUM_ITEM, uri).sendToTarget();
+                Util.broadcastNewPicture(mActivity, uri);
+            }
+        }
+    };
+
+    // The purpose is not to block the main thread in onCreate and onResume.
+    private class CameraStartUpThread extends Thread {
+        private volatile boolean mCancelled;
+
+        public void cancel() {
+            mCancelled = true;
+            interrupt();
+        }
+
+        public boolean isCanceled() {
+            return mCancelled;
+        }
+
+        @Override
+        public void run() {
+            try {
+                // We need to check whether the activity is paused before long
+                // operations to ensure that onPause() can be done ASAP.
+                if (mCancelled) return;
+                mCameraDevice = Util.openCamera(mActivity, mCameraId);
+                mParameters = mCameraDevice.getParameters();
+                // Wait until all the initialization needed by startPreview are
+                // done.
+                mStartPreviewPrerequisiteReady.block();
+
+                initializeCapabilities();
+                if (mFocusManager == null) initializeFocusManager();
+                if (mCancelled) return;
+                setCameraParameters(UPDATE_PARAM_ALL);
+                mHandler.sendEmptyMessage(CAMERA_OPEN_DONE);
+                if (mCancelled) return;
+                startPreview();
+                mHandler.sendEmptyMessage(START_PREVIEW_DONE);
+                mOnResumeTime = SystemClock.uptimeMillis();
+                mHandler.sendEmptyMessage(CHECK_DISPLAY_ROTATION);
+            } catch (CameraHardwareException e) {
+                mHandler.sendEmptyMessage(OPEN_CAMERA_FAIL);
+            } catch (CameraDisabledException e) {
+                mHandler.sendEmptyMessage(CAMERA_DISABLED);
+            }
+        }
+    }
+
+    /**
+     * This Handler is used to post message back onto the main thread of the
+     * application
+     */
+    private class MainHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case SETUP_PREVIEW: {
+                    setupPreview();
+                    break;
+                }
+
+                case CLEAR_SCREEN_DELAY: {
+                    mActivity.getWindow().clearFlags(
+                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+                    break;
+                }
+
+                case FIRST_TIME_INIT: {
+                    initializeFirstTime();
+                    break;
+                }
+
+                case SET_CAMERA_PARAMETERS_WHEN_IDLE: {
+                    setCameraParametersWhenIdle(0);
+                    break;
+                }
+
+                case CHECK_DISPLAY_ROTATION: {
+                    // Set the display orientation if display rotation has changed.
+                    // Sometimes this happens when the device is held upside
+                    // down and camera app is opened. Rotation animation will
+                    // take some time and the rotation value we have got may be
+                    // wrong. Framework does not have a callback for this now.
+                    if (Util.getDisplayRotation(mActivity) != mDisplayRotation) {
+                        setDisplayOrientation();
+                    }
+                    if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
+                        mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
+                    }
+                    break;
+                }
+
+                case SHOW_TAP_TO_FOCUS_TOAST: {
+                    showTapToFocusToast();
+                    break;
+                }
+
+                case SWITCH_CAMERA: {
+                    switchCamera();
+                    break;
+                }
+
+                case SWITCH_CAMERA_START_ANIMATION: {
+                    ((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
+                    break;
+                }
+
+                case CAMERA_OPEN_DONE: {
+                    initializeAfterCameraOpen();
+                    break;
+                }
+
+                case START_PREVIEW_DONE: {
+                    mCameraStartUpThread = null;
+                    setCameraState(IDLE);
+                    if (!ApiHelper.HAS_SURFACE_TEXTURE) {
+                        // This may happen if surfaceCreated has arrived.
+                        mCameraDevice.setPreviewDisplayAsync(mCameraSurfaceHolder);
+                    }
+                    startFaceDetection();
+                    locationFirstRun();
+                    break;
+                }
+
+                case OPEN_CAMERA_FAIL: {
+                    mCameraStartUpThread = null;
+                    mOpenCameraFail = true;
+                    Util.showErrorAndFinish(mActivity,
+                            R.string.cannot_connect_camera);
+                    break;
+                }
+
+                case CAMERA_DISABLED: {
+                    mCameraStartUpThread = null;
+                    mCameraDisabled = true;
+                    Util.showErrorAndFinish(mActivity,
+                            R.string.camera_disabled);
+                    break;
+                }
+
+                case UPDATE_SECURE_ALBUM_ITEM: {
+                    mActivity.addSecureAlbumItemIfNeeded(false, (Uri) msg.obj);
+                    break;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void init(CameraActivity activity, View parent, boolean reuseNail) {
+        mActivity = activity;
+        mRootView = parent;
+        mPreferences = new ComboPreferences(mActivity);
+        CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
+        mCameraId = getPreferredCameraId(mPreferences);
+
+        mContentResolver = mActivity.getContentResolver();
+
+        // To reduce startup time, open the camera and start the preview in
+        // another thread.
+        mCameraStartUpThread = new CameraStartUpThread();
+        mCameraStartUpThread.start();
+
+        mActivity.getLayoutInflater().inflate(R.layout.photo_module, (ViewGroup) mRootView);
+
+        // Surface texture is from camera screen nail and startPreview needs it.
+        // This must be done before startPreview.
+        mIsImageCaptureIntent = isImageCaptureIntent();
+        if (reuseNail) {
+            mActivity.reuseCameraScreenNail(!mIsImageCaptureIntent);
+        } else {
+            mActivity.createCameraScreenNail(!mIsImageCaptureIntent);
+        }
+
+        mPreferences.setLocalId(mActivity, mCameraId);
+        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
+        // we need to reset exposure for the preview
+        resetExposureCompensation();
+        // Starting the preview needs preferences, camera screen nail, and
+        // focus area indicator.
+        mStartPreviewPrerequisiteReady.open();
+
+        initializeControlByIntent();
+        mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
+        initializeMiscControls();
+        mLocationManager = new LocationManager(mActivity, this);
+        initOnScreenIndicator();
+        mCountDownView = (CountDownView) (mRootView.findViewById(R.id.count_down_to_capture));
+        mCountDownView.setCountDownFinishedListener(this);
+    }
+
+    // Prompt the user to pick to record location for the very first run of
+    // camera only
+    private void locationFirstRun() {
+        if (RecordLocationPreference.isSet(mPreferences)) {
+            return;
+        }
+        if (mActivity.isSecureCamera()) return;
+        // Check if the back camera exists
+        int backCameraId = CameraHolder.instance().getBackCameraId();
+        if (backCameraId == -1) {
+            // If there is no back camera, do not show the prompt.
+            return;
+        }
+
+        new AlertDialog.Builder(mActivity)
+            .setTitle(R.string.remember_location_title)
+            .setMessage(R.string.remember_location_prompt)
+            .setPositiveButton(R.string.remember_location_yes, new DialogInterface.OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialog, int arg1) {
+                    setLocationPreference(RecordLocationPreference.VALUE_ON);
+                }
+            })
+            .setNegativeButton(R.string.remember_location_no, new DialogInterface.OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialog, int arg1) {
+                    dialog.cancel();
+                }
+            })
+            .setOnCancelListener(new DialogInterface.OnCancelListener() {
+                @Override
+                public void onCancel(DialogInterface dialog) {
+                    setLocationPreference(RecordLocationPreference.VALUE_OFF);
+                }
+            })
+            .show();
+    }
+
+    private void setLocationPreference(String value) {
+        mPreferences.edit()
+            .putString(CameraSettings.KEY_RECORD_LOCATION, value)
+            .apply();
+        // TODO: Fix this to use the actual onSharedPreferencesChanged listener
+        // instead of invoking manually
+        onSharedPreferenceChanged();
+    }
+
+    private void initializeRenderOverlay() {
+        if (mPieRenderer != null) {
+            mRenderOverlay.addRenderer(mPieRenderer);
+            mFocusManager.setFocusRenderer(mPieRenderer);
+        }
+        if (mZoomRenderer != null) {
+            mRenderOverlay.addRenderer(mZoomRenderer);
+        }
+        if (mGestures != null) {
+            mGestures.clearTouchReceivers();
+            mGestures.setRenderOverlay(mRenderOverlay);
+            mGestures.addTouchReceiver(mMenu);
+            mGestures.addTouchReceiver(mBlocker);
+
+            if (isImageCaptureIntent()) {
+                if (mReviewCancelButton != null) {
+                    mGestures.addTouchReceiver((View) mReviewCancelButton);
+                }
+                if (mReviewDoneButton != null) {
+                    mGestures.addTouchReceiver((View) mReviewDoneButton);
+                }
+            }
+        }
+        mRenderOverlay.requestLayout();
+    }
+
+    private void initializeAfterCameraOpen() {
+        if (mPieRenderer == null) {
+            mPieRenderer = new PieRenderer(mActivity);
+            mPhotoControl = new PhotoController(mActivity, this, mPieRenderer);
+            mPhotoControl.setListener(this);
+            mPieRenderer.setPieListener(this);
+        }
+        if (mZoomRenderer == null) {
+            mZoomRenderer = new ZoomRenderer(mActivity);
+        }
+        if (mGestures == null) {
+            // this will handle gesture disambiguation and dispatching
+            mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer);
+        }
+        initializeRenderOverlay();
+        initializePhotoControl();
+
+        // These depend on camera parameters.
+        setPreviewFrameLayoutAspectRatio();
+        mFocusManager.setPreviewSize(mPreviewFrameLayout.getWidth(),
+                mPreviewFrameLayout.getHeight());
+        loadCameraPreferences();
+        initializeZoom();
+        updateOnScreenIndicators();
+        showTapToFocusToastIfNeeded();
+        onFullScreenChanged(mActivity.isInCameraApp());
+    }
+
+    private void initializePhotoControl() {
+        loadCameraPreferences();
+        if (mPhotoControl != null) {
+            mPhotoControl.initialize(mPreferenceGroup);
+        }
+        updateSceneModeUI();
+    }
+
+
+    private void resetExposureCompensation() {
+        String value = mPreferences.getString(CameraSettings.KEY_EXPOSURE,
+                CameraSettings.EXPOSURE_DEFAULT_VALUE);
+        if (!CameraSettings.EXPOSURE_DEFAULT_VALUE.equals(value)) {
+            Editor editor = mPreferences.edit();
+            editor.putString(CameraSettings.KEY_EXPOSURE, "0");
+            editor.apply();
+        }
+    }
+
+    private void keepMediaProviderInstance() {
+        // We want to keep a reference to MediaProvider in camera's lifecycle.
+        // TODO: Utilize mMediaProviderClient instance to replace
+        // ContentResolver calls.
+        if (mMediaProviderClient == null) {
+            mMediaProviderClient = mContentResolver
+                    .acquireContentProviderClient(MediaStore.AUTHORITY);
+        }
+    }
+
+    // Snapshots can only be taken after this is called. It should be called
+    // once only. We could have done these things in onCreate() but we want to
+    // make preview screen appear as soon as possible.
+    private void initializeFirstTime() {
+        if (mFirstTimeInitialized) return;
+
+        // Initialize location service.
+        boolean recordLocation = RecordLocationPreference.get(
+                mPreferences, mContentResolver);
+        mLocationManager.recordLocation(recordLocation);
+
+        keepMediaProviderInstance();
+
+        // Initialize shutter button.
+        mShutterButton = mActivity.getShutterButton();
+        mShutterButton.setImageResource(R.drawable.btn_new_shutter);
+        mShutterButton.setOnShutterButtonListener(this);
+        mShutterButton.setVisibility(View.VISIBLE);
+
+        mMediaSaver = new MediaSaver(mContentResolver);
+        mNamedImages = new NamedImages();
+
+        mFirstTimeInitialized = true;
+        addIdleHandler();
+
+        mActivity.updateStorageSpaceAndHint();
+    }
+
+    private void showTapToFocusToastIfNeeded() {
+        // Show the tap to focus toast if this is the first start.
+        if (mFocusAreaSupported &&
+                mPreferences.getBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, true)) {
+            // Delay the toast for one second to wait for orientation.
+            mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_FOCUS_TOAST, 1000);
+        }
+    }
+
+    private void addIdleHandler() {
+        MessageQueue queue = Looper.myQueue();
+        queue.addIdleHandler(new MessageQueue.IdleHandler() {
+            @Override
+            public boolean queueIdle() {
+                Storage.ensureOSXCompatible();
+                return false;
+            }
+        });
+    }
+
+    // If the activity is paused and resumed, this method will be called in
+    // onResume.
+    private void initializeSecondTime() {
+
+        // Start location update if needed.
+        boolean recordLocation = RecordLocationPreference.get(
+                mPreferences, mContentResolver);
+        mLocationManager.recordLocation(recordLocation);
+
+        mMediaSaver = new MediaSaver(mContentResolver);
+        mNamedImages = new NamedImages();
+        initializeZoom();
+        keepMediaProviderInstance();
+        hidePostCaptureAlert();
+
+        if (mPhotoControl != null) {
+            mPhotoControl.reloadPreferences();
+        }
+    }
+
+    private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener {
+        @Override
+        public void onZoomValueChanged(int index) {
+            // Not useful to change zoom value when the activity is paused.
+            if (mPaused) return;
+            mZoomValue = index;
+            if (mParameters == null || mCameraDevice == null) return;
+            // Set zoom parameters asynchronously
+            mParameters.setZoom(mZoomValue);
+            mCameraDevice.setParametersAsync(mParameters);
+            if (mZoomRenderer != null) {
+                Parameters p = mCameraDevice.getParameters();
+                mZoomRenderer.setZoomValue(mZoomRatios.get(p.getZoom()));
+            }
+        }
+
+        @Override
+        public void onZoomStart() {
+            if (mPieRenderer != null) {
+                mPieRenderer.setBlockFocus(true);
+            }
+        }
+
+        @Override
+        public void onZoomEnd() {
+            if (mPieRenderer != null) {
+                mPieRenderer.setBlockFocus(false);
+            }
+        }
+    }
+
+    private void initializeZoom() {
+        if ((mParameters == null) || !mParameters.isZoomSupported()
+                || (mZoomRenderer == null)) return;
+        mZoomMax = mParameters.getMaxZoom();
+        mZoomRatios = mParameters.getZoomRatios();
+        // Currently we use immediate zoom for fast zooming to get better UX and
+        // there is no plan to take advantage of the smooth zoom.
+        if (mZoomRenderer != null) {
+            mZoomRenderer.setZoomMax(mZoomMax);
+            mZoomRenderer.setZoom(mParameters.getZoom());
+            mZoomRenderer.setZoomValue(mZoomRatios.get(mParameters.getZoom()));
+            mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener());
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    @Override
+    public void startFaceDetection() {
+        if (!ApiHelper.HAS_FACE_DETECTION) return;
+        if (mFaceDetectionStarted) return;
+        if (mParameters.getMaxNumDetectedFaces() > 0) {
+            mFaceDetectionStarted = true;
+            mFaceView.clear();
+            mFaceView.setVisibility(View.VISIBLE);
+            mFaceView.setDisplayOrientation(mDisplayOrientation);
+            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+            mFaceView.setMirror(info.facing == CameraInfo.CAMERA_FACING_FRONT);
+            mFaceView.resume();
+            mFocusManager.setFaceView(mFaceView);
+            mCameraDevice.setFaceDetectionListener(new FaceDetectionListener() {
+                @Override
+                public void onFaceDetection(Face[] faces, android.hardware.Camera camera) {
+                    mFaceView.setFaces(faces);
+                }
+            });
+            mCameraDevice.startFaceDetection();
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    @Override
+    public void stopFaceDetection() {
+        if (!ApiHelper.HAS_FACE_DETECTION) return;
+        if (!mFaceDetectionStarted) return;
+        if (mParameters.getMaxNumDetectedFaces() > 0) {
+            mFaceDetectionStarted = false;
+            mCameraDevice.setFaceDetectionListener(null);
+            mCameraDevice.stopFaceDetection();
+            if (mFaceView != null) mFaceView.clear();
+        }
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent m) {
+        if (mCameraState == SWITCHING_CAMERA) return true;
+        if (mPopup != null) {
+            return mActivity.superDispatchTouchEvent(m);
+        } else if (mGestures != null && mRenderOverlay != null) {
+            return mGestures.dispatchTouch(m);
+        }
+        return false;
+    }
+
+    private void initOnScreenIndicator() {
+        mOnScreenIndicators = mRootView.findViewById(R.id.on_screen_indicators);
+        mExposureIndicator = (ImageView) mOnScreenIndicators.findViewById(R.id.menu_exposure_indicator);
+        mFlashIndicator = (ImageView) mOnScreenIndicators.findViewById(R.id.menu_flash_indicator);
+        mSceneIndicator = (ImageView) mOnScreenIndicators.findViewById(R.id.menu_scenemode_indicator);
+        mHdrIndicator = (ImageView) mOnScreenIndicators.findViewById(R.id.menu_hdr_indicator);
+    }
+
+    @Override
+    public void showGpsOnScreenIndicator(boolean hasSignal) { }
+
+    @Override
+    public void hideGpsOnScreenIndicator() { }
+
+    private void updateExposureOnScreenIndicator(int value) {
+        if (mExposureIndicator == null) {
+            return;
+        }
+        int id = 0;
+        float step = mParameters.getExposureCompensationStep();
+        value = (int) Math.round(value * step);
+        switch(value) {
+        case -3:
+            id = R.drawable.ic_indicator_ev_n3;
+            break;
+        case -2:
+            id = R.drawable.ic_indicator_ev_n2;
+            break;
+        case -1:
+            id = R.drawable.ic_indicator_ev_n1;
+            break;
+        case 0:
+            id = R.drawable.ic_indicator_ev_0;
+            break;
+        case 1:
+            id = R.drawable.ic_indicator_ev_p1;
+            break;
+        case 2:
+            id = R.drawable.ic_indicator_ev_p2;
+            break;
+        case 3:
+            id = R.drawable.ic_indicator_ev_p3;
+            break;
+        }
+        mExposureIndicator.setImageResource(id);
+
+    }
+
+    private void updateFlashOnScreenIndicator(String value) {
+        if (mFlashIndicator == null) {
+            return;
+        }
+        if (value == null || Parameters.FLASH_MODE_OFF.equals(value)) {
+            mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off);
+        } else {
+            if (Parameters.FLASH_MODE_AUTO.equals(value)) {
+                mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_auto);
+            } else if (Parameters.FLASH_MODE_ON.equals(value)) {
+                mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_on);
+            } else {
+                mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off);
+            }
+        }
+    }
+
+    private void updateSceneOnScreenIndicator(String value) {
+        if (mSceneIndicator == null) {
+            return;
+        }
+        if ((value == null) || Parameters.SCENE_MODE_AUTO.equals(value)
+                || Parameters.SCENE_MODE_HDR.equals(value)) {
+            mSceneIndicator.setImageResource(R.drawable.ic_indicator_sce_off);
+        } else {
+            mSceneIndicator.setImageResource(R.drawable.ic_indicator_sce_on);
+        }
+    }
+
+    private void updateHdrOnScreenIndicator(String value) {
+        if (mHdrIndicator == null) {
+            return;
+        }
+        if ((value != null) && Parameters.SCENE_MODE_HDR.equals(value)) {
+            mHdrIndicator.setImageResource(R.drawable.ic_indicator_hdr_on);
+        } else {
+            mHdrIndicator.setImageResource(R.drawable.ic_indicator_hdr_off);
+        }
+    }
+
+    private void updateOnScreenIndicators() {
+        if (mParameters == null) return;
+        updateSceneOnScreenIndicator(mParameters.getSceneMode());
+        updateExposureOnScreenIndicator(CameraSettings.readExposure(mPreferences));
+        updateFlashOnScreenIndicator(mParameters.getFlashMode());
+        updateHdrOnScreenIndicator(mParameters.getSceneMode());
+    }
+
+    private final class ShutterCallback
+            implements android.hardware.Camera.ShutterCallback {
+        @Override
+        public void onShutter() {
+            mShutterCallbackTime = System.currentTimeMillis();
+            mShutterLag = mShutterCallbackTime - mCaptureStartTime;
+            Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
+        }
+    }
+
+    private final class PostViewPictureCallback implements PictureCallback {
+        @Override
+        public void onPictureTaken(
+                byte [] data, android.hardware.Camera camera) {
+            mPostViewPictureCallbackTime = System.currentTimeMillis();
+            Log.v(TAG, "mShutterToPostViewCallbackTime = "
+                    + (mPostViewPictureCallbackTime - mShutterCallbackTime)
+                    + "ms");
+        }
+    }
+
+    private final class RawPictureCallback implements PictureCallback {
+        @Override
+        public void onPictureTaken(
+                byte [] rawData, android.hardware.Camera camera) {
+            mRawPictureCallbackTime = System.currentTimeMillis();
+            Log.v(TAG, "mShutterToRawCallbackTime = "
+                    + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
+        }
+    }
+
+    private final class JpegPictureCallback implements PictureCallback {
+        Location mLocation;
+
+        public JpegPictureCallback(Location loc) {
+            mLocation = loc;
+        }
+
+        @Override
+        public void onPictureTaken(
+                final byte [] jpegData, final android.hardware.Camera camera) {
+            if (mPaused) {
+                return;
+            }
+            if (mSceneMode == Util.SCENE_MODE_HDR) {
+                mActivity.showSwitcher();
+                mActivity.setSwipingEnabled(true);
+            }
+
+            mJpegPictureCallbackTime = System.currentTimeMillis();
+            // If postview callback has arrived, the captured image is displayed
+            // in postview callback. If not, the captured image is displayed in
+            // raw picture callback.
+            if (mPostViewPictureCallbackTime != 0) {
+                mShutterToPictureDisplayedTime =
+                        mPostViewPictureCallbackTime - mShutterCallbackTime;
+                mPictureDisplayedToJpegCallbackTime =
+                        mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
+            } else {
+                mShutterToPictureDisplayedTime =
+                        mRawPictureCallbackTime - mShutterCallbackTime;
+                mPictureDisplayedToJpegCallbackTime =
+                        mJpegPictureCallbackTime - mRawPictureCallbackTime;
+            }
+            Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
+                    + mPictureDisplayedToJpegCallbackTime + "ms");
+
+            // Only animate when in full screen capture mode
+            // i.e. If monkey/a user swipes to the gallery during picture taking,
+            // don't show animation
+            if (ApiHelper.HAS_SURFACE_TEXTURE && !mIsImageCaptureIntent
+                    && mActivity.mShowCameraAppView) {
+                // Finish capture animation
+                ((CameraScreenNail) mActivity.mCameraScreenNail).animateSlide();
+            }
+            mFocusManager.updateFocusUI(); // Ensure focus indicator is hidden.
+            if (!mIsImageCaptureIntent) {
+                if (ApiHelper.CAN_START_PREVIEW_IN_JPEG_CALLBACK) {
+                    setupPreview();
+                } else {
+                    // Camera HAL of some devices have a bug. Starting preview
+                    // immediately after taking a picture will fail. Wait some
+                    // time before starting the preview.
+                    mHandler.sendEmptyMessageDelayed(SETUP_PREVIEW, 300);
+                }
+            }
+
+            if (!mIsImageCaptureIntent) {
+                // Calculate the width and the height of the jpeg.
+                Size s = mParameters.getPictureSize();
+                int orientation = Exif.getOrientation(jpegData);
+                int width, height;
+                if ((mJpegRotation + orientation) % 180 == 0) {
+                    width = s.width;
+                    height = s.height;
+                } else {
+                    width = s.height;
+                    height = s.width;
+                }
+                String title = mNamedImages.getTitle();
+                long date = mNamedImages.getDate();
+                if (title == null) {
+                    Log.e(TAG, "Unbalanced name/data pair");
+                } else {
+                    if (date == -1) date = mCaptureStartTime;
+                    mMediaSaver.addImage(jpegData, title, date, mLocation, width, height,
+                            orientation, mOnMediaSavedListener);
+                }
+            } else {
+                mJpegImageData = jpegData;
+                if (!mQuickCapture) {
+                    showPostCaptureAlert();
+                } else {
+                    doAttach();
+                }
+            }
+
+            // Check this in advance of each shot so we don't add to shutter
+            // latency. It's true that someone else could write to the SD card in
+            // the mean time and fill it, but that could have happened between the
+            // shutter press and saving the JPEG too.
+            mActivity.updateStorageSpaceAndHint();
+
+            long now = System.currentTimeMillis();
+            mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
+            Log.v(TAG, "mJpegCallbackFinishTime = "
+                    + mJpegCallbackFinishTime + "ms");
+            mJpegPictureCallbackTime = 0;
+        }
+    }
+
+    private final class AutoFocusCallback
+            implements android.hardware.Camera.AutoFocusCallback {
+        @Override
+        public void onAutoFocus(
+                boolean focused, android.hardware.Camera camera) {
+            if (mPaused) return;
+
+            mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;
+            Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms");
+            setCameraState(IDLE);
+            mFocusManager.onAutoFocus(focused, mShutterButton.isPressed());
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+    private final class AutoFocusMoveCallback
+            implements android.hardware.Camera.AutoFocusMoveCallback {
+        @Override
+        public void onAutoFocusMoving(
+            boolean moving, android.hardware.Camera camera) {
+                mFocusManager.onAutoFocusMoving(moving);
+        }
+    }
+
+    private static class NamedImages {
+        private ArrayList<NamedEntity> mQueue;
+        private boolean mStop;
+        private NamedEntity mNamedEntity;
+
+        public NamedImages() {
+            mQueue = new ArrayList<NamedEntity>();
+        }
+
+        public void nameNewImage(ContentResolver resolver, long date) {
+            NamedEntity r = new NamedEntity();
+            r.title = Util.createJpegName(date);
+            r.date = date;
+            mQueue.add(r);
+        }
+
+        public String getTitle() {
+            if (mQueue.isEmpty()) {
+                mNamedEntity = null;
+                return null;
+            }
+            mNamedEntity = mQueue.get(0);
+            mQueue.remove(0);
+
+            return mNamedEntity.title;
+        }
+
+        // Must be called after getTitle().
+        public long getDate() {
+            if (mNamedEntity == null) return -1;
+            return mNamedEntity.date;
+        }
+
+        private static class NamedEntity {
+            String title;
+            long date;
+        }
+    }
+
+    private void setCameraState(int state) {
+        mCameraState = state;
+        switch (state) {
+            case PREVIEW_STOPPED:
+            case SNAPSHOT_IN_PROGRESS:
+            case FOCUSING:
+            case SWITCHING_CAMERA:
+                if (mGestures != null) mGestures.setEnabled(false);
+                break;
+            case IDLE:
+                if (mGestures != null && mActivity.mShowCameraAppView) {
+                    // Enable gestures only when the camera app view is visible
+                    mGestures.setEnabled(true);
+                }
+                break;
+        }
+    }
+
+    private void animateFlash() {
+        // Only animate when in full screen capture mode
+        // i.e. If monkey/a user swipes to the gallery during picture taking,
+        // don't show animation
+        if (ApiHelper.HAS_SURFACE_TEXTURE && !mIsImageCaptureIntent
+                && mActivity.mShowCameraAppView) {
+            // Start capture animation.
+            ((CameraScreenNail) mActivity.mCameraScreenNail).animateFlash(mDisplayRotation);
+        }
+    }
+
+    @Override
+    public boolean capture() {
+        // If we are already in the middle of taking a snapshot or the image save request
+        // is full then ignore.
+        if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS
+                || mCameraState == SWITCHING_CAMERA || mMediaSaver.queueFull()) {
+            return false;
+        }
+        mCaptureStartTime = System.currentTimeMillis();
+        mPostViewPictureCallbackTime = 0;
+        mJpegImageData = null;
+
+        final boolean animateBefore = (mSceneMode == Util.SCENE_MODE_HDR);
+
+        if (animateBefore) {
+            animateFlash();
+        }
+
+        // Set rotation and gps data.
+        mJpegRotation = Util.getJpegRotation(mCameraId, mOrientation);
+        mParameters.setRotation(mJpegRotation);
+        Location loc = mLocationManager.getCurrentLocation();
+        Util.setGpsParameters(mParameters, loc);
+        mCameraDevice.setParameters(mParameters);
+
+        mCameraDevice.takePicture2(mShutterCallback, mRawPictureCallback,
+                mPostViewPictureCallback, new JpegPictureCallback(loc),
+                mCameraState, mFocusManager.getFocusState());
+
+        if (!animateBefore) {
+            animateFlash();
+        }
+
+        mNamedImages.nameNewImage(mContentResolver, mCaptureStartTime);
+
+        mFaceDetectionStarted = false;
+        setCameraState(SNAPSHOT_IN_PROGRESS);
+        return true;
+    }
+
+    @Override
+    public void setFocusParameters() {
+        setCameraParameters(UPDATE_PARAM_PREFERENCE);
+    }
+
+    private int getPreferredCameraId(ComboPreferences preferences) {
+        int intentCameraId = Util.getCameraFacingIntentExtras(mActivity);
+        if (intentCameraId != -1) {
+            // Testing purpose. Launch a specific camera through the intent
+            // extras.
+            return intentCameraId;
+        } else {
+            return CameraSettings.readPreferredCameraId(preferences);
+        }
+    }
+
+    private void setShowMenu(boolean show) {
+        if (mOnScreenIndicators != null) {
+            mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE);
+        }
+        if (mMenu != null) {
+            mMenu.setVisibility(show ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    @Override
+    public void onFullScreenChanged(boolean full) {
+        if (mFaceView != null) {
+            mFaceView.setBlockDraw(!full);
+        }
+        if (mPopup != null) {
+            dismissPopup(false, full);
+        }
+        if (mGestures != null) {
+            mGestures.setEnabled(full);
+        }
+        if (mRenderOverlay != null) {
+            // this can not happen in capture mode
+            mRenderOverlay.setVisibility(full ? View.VISIBLE : View.GONE);
+        }
+        if (mPieRenderer != null) {
+            mPieRenderer.setBlockFocus(!full);
+        }
+        setShowMenu(full);
+        if (mBlocker != null) {
+            mBlocker.setVisibility(full ? View.VISIBLE : View.GONE);
+        }
+        if (!full && mCountDownView != null) mCountDownView.cancelCountDown();
+        if (ApiHelper.HAS_SURFACE_TEXTURE) {
+            if (mActivity.mCameraScreenNail != null) {
+                ((CameraScreenNail) mActivity.mCameraScreenNail).setFullScreen(full);
+            }
+            return;
+        }
+        if (full) {
+            mPreviewSurfaceView.expand();
+        } else {
+            mPreviewSurfaceView.shrink();
+        }
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        Log.v(TAG, "surfaceChanged:" + holder + " width=" + width + ". height="
+                + height);
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        Log.v(TAG, "surfaceCreated: " + holder);
+        mCameraSurfaceHolder = holder;
+        // Do not access the camera if camera start up thread is not finished.
+        if (mCameraDevice == null || mCameraStartUpThread != null) return;
+
+        mCameraDevice.setPreviewDisplayAsync(holder);
+        // This happens when onConfigurationChanged arrives, surface has been
+        // destroyed, and there is no onFullScreenChanged.
+        if (mCameraState == PREVIEW_STOPPED) {
+            setupPreview();
+        }
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        Log.v(TAG, "surfaceDestroyed: " + holder);
+        mCameraSurfaceHolder = null;
+        stopPreview();
+    }
+
+    private void updateSceneModeUI() {
+        // If scene mode is set, we cannot set flash mode, white balance, and
+        // focus mode, instead, we read it from driver
+        if (!Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
+            overrideCameraSettings(mParameters.getFlashMode(),
+                    mParameters.getWhiteBalance(), mParameters.getFocusMode());
+        } else {
+            overrideCameraSettings(null, null, null);
+        }
+    }
+
+    private void overrideCameraSettings(final String flashMode,
+            final String whiteBalance, final String focusMode) {
+        if (mPhotoControl != null) {
+//            mPieControl.enableFilter(true);
+            mPhotoControl.overrideSettings(
+                    CameraSettings.KEY_FLASH_MODE, flashMode,
+                    CameraSettings.KEY_WHITE_BALANCE, whiteBalance,
+                    CameraSettings.KEY_FOCUS_MODE, focusMode);
+//            mPieControl.enableFilter(false);
+        }
+    }
+
+    private void loadCameraPreferences() {
+        CameraSettings settings = new CameraSettings(mActivity, mInitialParams,
+                mCameraId, CameraHolder.instance().getCameraInfo());
+        mPreferenceGroup = settings.getPreferenceGroup(R.xml.camera_preferences);
+    }
+
+    @Override
+    public boolean collapseCameraControls() {
+        // Remove all the popups/dialog boxes
+        boolean ret = false;
+        if (mPopup != null) {
+            dismissPopup(false);
+            ret = true;
+        }
+        return ret;
+    }
+
+    public boolean removeTopLevelPopup() {
+        // Remove the top level popup or dialog box and return true if there's any
+        if (mPopup != null) {
+            dismissPopup(true);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void onOrientationChanged(int orientation) {
+        // We keep the last known orientation. So if the user first orient
+        // the camera then point the camera to floor or sky, we still have
+        // the correct orientation.
+        if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return;
+        mOrientation = Util.roundOrientation(orientation, mOrientation);
+
+        // Show the toast after getting the first orientation changed.
+        if (mHandler.hasMessages(SHOW_TAP_TO_FOCUS_TOAST)) {
+            mHandler.removeMessages(SHOW_TAP_TO_FOCUS_TOAST);
+            showTapToFocusToast();
+        }
+    }
+
+    @Override
+    public void onStop() {
+        if (mMediaProviderClient != null) {
+            mMediaProviderClient.release();
+            mMediaProviderClient = null;
+        }
+    }
+
+    // onClick handler for R.id.btn_done
+    @OnClickAttr
+    public void onReviewDoneClicked(View v) {
+        doAttach();
+    }
+
+    // onClick handler for R.id.btn_cancel
+    @OnClickAttr
+    public void onReviewCancelClicked(View v) {
+        doCancel();
+    }
+
+    // onClick handler for R.id.btn_retake
+    @OnClickAttr
+    public void onReviewRetakeClicked(View v) {
+        if (mPaused)
+            return;
+
+        hidePostCaptureAlert();
+        setupPreview();
+    }
+
+    private void doAttach() {
+        if (mPaused) {
+            return;
+        }
+
+        byte[] data = mJpegImageData;
+
+        if (mCropValue == null) {
+            // First handle the no crop case -- just return the value.  If the
+            // caller specifies a "save uri" then write the data to its
+            // stream. Otherwise, pass back a scaled down version of the bitmap
+            // directly in the extras.
+            if (mSaveUri != null) {
+                OutputStream outputStream = null;
+                try {
+                    outputStream = mContentResolver.openOutputStream(mSaveUri);
+                    outputStream.write(data);
+                    outputStream.close();
+
+                    mActivity.setResultEx(Activity.RESULT_OK);
+                    mActivity.finish();
+                } catch (IOException ex) {
+                    // ignore exception
+                } finally {
+                    Util.closeSilently(outputStream);
+                }
+            } else {
+                int orientation = Exif.getOrientation(data);
+                Bitmap bitmap = Util.makeBitmap(data, 50 * 1024);
+                bitmap = Util.rotate(bitmap, orientation);
+                mActivity.setResultEx(Activity.RESULT_OK,
+                        new Intent("inline-data").putExtra("data", bitmap));
+                mActivity.finish();
+            }
+        } else {
+            // Save the image to a temp file and invoke the cropper
+            Uri tempUri = null;
+            FileOutputStream tempStream = null;
+            try {
+                File path = mActivity.getFileStreamPath(sTempCropFilename);
+                path.delete();
+                tempStream = mActivity.openFileOutput(sTempCropFilename, 0);
+                tempStream.write(data);
+                tempStream.close();
+                tempUri = Uri.fromFile(path);
+            } catch (FileNotFoundException ex) {
+                mActivity.setResultEx(Activity.RESULT_CANCELED);
+                mActivity.finish();
+                return;
+            } catch (IOException ex) {
+                mActivity.setResultEx(Activity.RESULT_CANCELED);
+                mActivity.finish();
+                return;
+            } finally {
+                Util.closeSilently(tempStream);
+            }
+
+            Bundle newExtras = new Bundle();
+            if (mCropValue.equals("circle")) {
+                newExtras.putString("circleCrop", "true");
+            }
+            if (mSaveUri != null) {
+                newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
+            } else {
+                newExtras.putBoolean(CropExtras.KEY_RETURN_DATA, true);
+            }
+            if (mActivity.isSecureCamera()) {
+                newExtras.putBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, true);
+            }
+
+            Intent cropIntent = new Intent(FilterShowActivity.CROP_ACTION);
+
+            cropIntent.setData(tempUri);
+            cropIntent.putExtras(newExtras);
+
+            mActivity.startActivityForResult(cropIntent, REQUEST_CROP);
+        }
+    }
+
+    private void doCancel() {
+        mActivity.setResultEx(Activity.RESULT_CANCELED, new Intent());
+        mActivity.finish();
+    }
+
+    @Override
+    public void onShutterButtonFocus(boolean pressed) {
+        if (mPaused || collapseCameraControls()
+                || (mCameraState == SNAPSHOT_IN_PROGRESS)
+                || (mCameraState == PREVIEW_STOPPED)) return;
+
+        // Do not do focus if there is not enough storage.
+        if (pressed && !canTakePicture()) return;
+
+        if (pressed) {
+            if (mSceneMode == Util.SCENE_MODE_HDR) {
+                mActivity.hideSwitcher();
+                mActivity.setSwipingEnabled(false);
+            }
+            mFocusManager.onShutterDown();
+        } else {
+            mFocusManager.onShutterUp();
+        }
+    }
+
+    @Override
+    public void onShutterButtonClick() {
+        if (mPaused || collapseCameraControls()
+                || (mCameraState == SWITCHING_CAMERA)
+                || (mCameraState == PREVIEW_STOPPED)) return;
+
+        // Do not take the picture if there is not enough storage.
+        if (mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) {
+            Log.i(TAG, "Not enough space or storage not ready. remaining="
+                    + mActivity.getStorageSpace());
+            return;
+        }
+        Log.v(TAG, "onShutterButtonClick: mCameraState=" + mCameraState);
+
+        // If the user wants to do a snapshot while the previous one is still
+        // in progress, remember the fact and do it after we finish the previous
+        // one and re-start the preview. Snapshot in progress also includes the
+        // state that autofocus is focusing and a picture will be taken when
+        // focus callback arrives.
+        if ((mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS)
+                && !mIsImageCaptureIntent) {
+            mSnapshotOnIdle = true;
+            return;
+        }
+
+        String timer = mPreferences.getString(
+                CameraSettings.KEY_TIMER,
+                mActivity.getString(R.string.pref_camera_timer_default));
+        boolean playSound = mPreferences.getString(CameraSettings.KEY_TIMER_SOUND_EFFECTS,
+                mActivity.getString(R.string.pref_camera_timer_sound_default))
+                .equals(mActivity.getString(R.string.setting_on_value));
+
+        int seconds = Integer.parseInt(timer);
+        // When shutter button is pressed, check whether the previous countdown is
+        // finished. If not, cancel the previous countdown and start a new one.
+        if (mCountDownView.isCountingDown()) {
+            mCountDownView.cancelCountDown();
+            mCountDownView.startCountDown(seconds, playSound);
+        } else if (seconds > 0) {
+            mCountDownView.startCountDown(seconds, playSound);
+        } else {
+           mSnapshotOnIdle = false;
+           mFocusManager.doSnap();
+        }
+    }
+
+    @Override
+    public void installIntentFilter() {
+    }
+
+    @Override
+    public boolean updateStorageHintOnResume() {
+        return mFirstTimeInitialized;
+    }
+
+    @Override
+    public void updateCameraAppView() {
+    }
+
+    @Override
+    public void onResumeBeforeSuper() {
+        mPaused = false;
+    }
+
+    @Override
+    public void onResumeAfterSuper() {
+        if (mOpenCameraFail || mCameraDisabled) return;
+
+        mJpegPictureCallbackTime = 0;
+        mZoomValue = 0;
+
+        // Start the preview if it is not started.
+        if (mCameraState == PREVIEW_STOPPED && mCameraStartUpThread == null) {
+            resetExposureCompensation();
+            mCameraStartUpThread = new CameraStartUpThread();
+            mCameraStartUpThread.start();
+        }
+
+        // If first time initialization is not finished, put it in the
+        // message queue.
+        if (!mFirstTimeInitialized) {
+            mHandler.sendEmptyMessage(FIRST_TIME_INIT);
+        } else {
+            initializeSecondTime();
+        }
+        keepScreenOnAwhile();
+
+        // Dismiss open menu if exists.
+        PopupManager.getInstance(mActivity).notifyShowPopup(null);
+    }
+
+    void waitCameraStartUpThread() {
+        try {
+            if (mCameraStartUpThread != null) {
+                mCameraStartUpThread.cancel();
+                mCameraStartUpThread.join();
+                mCameraStartUpThread = null;
+                setCameraState(IDLE);
+            }
+        } catch (InterruptedException e) {
+            // ignore
+        }
+    }
+
+    @Override
+    public void onPauseBeforeSuper() {
+        mPaused = true;
+    }
+
+    @Override
+    public void onPauseAfterSuper() {
+        // Wait the camera start up thread to finish.
+        waitCameraStartUpThread();
+
+        // When camera is started from secure lock screen for the first time
+        // after screen on, the activity gets onCreate->onResume->onPause->onResume.
+        // To reduce the latency, keep the camera for a short time so it does
+        // not need to be opened again.
+        if (mCameraDevice != null && mActivity.isSecureCamera()
+                && ActivityBase.isFirstStartAfterScreenOn()) {
+            ActivityBase.resetFirstStartAfterScreenOn();
+            CameraHolder.instance().keep(KEEP_CAMERA_TIMEOUT);
+        }
+        // Reset the focus first. Camera CTS does not guarantee that
+        // cancelAutoFocus is allowed after preview stops.
+        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
+            mCameraDevice.cancelAutoFocus();
+        }
+        stopPreview();
+        mCountDownView.cancelCountDown();
+        // Close the camera now because other activities may need to use it.
+        closeCamera();
+        if (mSurfaceTexture != null) {
+            ((CameraScreenNail) mActivity.mCameraScreenNail).releaseSurfaceTexture();
+            mSurfaceTexture = null;
+        }
+        resetScreenOn();
+
+        // Clear UI.
+        collapseCameraControls();
+        if (mFaceView != null) mFaceView.clear();
+
+        if (mFirstTimeInitialized) {
+            if (mMediaSaver != null) {
+                mMediaSaver.finish();
+                mMediaSaver = null;
+                mNamedImages = null;
+            }
+        }
+
+        if (mLocationManager != null) mLocationManager.recordLocation(false);
+
+        // If we are in an image capture intent and has taken
+        // a picture, we just clear it in onPause.
+        mJpegImageData = null;
+
+        // Remove the messages in the event queue.
+        mHandler.removeMessages(SETUP_PREVIEW);
+        mHandler.removeMessages(FIRST_TIME_INIT);
+        mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
+        mHandler.removeMessages(SWITCH_CAMERA);
+        mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION);
+        mHandler.removeMessages(CAMERA_OPEN_DONE);
+        mHandler.removeMessages(START_PREVIEW_DONE);
+        mHandler.removeMessages(OPEN_CAMERA_FAIL);
+        mHandler.removeMessages(CAMERA_DISABLED);
+
+        mPendingSwitchCameraId = -1;
+        if (mFocusManager != null) mFocusManager.removeMessages();
+    }
+
+    private void initializeControlByIntent() {
+        mBlocker = mRootView.findViewById(R.id.blocker);
+        mMenu = mRootView.findViewById(R.id.menu);
+        mMenu.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mPieRenderer != null) {
+                    // If autofocus is not finished, cancel autofocus so that the
+                    // subsequent touch can be handled by PreviewGestures
+                    if (mCameraState == FOCUSING) cancelAutoFocus();
+                    mPieRenderer.showInCenter();
+                }
+            }
+        });
+        if (mIsImageCaptureIntent) {
+
+            mActivity.hideSwitcher();
+            // Cannot use RotateImageView for "done" and "cancel" button because
+            // the tablet layout uses RotateLayout, which cannot be cast to
+            // RotateImageView.
+            mReviewDoneButton = (Rotatable) mRootView.findViewById(R.id.btn_done);
+            mReviewCancelButton = (Rotatable) mRootView.findViewById(R.id.btn_cancel);
+            mReviewRetakeButton = mRootView.findViewById(R.id.btn_retake);
+            ((View) mReviewCancelButton).setVisibility(View.VISIBLE);
+
+            ((View) mReviewDoneButton).setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    onReviewDoneClicked(v);
+                }
+            });
+            ((View) mReviewCancelButton).setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    onReviewCancelClicked(v);
+                }
+            });
+
+            mReviewRetakeButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    onReviewRetakeClicked(v);
+                }
+            });
+
+            // Not grayed out upon disabled, to make the follow-up fade-out
+            // effect look smooth. Note that the review done button in tablet
+            // layout is not a TwoStateImageView.
+            if (mReviewDoneButton instanceof TwoStateImageView) {
+                ((TwoStateImageView) mReviewDoneButton).enableFilter(false);
+            }
+
+            setupCaptureParams();
+        }
+    }
+
+    /**
+     * The focus manager is the first UI related element to get initialized,
+     * and it requires the RenderOverlay, so initialize it here
+     */
+    private void initializeFocusManager() {
+        // Create FocusManager object. startPreview needs it.
+        mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
+        // if mFocusManager not null, reuse it
+        // otherwise create a new instance
+        if (mFocusManager != null) {
+            mFocusManager.removeMessages();
+        } else {
+            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+            boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
+            String[] defaultFocusModes = mActivity.getResources().getStringArray(
+                    R.array.pref_camera_focusmode_default_array);
+            mFocusManager = new FocusOverlayManager(mPreferences, defaultFocusModes,
+                    mInitialParams, this, mirror,
+                    mActivity.getMainLooper());
+        }
+    }
+
+    private void initializeMiscControls() {
+        // startPreview needs this.
+        mPreviewFrameLayout = (PreviewFrameLayout) mRootView.findViewById(R.id.frame);
+        // Set touch focus listener.
+        mActivity.setSingleTapUpListener(mPreviewFrameLayout);
+
+        mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);
+        mPreviewFrameLayout.setOnSizeChangedListener(this);
+        mPreviewFrameLayout.setOnLayoutChangeListener(mActivity);
+        if (!ApiHelper.HAS_SURFACE_TEXTURE) {
+            mPreviewSurfaceView =
+                    (PreviewSurfaceView) mRootView.findViewById(R.id.preview_surface_view);
+            mPreviewSurfaceView.setVisibility(View.VISIBLE);
+            mPreviewSurfaceView.getHolder().addCallback(this);
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        Log.v(TAG, "onConfigurationChanged");
+        setDisplayOrientation();
+
+        // Only the views in photo_module_content need to be removed and recreated
+        // i.e. CountDownView won't be recreated
+        ViewGroup viewGroup = (ViewGroup) mRootView.findViewById(R.id.camera_app);
+        viewGroup.removeAllViews();
+        LayoutInflater inflater = mActivity.getLayoutInflater();
+        inflater.inflate(R.layout.photo_module_content, (ViewGroup) viewGroup);
+
+        // from onCreate()
+        initializeControlByIntent();
+
+        initializeFocusManager();
+        initializeMiscControls();
+        loadCameraPreferences();
+
+        // from initializeFirstTime()
+        mShutterButton = mActivity.getShutterButton();
+        mShutterButton.setOnShutterButtonListener(this);
+        initializeZoom();
+        initOnScreenIndicator();
+        updateOnScreenIndicators();
+        if (mFaceView != null) {
+            mFaceView.clear();
+            mFaceView.setVisibility(View.VISIBLE);
+            mFaceView.setDisplayOrientation(mDisplayOrientation);
+            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+            mFaceView.setMirror(info.facing == CameraInfo.CAMERA_FACING_FRONT);
+            mFaceView.resume();
+            mFocusManager.setFaceView(mFaceView);
+        }
+        initializeRenderOverlay();
+        onFullScreenChanged(mActivity.isInCameraApp());
+        if (mJpegImageData != null) {  // Jpeg data found, picture has been taken.
+            showPostCaptureAlert();
+        }
+    }
+
+    @Override
+    public void onActivityResult(
+            int requestCode, int resultCode, Intent data) {
+        switch (requestCode) {
+            case REQUEST_CROP: {
+                Intent intent = new Intent();
+                if (data != null) {
+                    Bundle extras = data.getExtras();
+                    if (extras != null) {
+                        intent.putExtras(extras);
+                    }
+                }
+                mActivity.setResultEx(resultCode, intent);
+                mActivity.finish();
+
+                File path = mActivity.getFileStreamPath(sTempCropFilename);
+                path.delete();
+
+                break;
+            }
+        }
+    }
+
+    private boolean canTakePicture() {
+        return isCameraIdle() && (mActivity.getStorageSpace() > Storage.LOW_STORAGE_THRESHOLD);
+    }
+
+    @Override
+    public void autoFocus() {
+        mFocusStartTime = System.currentTimeMillis();
+        mCameraDevice.autoFocus(mAutoFocusCallback);
+        setCameraState(FOCUSING);
+    }
+
+    @Override
+    public void cancelAutoFocus() {
+        mCameraDevice.cancelAutoFocus();
+        setCameraState(IDLE);
+        setCameraParameters(UPDATE_PARAM_PREFERENCE);
+    }
+
+    // Preview area is touched. Handle touch focus.
+    @Override
+    public void onSingleTapUp(View view, int x, int y) {
+        if (mPaused || mCameraDevice == null || !mFirstTimeInitialized
+                || mCameraState == SNAPSHOT_IN_PROGRESS
+                || mCameraState == SWITCHING_CAMERA
+                || mCameraState == PREVIEW_STOPPED) {
+            return;
+        }
+
+        // Do not trigger touch focus if popup window is opened.
+        if (removeTopLevelPopup()) return;
+
+        // Check if metering area or focus area is supported.
+        if (!mFocusAreaSupported && !mMeteringAreaSupported) return;
+        mFocusManager.onSingleTapUp(x, y);
+    }
+
+    @Override
+    public boolean onBackPressed() {
+        if (mPieRenderer != null && mPieRenderer.showsItems()) {
+            mPieRenderer.hide();
+            return true;
+        }
+        // In image capture mode, back button should:
+        // 1) if there is any popup, dismiss them, 2) otherwise, get out of image capture
+        if (mIsImageCaptureIntent) {
+            if (!removeTopLevelPopup()) {
+                // no popup to dismiss, cancel image capture
+                doCancel();
+            }
+            return true;
+        } else if (!isCameraIdle()) {
+            // ignore backs while we're taking a picture
+            return true;
+        } else {
+            return removeTopLevelPopup();
+        }
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_VOLUME_UP:
+        case KeyEvent.KEYCODE_VOLUME_DOWN:
+        case KeyEvent.KEYCODE_FOCUS:
+            if (mActivity.isInCameraApp() && mFirstTimeInitialized) {
+                if (event.getRepeatCount() == 0) {
+                    onShutterButtonFocus(true);
+                }
+                return true;
+            }
+            return false;
+        case KeyEvent.KEYCODE_CAMERA:
+            if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
+                onShutterButtonClick();
+            }
+            return true;
+        case KeyEvent.KEYCODE_DPAD_CENTER:
+            // If we get a dpad center event without any focused view, move
+            // the focus to the shutter button and press it.
+            if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
+                // Start auto-focus immediately to reduce shutter lag. After
+                // the shutter button gets the focus, onShutterButtonFocus()
+                // will be called again but it is fine.
+                if (removeTopLevelPopup()) return true;
+                onShutterButtonFocus(true);
+                if (mShutterButton.isInTouchMode()) {
+                    mShutterButton.requestFocusFromTouch();
+                } else {
+                    mShutterButton.requestFocus();
+                }
+                mShutterButton.setPressed(true);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_VOLUME_UP:
+        case KeyEvent.KEYCODE_VOLUME_DOWN:
+            if (mActivity.isInCameraApp() && mFirstTimeInitialized) {
+                onShutterButtonClick();
+                return true;
+            }
+            return false;
+        case KeyEvent.KEYCODE_FOCUS:
+            if (mFirstTimeInitialized) {
+                onShutterButtonFocus(false);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    private void closeCamera() {
+        if (mCameraDevice != null) {
+            mCameraDevice.setZoomChangeListener(null);
+            if(ApiHelper.HAS_FACE_DETECTION) {
+                mCameraDevice.setFaceDetectionListener(null);
+            }
+            mCameraDevice.setErrorCallback(null);
+            CameraHolder.instance().release();
+            mFaceDetectionStarted = false;
+            mCameraDevice = null;
+            setCameraState(PREVIEW_STOPPED);
+            mFocusManager.onCameraReleased();
+        }
+    }
+
+    private void setDisplayOrientation() {
+        mDisplayRotation = Util.getDisplayRotation(mActivity);
+        mDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
+        mCameraDisplayOrientation = Util.getDisplayOrientation(0, mCameraId);
+        if (mFaceView != null) {
+            mFaceView.setDisplayOrientation(mDisplayOrientation);
+        }
+        if (mFocusManager != null) {
+            mFocusManager.setDisplayOrientation(mDisplayOrientation);
+        }
+        // GLRoot also uses the DisplayRotation, and needs to be told to layout to update
+        mActivity.getGLRoot().requestLayoutContentPane();
+    }
+
+    // Only called by UI thread.
+    private void setupPreview() {
+        mFocusManager.resetTouchFocus();
+        startPreview();
+        setCameraState(IDLE);
+        startFaceDetection();
+    }
+
+    // This can be called by UI Thread or CameraStartUpThread. So this should
+    // not modify the views.
+    private void startPreview() {
+        mCameraDevice.setErrorCallback(mErrorCallback);
+
+        // ICS camera frameworks has a bug. Face detection state is not cleared
+        // after taking a picture. Stop the preview to work around it. The bug
+        // was fixed in JB.
+        if (mCameraState != PREVIEW_STOPPED) stopPreview();
+
+        setDisplayOrientation();
+
+        if (!mSnapshotOnIdle) {
+            // If the focus mode is continuous autofocus, call cancelAutoFocus to
+            // resume it because it may have been paused by autoFocus call.
+            if (Util.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusManager.getFocusMode())) {
+                mCameraDevice.cancelAutoFocus();
+            }
+            mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
+        }
+        setCameraParameters(UPDATE_PARAM_ALL);
+
+        if (ApiHelper.HAS_SURFACE_TEXTURE) {
+            CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
+            if (mSurfaceTexture == null) {
+                Size size = mParameters.getPreviewSize();
+                if (mCameraDisplayOrientation % 180 == 0) {
+                    screenNail.setSize(size.width, size.height);
+                } else {
+                    screenNail.setSize(size.height, size.width);
+                }
+                screenNail.enableAspectRatioClamping();
+                mActivity.notifyScreenNailChanged();
+                screenNail.acquireSurfaceTexture();
+                CameraStartUpThread t = mCameraStartUpThread;
+                if (t != null && t.isCanceled()) {
+                    return; // Exiting, so no need to get the surface texture.
+                }
+                mSurfaceTexture = screenNail.getSurfaceTexture();
+            }
+            mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
+            if (mSurfaceTexture != null) {
+                mCameraDevice.setPreviewTextureAsync((SurfaceTexture) mSurfaceTexture);
+            }
+        } else {
+            mCameraDevice.setDisplayOrientation(mDisplayOrientation);
+            mCameraDevice.setPreviewDisplayAsync(mCameraSurfaceHolder);
+        }
+
+        Log.v(TAG, "startPreview");
+        mCameraDevice.startPreviewAsync();
+
+        mFocusManager.onPreviewStarted();
+
+        if (mSnapshotOnIdle) {
+            mHandler.post(mDoSnapRunnable);
+        }
+    }
+
+    private void stopPreview() {
+        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
+            Log.v(TAG, "stopPreview");
+            mCameraDevice.stopPreview();
+            mFaceDetectionStarted = false;
+        }
+        setCameraState(PREVIEW_STOPPED);
+        if (mFocusManager != null) mFocusManager.onPreviewStopped();
+    }
+
+    @SuppressWarnings("deprecation")
+    private void updateCameraParametersInitialize() {
+        // Reset preview frame rate to the maximum because it may be lowered by
+        // video camera application.
+        List<Integer> frameRates = mParameters.getSupportedPreviewFrameRates();
+        if (frameRates != null) {
+            Integer max = Collections.max(frameRates);
+            mParameters.setPreviewFrameRate(max);
+        }
+
+        mParameters.set(Util.RECORDING_HINT, Util.FALSE);
+
+        // Disable video stabilization. Convenience methods not available in API
+        // level <= 14
+        String vstabSupported = mParameters.get("video-stabilization-supported");
+        if ("true".equals(vstabSupported)) {
+            mParameters.set("video-stabilization", "false");
+        }
+    }
+
+    private void updateCameraParametersZoom() {
+        // Set zoom.
+        if (mParameters.isZoomSupported()) {
+            mParameters.setZoom(mZoomValue);
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+    private void setAutoExposureLockIfSupported() {
+        if (mAeLockSupported) {
+            mParameters.setAutoExposureLock(mFocusManager.getAeAwbLock());
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+    private void setAutoWhiteBalanceLockIfSupported() {
+        if (mAwbLockSupported) {
+            mParameters.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    private void setFocusAreasIfSupported() {
+        if (mFocusAreaSupported) {
+            mParameters.setFocusAreas(mFocusManager.getFocusAreas());
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    private void setMeteringAreasIfSupported() {
+        if (mMeteringAreaSupported) {
+            // Use the same area for focus and metering.
+            mParameters.setMeteringAreas(mFocusManager.getMeteringAreas());
+        }
+    }
+
+    private void updateCameraParametersPreference() {
+        setAutoExposureLockIfSupported();
+        setAutoWhiteBalanceLockIfSupported();
+        setFocusAreasIfSupported();
+        setMeteringAreasIfSupported();
+
+        // Set picture size.
+        String pictureSize = mPreferences.getString(
+                CameraSettings.KEY_PICTURE_SIZE, null);
+        if (pictureSize == null) {
+            CameraSettings.initialCameraPictureSize(mActivity, mParameters);
+        } else {
+            List<Size> supported = mParameters.getSupportedPictureSizes();
+            CameraSettings.setCameraPictureSize(
+                    pictureSize, supported, mParameters);
+        }
+        Size size = mParameters.getPictureSize();
+
+        // Set a preview size that is closest to the viewfinder height and has
+        // the right aspect ratio.
+        List<Size> sizes = mParameters.getSupportedPreviewSizes();
+        Size optimalSize = Util.getOptimalPreviewSize(mActivity, sizes,
+                (double) size.width / size.height);
+        Size original = mParameters.getPreviewSize();
+        if (!original.equals(optimalSize)) {
+            mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
+
+            // Zoom related settings will be changed for different preview
+            // sizes, so set and read the parameters to get latest values
+            mCameraDevice.setParameters(mParameters);
+            mParameters = mCameraDevice.getParameters();
+        }
+        Log.v(TAG, "Preview size is " + optimalSize.width + "x" + optimalSize.height);
+
+        // Since changing scene mode may change supported values, set scene mode
+        // first. HDR is a scene mode. To promote it in UI, it is stored in a
+        // separate preference.
+        String hdr = mPreferences.getString(CameraSettings.KEY_CAMERA_HDR,
+                mActivity.getString(R.string.pref_camera_hdr_default));
+        if (mActivity.getString(R.string.setting_on_value).equals(hdr)) {
+            mSceneMode = Util.SCENE_MODE_HDR;
+        } else {
+            mSceneMode = mPreferences.getString(
+                CameraSettings.KEY_SCENE_MODE,
+                mActivity.getString(R.string.pref_camera_scenemode_default));
+        }
+        if (Util.isSupported(mSceneMode, mParameters.getSupportedSceneModes())) {
+            if (!mParameters.getSceneMode().equals(mSceneMode)) {
+                mParameters.setSceneMode(mSceneMode);
+
+                // Setting scene mode will change the settings of flash mode,
+                // white balance, and focus mode. Here we read back the
+                // parameters, so we can know those settings.
+                mCameraDevice.setParameters(mParameters);
+                mParameters = mCameraDevice.getParameters();
+            }
+        } else {
+            mSceneMode = mParameters.getSceneMode();
+            if (mSceneMode == null) {
+                mSceneMode = Parameters.SCENE_MODE_AUTO;
+            }
+        }
+
+        // Set JPEG quality.
+        int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
+                CameraProfile.QUALITY_HIGH);
+        mParameters.setJpegQuality(jpegQuality);
+
+        // For the following settings, we need to check if the settings are
+        // still supported by latest driver, if not, ignore the settings.
+
+        // Set exposure compensation
+        int value = CameraSettings.readExposure(mPreferences);
+        int max = mParameters.getMaxExposureCompensation();
+        int min = mParameters.getMinExposureCompensation();
+        if (value >= min && value <= max) {
+            mParameters.setExposureCompensation(value);
+        } else {
+            Log.w(TAG, "invalid exposure range: " + value);
+        }
+
+        if (Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
+            // Set flash mode.
+            String flashMode = mPreferences.getString(
+                    CameraSettings.KEY_FLASH_MODE,
+                    mActivity.getString(R.string.pref_camera_flashmode_default));
+            List<String> supportedFlash = mParameters.getSupportedFlashModes();
+            if (Util.isSupported(flashMode, supportedFlash)) {
+                mParameters.setFlashMode(flashMode);
+            } else {
+                flashMode = mParameters.getFlashMode();
+                if (flashMode == null) {
+                    flashMode = mActivity.getString(
+                            R.string.pref_camera_flashmode_no_flash);
+                }
+            }
+
+            // Set white balance parameter.
+            String whiteBalance = mPreferences.getString(
+                    CameraSettings.KEY_WHITE_BALANCE,
+                    mActivity.getString(R.string.pref_camera_whitebalance_default));
+            if (Util.isSupported(whiteBalance,
+                    mParameters.getSupportedWhiteBalance())) {
+                mParameters.setWhiteBalance(whiteBalance);
+            } else {
+                whiteBalance = mParameters.getWhiteBalance();
+                if (whiteBalance == null) {
+                    whiteBalance = Parameters.WHITE_BALANCE_AUTO;
+                }
+            }
+
+            // Set focus mode.
+            mFocusManager.overrideFocusMode(null);
+            mParameters.setFocusMode(mFocusManager.getFocusMode());
+        } else {
+            mFocusManager.overrideFocusMode(mParameters.getFocusMode());
+        }
+
+        if (mContinousFocusSupported && ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK) {
+            updateAutoFocusMoveCallback();
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+    private void updateAutoFocusMoveCallback() {
+        if (mParameters.getFocusMode().equals(Util.FOCUS_MODE_CONTINUOUS_PICTURE)) {
+            mCameraDevice.setAutoFocusMoveCallback(
+                (AutoFocusMoveCallback) mAutoFocusMoveCallback);
+        } else {
+            mCameraDevice.setAutoFocusMoveCallback(null);
+        }
+    }
+
+    // We separate the parameters into several subsets, so we can update only
+    // the subsets actually need updating. The PREFERENCE set needs extra
+    // locking because the preference can be changed from GLThread as well.
+    private void setCameraParameters(int updateSet) {
+        if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
+            updateCameraParametersInitialize();
+        }
+
+        if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
+            updateCameraParametersZoom();
+        }
+
+        if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
+            updateCameraParametersPreference();
+        }
+
+        mCameraDevice.setParameters(mParameters);
+    }
+
+    // If the Camera is idle, update the parameters immediately, otherwise
+    // accumulate them in mUpdateSet and update later.
+    private void setCameraParametersWhenIdle(int additionalUpdateSet) {
+        mUpdateSet |= additionalUpdateSet;
+        if (mCameraDevice == null) {
+            // We will update all the parameters when we open the device, so
+            // we don't need to do anything now.
+            mUpdateSet = 0;
+            return;
+        } else if (isCameraIdle()) {
+            setCameraParameters(mUpdateSet);
+            updateSceneModeUI();
+            mUpdateSet = 0;
+        } else {
+            if (!mHandler.hasMessages(SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
+                mHandler.sendEmptyMessageDelayed(
+                        SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
+            }
+        }
+    }
+
+    private boolean isCameraIdle() {
+        return (mCameraState == IDLE) ||
+                (mCameraState == PREVIEW_STOPPED) ||
+                ((mFocusManager != null) && mFocusManager.isFocusCompleted()
+                        && (mCameraState != SWITCHING_CAMERA));
+    }
+
+    private boolean isImageCaptureIntent() {
+        String action = mActivity.getIntent().getAction();
+        return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
+                || ActivityBase.ACTION_IMAGE_CAPTURE_SECURE.equals(action));
+    }
+
+    private void setupCaptureParams() {
+        Bundle myExtras = mActivity.getIntent().getExtras();
+        if (myExtras != null) {
+            mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
+            mCropValue = myExtras.getString("crop");
+        }
+    }
+
+    private void showPostCaptureAlert() {
+        if (mIsImageCaptureIntent) {
+            mOnScreenIndicators.setVisibility(View.GONE);
+            mMenu.setVisibility(View.GONE);
+            Util.fadeIn((View) mReviewDoneButton);
+            mShutterButton.setVisibility(View.INVISIBLE);
+            Util.fadeIn(mReviewRetakeButton);
+        }
+    }
+
+    private void hidePostCaptureAlert() {
+        if (mIsImageCaptureIntent) {
+            mOnScreenIndicators.setVisibility(View.VISIBLE);
+            mMenu.setVisibility(View.VISIBLE);
+            Util.fadeOut((View) mReviewDoneButton);
+            mShutterButton.setVisibility(View.VISIBLE);
+            Util.fadeOut(mReviewRetakeButton);
+        }
+    }
+
+    @Override
+    public void onSharedPreferenceChanged() {
+        // ignore the events after "onPause()"
+        if (mPaused) return;
+
+        boolean recordLocation = RecordLocationPreference.get(
+                mPreferences, mContentResolver);
+        mLocationManager.recordLocation(recordLocation);
+
+        setCameraParametersWhenIdle(UPDATE_PARAM_PREFERENCE);
+        setPreviewFrameLayoutAspectRatio();
+        updateOnScreenIndicators();
+    }
+
+    @Override
+    public void onCameraPickerClicked(int cameraId) {
+        if (mPaused || mPendingSwitchCameraId != -1) return;
+
+        mPendingSwitchCameraId = cameraId;
+        if (ApiHelper.HAS_SURFACE_TEXTURE) {
+            Log.v(TAG, "Start to copy texture. cameraId=" + cameraId);
+            // We need to keep a preview frame for the animation before
+            // releasing the camera. This will trigger onPreviewTextureCopied.
+            ((CameraScreenNail) mActivity.mCameraScreenNail).copyTexture();
+            // Disable all camera controls.
+            setCameraState(SWITCHING_CAMERA);
+        } else {
+            switchCamera();
+        }
+    }
+
+    private void switchCamera() {
+        if (mPaused) return;
+
+        Log.v(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId);
+        mCameraId = mPendingSwitchCameraId;
+        mPendingSwitchCameraId = -1;
+        mPhotoControl.setCameraId(mCameraId);
+
+        // from onPause
+        closeCamera();
+        collapseCameraControls();
+        if (mFaceView != null) mFaceView.clear();
+        if (mFocusManager != null) mFocusManager.removeMessages();
+
+        // Restart the camera and initialize the UI. From onCreate.
+        mPreferences.setLocalId(mActivity, mCameraId);
+        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
+        try {
+            mCameraDevice = Util.openCamera(mActivity, mCameraId);
+            mParameters = mCameraDevice.getParameters();
+        } catch (CameraHardwareException e) {
+            Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
+            return;
+        } catch (CameraDisabledException e) {
+            Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
+            return;
+        }
+        initializeCapabilities();
+        CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+        boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
+        mFocusManager.setMirror(mirror);
+        mFocusManager.setParameters(mInitialParams);
+        setupPreview();
+        loadCameraPreferences();
+        initializePhotoControl();
+
+        // from initializeFirstTime
+        initializeZoom();
+        updateOnScreenIndicators();
+        showTapToFocusToastIfNeeded();
+
+        if (ApiHelper.HAS_SURFACE_TEXTURE) {
+            // Start switch camera animation. Post a message because
+            // onFrameAvailable from the old camera may already exist.
+            mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
+        }
+    }
+
+    @Override
+    public void onPieOpened(int centerX, int centerY) {
+        mActivity.cancelActivityTouchHandling();
+        mActivity.setSwipingEnabled(false);
+        if (mFaceView != null) {
+            mFaceView.setBlockDraw(true);
+        }
+    }
+
+    @Override
+    public void onPieClosed() {
+        mActivity.setSwipingEnabled(true);
+        if (mFaceView != null) {
+            mFaceView.setBlockDraw(false);
+        }
+    }
+
+    // Preview texture has been copied. Now camera can be released and the
+    // animation can be started.
+    @Override
+    public void onPreviewTextureCopied() {
+        mHandler.sendEmptyMessage(SWITCH_CAMERA);
+    }
+
+    @Override
+    public void onCaptureTextureCopied() {
+    }
+
+    @Override
+    public void onUserInteraction() {
+        if (!mActivity.isFinishing()) keepScreenOnAwhile();
+    }
+
+    private void resetScreenOn() {
+        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+        mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+
+    private void keepScreenOnAwhile() {
+        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
+    }
+
+    // TODO: Delete this function after old camera code is removed
+    @Override
+    public void onRestorePreferencesClicked() {
+    }
+
+    @Override
+    public void onOverriddenPreferencesClicked() {
+        if (mPaused) return;
+        if (mNotSelectableToast == null) {
+            String str = mActivity.getResources().getString(R.string.not_selectable_in_scene_mode);
+            mNotSelectableToast = Toast.makeText(mActivity, str, Toast.LENGTH_SHORT);
+        }
+        mNotSelectableToast.show();
+    }
+
+    private void showTapToFocusToast() {
+        // TODO: Use a toast?
+        new RotateTextToast(mActivity, R.string.tap_to_focus, 0).show();
+        // Clear the preference.
+        Editor editor = mPreferences.edit();
+        editor.putBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, false);
+        editor.apply();
+    }
+
+    private void initializeCapabilities() {
+        mInitialParams = mCameraDevice.getParameters();
+        mFocusAreaSupported = Util.isFocusAreaSupported(mInitialParams);
+        mMeteringAreaSupported = Util.isMeteringAreaSupported(mInitialParams);
+        mAeLockSupported = Util.isAutoExposureLockSupported(mInitialParams);
+        mAwbLockSupported = Util.isAutoWhiteBalanceLockSupported(mInitialParams);
+        mContinousFocusSupported = mInitialParams.getSupportedFocusModes().contains(
+                Util.FOCUS_MODE_CONTINUOUS_PICTURE);
+    }
+
+    // PreviewFrameLayout size has changed.
+    @Override
+    public void onSizeChanged(int width, int height) {
+        if (mFocusManager != null) mFocusManager.setPreviewSize(width, height);
+    }
+
+    @Override
+    public void onCountDownFinished() {
+        mSnapshotOnIdle = false;
+        mFocusManager.doSnap();
+    }
+
+    void setPreviewFrameLayoutAspectRatio() {
+        // Set the preview frame aspect ratio according to the picture size.
+        Size size = mParameters.getPictureSize();
+        mPreviewFrameLayout.setAspectRatio((double) size.width / size.height);
+    }
+
+    @Override
+    public boolean needsSwitcher() {
+        return !mIsImageCaptureIntent;
+    }
+
+    public void showPopup(AbstractSettingPopup popup) {
+        mActivity.hideUI();
+        mBlocker.setVisibility(View.INVISIBLE);
+        setShowMenu(false);
+        mPopup = popup;
+        mPopup.setVisibility(View.VISIBLE);
+        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
+                LayoutParams.WRAP_CONTENT);
+        lp.gravity = Gravity.CENTER;
+        ((FrameLayout) mRootView).addView(mPopup, lp);
+    }
+
+    public void dismissPopup(boolean topPopupOnly) {
+        dismissPopup(topPopupOnly, true);
+    }
+
+    private void dismissPopup(boolean topOnly, boolean fullScreen) {
+        if (fullScreen) {
+            mActivity.showUI();
+            mBlocker.setVisibility(View.VISIBLE);
+        }
+        setShowMenu(fullScreen);
+        if (mPopup != null) {
+            ((FrameLayout) mRootView).removeView(mPopup);
+            mPopup = null;
+        }
+        mPhotoControl.popupDismissed(topOnly);
+    }
+
+    @Override
+    public void onShowSwitcherPopup() {
+        if (mPieRenderer != null && mPieRenderer.showsItems()) {
+            mPieRenderer.hide();
+        }
+    }
+
+}