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