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