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