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