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