PhotoModule.java revision 5cb575db0bfab1686af0c379280e3f317f82bbcf
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.ContentResolver;
22import android.content.Context;
23import android.content.Intent;
24import android.graphics.Bitmap;
25import android.graphics.SurfaceTexture;
26import android.hardware.Camera.CameraInfo;
27import android.hardware.Camera.Parameters;
28import android.hardware.Camera.Size;
29import android.hardware.Sensor;
30import android.hardware.SensorEvent;
31import android.hardware.SensorEventListener;
32import android.hardware.SensorManager;
33import android.location.Location;
34import android.media.CameraProfile;
35import android.net.Uri;
36import android.os.Build;
37import android.os.Bundle;
38import android.os.Handler;
39import android.os.Looper;
40import android.os.Message;
41import android.os.MessageQueue;
42import android.os.SystemClock;
43import android.provider.MediaStore;
44import android.util.Log;
45import android.view.KeyEvent;
46import android.view.OrientationEventListener;
47import android.view.View;
48import com.android.camera.PhotoModule.NamedImages.NamedEntity;
49import com.android.camera.app.AppController;
50import com.android.camera.app.CameraAppUI;
51import com.android.camera.app.CameraManager.CameraAFCallback;
52import com.android.camera.app.CameraManager.CameraAFMoveCallback;
53import com.android.camera.app.CameraManager.CameraPictureCallback;
54import com.android.camera.app.CameraManager.CameraProxy;
55import com.android.camera.app.CameraManager.CameraShutterCallback;
56import com.android.camera.app.LocationManager;
57import com.android.camera.app.MediaSaver;
58import com.android.camera.app.MemoryManager;
59import com.android.camera.app.MemoryManager.MemoryListener;
60import com.android.camera.exif.ExifInterface;
61import com.android.camera.exif.ExifTag;
62import com.android.camera.exif.Rational;
63import com.android.camera.hardware.HardwareSpec;
64import com.android.camera.hardware.HardwareSpecImpl;
65import com.android.camera.module.ModuleController;
66import com.android.camera.settings.SettingsManager;
67import com.android.camera.settings.SettingsUtil;
68import com.android.camera.ui.RotateTextToast;
69import com.android.camera.util.ApiHelper;
70import com.android.camera.util.CameraUtil;
71import com.android.camera.util.GcamHelper;
72import com.android.camera.util.UsageStatistics;
73import com.android.camera2.R;
74import com.google.common.logging.eventprotos;
75
76import java.io.File;
77import java.io.FileNotFoundException;
78import java.io.FileOutputStream;
79import java.io.IOException;
80import java.io.OutputStream;
81import java.lang.ref.WeakReference;
82import java.util.List;
83import java.util.Vector;
84
85public class PhotoModule
86        extends CameraModule
87        implements PhotoController,
88        ModuleController,
89        MemoryListener,
90        FocusOverlayManager.Listener,
91        ShutterButton.OnShutterButtonListener,
92        SensorEventListener,
93        SettingsManager.OnSettingChangedListener {
94
95    private static final String TAG = "PhotoModule";
96
97    // We number the request code from 1000 to avoid collision with Gallery.
98    private static final int REQUEST_CROP = 1000;
99
100    // Messages defined for the UI thread handler.
101    private static final int MSG_FIRST_TIME_INIT = 1;
102    private static final int MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE = 2;
103    private static final int MSG_SHOW_TAP_TO_FOCUS_TOAST = 3;
104    private static final int MSG_SWITCH_TO_GCAM_MODULE = 4;
105
106    // The subset of parameters we need to update in setCameraParameters().
107    private static final int UPDATE_PARAM_INITIALIZE = 1;
108    private static final int UPDATE_PARAM_ZOOM = 2;
109    private static final int UPDATE_PARAM_PREFERENCE = 4;
110    private static final int UPDATE_PARAM_ALL = -1;
111
112    // This is the delay before we execute onResume tasks when coming
113    // from the lock screen, to allow time for onPause to execute.
114    private static final int ON_RESUME_TASKS_DELAY_MSEC = 20;
115
116    private static final String DEBUG_IMAGE_PREFIX = "DEBUG_";
117
118    private CameraActivity mActivity;
119    private CameraProxy mCameraDevice;
120    private int mCameraId;
121    private Parameters mParameters;
122    private boolean mPaused;
123
124    private PhotoUI mUI;
125
126    // The activity is going to switch to the specified camera id. This is
127    // needed because texture copy is done in GL thread. -1 means camera is not
128    // switching.
129    protected int mPendingSwitchCameraId = -1;
130
131    // When setCameraParametersWhenIdle() is called, we accumulate the subsets
132    // needed to be updated in mUpdateSet.
133    private int mUpdateSet;
134
135    private static final int SCREEN_DELAY = 2 * 60 * 1000;
136
137    private int mZoomValue; // The current zoom value.
138
139    private Parameters mInitialParams;
140    private boolean mFocusAreaSupported;
141    private boolean mMeteringAreaSupported;
142    private boolean mAeLockSupported;
143    private boolean mAwbLockSupported;
144    private boolean mContinuousFocusSupported;
145
146    // The degrees of the device rotated clockwise from its natural orientation.
147    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
148
149    private static final String sTempCropFilename = "crop-temp";
150
151    private boolean mFaceDetectionStarted = false;
152
153    // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
154    private String mCropValue;
155    private Uri mSaveUri;
156
157    private Uri mDebugUri;
158
159    // We use a queue to generated names of the images to be used later
160    // when the image is ready to be saved.
161    private NamedImages mNamedImages;
162
163    private final Runnable mDoSnapRunnable = new Runnable() {
164        @Override
165        public void run() {
166            onShutterButtonClick();
167        }
168    };
169
170    /**
171     * An unpublished intent flag requesting to return as soon as capturing is
172     * completed. TODO: consider publishing by moving into MediaStore.
173     */
174    private static final String EXTRA_QUICK_CAPTURE =
175            "android.intent.extra.quickCapture";
176
177    // The display rotation in degrees. This is only valid when mCameraState is
178    // not PREVIEW_STOPPED.
179    private int mDisplayRotation;
180    // The value for android.hardware.Camera.setDisplayOrientation.
181    private int mCameraDisplayOrientation;
182    // The value for UI components like indicators.
183    private int mDisplayOrientation;
184    // The value for android.hardware.Camera.Parameters.setRotation.
185    private int mJpegRotation;
186    // Indicates whether we are using front camera
187    private boolean mMirror;
188    private boolean mFirstTimeInitialized;
189    private boolean mIsImageCaptureIntent;
190
191    private int mCameraState = PREVIEW_STOPPED;
192    private boolean mSnapshotOnIdle = false;
193
194    private ContentResolver mContentResolver;
195
196    private LocationManager mLocationManager;
197    private AppController mAppController;
198
199    private final PostViewPictureCallback mPostViewPictureCallback =
200            new PostViewPictureCallback();
201    private final RawPictureCallback mRawPictureCallback =
202            new RawPictureCallback();
203    private final AutoFocusCallback mAutoFocusCallback =
204            new AutoFocusCallback();
205    private final Object mAutoFocusMoveCallback =
206            ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
207                    ? new AutoFocusMoveCallback()
208                    : null;
209
210    private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
211
212    private long mFocusStartTime;
213    private long mShutterCallbackTime;
214    private long mPostViewPictureCallbackTime;
215    private long mRawPictureCallbackTime;
216    private long mJpegPictureCallbackTime;
217    private long mOnResumeTime;
218    private byte[] mJpegImageData;
219
220    // These latency time are for the CameraLatency test.
221    public long mAutoFocusTime;
222    public long mShutterLag;
223    public long mShutterToPictureDisplayedTime;
224    public long mPictureDisplayedToJpegCallbackTime;
225    public long mJpegCallbackFinishTime;
226    public long mCaptureStartTime;
227
228    // This handles everything about focus.
229    private FocusOverlayManager mFocusManager;
230
231    private final int mGcamModeIndex;
232
233    private String mSceneMode;
234
235    private final Handler mHandler = new MainHandler(this);
236
237    private boolean mQuickCapture;
238    private SensorManager mSensorManager;
239    private final float[] mGData = new float[3];
240    private final float[] mMData = new float[3];
241    private final float[] mR = new float[16];
242    private int mHeading = -1;
243
244    /** Whether shutter is enabled. */
245    private boolean mShutterEnabled = true;
246
247    /** True if all the parameters needed to start preview is ready. */
248    private boolean mCameraPreviewParamsReady = false;
249
250    private final MediaSaver.OnMediaSavedListener mOnMediaSavedListener =
251            new MediaSaver.OnMediaSavedListener() {
252                @Override
253                public void onMediaSaved(Uri uri) {
254                    if (uri != null) {
255                        mActivity.notifyNewMedia(uri);
256                    }
257                }
258            };
259
260    private void checkDisplayRotation() {
261        // Set the display orientation if display rotation has changed.
262        // Sometimes this happens when the device is held upside
263        // down and camera app is opened. Rotation animation will
264        // take some time and the rotation value we have got may be
265        // wrong. Framework does not have a callback for this now.
266        if (CameraUtil.getDisplayRotation(mActivity) != mDisplayRotation) {
267            setDisplayOrientation();
268        }
269        if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
270            mHandler.postDelayed(new Runnable() {
271                @Override
272                public void run() {
273                    checkDisplayRotation();
274                }
275            }, 100);
276        }
277    }
278
279    /**
280     * This Handler is used to post message back onto the main thread of the
281     * application
282     */
283    private static class MainHandler extends Handler {
284        private final WeakReference<PhotoModule> mModule;
285
286        public MainHandler(PhotoModule module) {
287            super(Looper.getMainLooper());
288            mModule = new WeakReference<PhotoModule>(module);
289        }
290
291        @Override
292        public void handleMessage(Message msg) {
293            PhotoModule module = mModule.get();
294            if (module == null) {
295                return;
296            }
297            switch (msg.what) {
298                case MSG_FIRST_TIME_INIT: {
299                    module.initializeFirstTime();
300                    break;
301                }
302
303                case MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE: {
304                    module.setCameraParametersWhenIdle(0);
305                    break;
306                }
307
308                case MSG_SHOW_TAP_TO_FOCUS_TOAST: {
309                    module.showTapToFocusToast();
310                    break;
311                }
312
313                case MSG_SWITCH_TO_GCAM_MODULE: {
314                    module.mActivity.onModeSelected(module.mGcamModeIndex);
315                }
316            }
317        }
318    }
319
320    /**
321     * Constructs a new photo module.
322     */
323    public PhotoModule(AppController app) {
324        super(app);
325        mGcamModeIndex = app.getAndroidContext().getResources()
326                .getInteger(R.integer.camera_mode_gcam);
327    }
328
329    @Override
330    public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
331        mActivity = activity;
332        // TODO: Need to look at the controller interface to see if we can get
333        // rid of passing in the activity directly.
334        mAppController = mActivity;
335
336        mUI = new PhotoUI(mActivity, this, mActivity.getModuleLayoutRoot());
337        mActivity.setPreviewStatusListener(mUI);
338        mActivity.getCameraAppUI().setBottomBarShutterListener(this);
339
340        SettingsManager settingsManager = mActivity.getSettingsManager();
341        mCameraId = Integer.parseInt(settingsManager.get(SettingsManager.SETTING_CAMERA_ID));
342
343        mContentResolver = mActivity.getContentResolver();
344
345        // Surface texture is from camera screen nail and startPreview needs it.
346        // This must be done before startPreview.
347        mIsImageCaptureIntent = isImageCaptureIntent();
348
349        mActivity.getCameraProvider().requestCamera(mCameraId);
350
351        mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
352        mLocationManager = mActivity.getLocationManager();
353        mSensorManager = (SensorManager) (mActivity.getSystemService(Context.SENSOR_SERVICE));
354    }
355
356    @Override
357    public boolean isUsingBottomBar() {
358        return true;
359    }
360
361    private void initializeControlByIntent() {
362        if (mIsImageCaptureIntent) {
363            mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
364            setupCaptureParams();
365        }
366    }
367
368    private void onPreviewStarted() {
369        mAppController.onPreviewStarted();
370        setCameraState(IDLE);
371        startFaceDetection();
372        locationFirstRun();
373    }
374
375    @Override
376    public void onPreviewInitialDataReceived() {
377    }
378
379    // Prompt the user to pick to record location for the very first run of
380    // camera only
381    private void locationFirstRun() {
382        SettingsManager settingsManager = mActivity.getSettingsManager();
383
384        // Check whether we can parse the value. If not, let the user choose
385        // again.
386        try {
387            if (settingsManager.isSet(SettingsManager.SETTING_RECORD_LOCATION)) {
388                // This line will throw an exception if the value cannot be
389                // parsed. Otherwise, we will return.
390                settingsManager.getBoolean(SettingsManager.SETTING_RECORD_LOCATION);
391                return;
392            }
393        } catch (Exception ex) {
394            // Fall through and open the dialog.
395        }
396        if (mActivity.isSecureCamera()) {
397            return;
398        }
399        // Check if the back camera exists
400        int backCameraId = mAppController.getCameraProvider().getFirstBackCameraId();
401        if (backCameraId == -1) {
402            // If there is no back camera, do not show the prompt.
403            return;
404        }
405        mUI.showLocationDialog();
406    }
407
408    @Override
409    public void onPreviewUIReady() {
410        startPreview();
411    }
412
413    @Override
414    public void onPreviewUIDestroyed() {
415        if (mCameraDevice == null) {
416            return;
417        }
418        mCameraDevice.setPreviewTexture(null);
419        stopPreview();
420    }
421
422    @Override
423    public void startPreCaptureAnimation() {
424        mAppController.startPreCaptureAnimation();
425    }
426
427    private void onCameraOpened() {
428        openCameraCommon();
429        initializeControlByIntent();
430    }
431
432    private void switchCamera() {
433        if (mPaused) {
434            return;
435        }
436        SettingsManager settingsManager = mActivity.getSettingsManager();
437
438        Log.v(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId);
439        closeCamera();
440        mCameraId = mPendingSwitchCameraId;
441        mPendingSwitchCameraId = -1;
442        settingsManager.set(SettingsManager.SETTING_CAMERA_ID, "" + mCameraId);
443        mActivity.getCameraProvider().requestCamera(mCameraId);
444        mUI.clearFaces();
445        if (mFocusManager != null) {
446            mFocusManager.removeMessages();
447        }
448
449        // TODO: this needs to be brought into onCameraAvailable();
450        CameraInfo info = mActivity.getCameraProvider().getCameraInfo()[mCameraId];
451        mMirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
452        mFocusManager.setMirror(mMirror);
453        // Start switch camera animation. Post a message because
454        // onFrameAvailable from the old camera may already exist.
455    }
456
457    private final ButtonManager.ButtonCallback mCameraCallback =
458            new ButtonManager.ButtonCallback() {
459                @Override
460                public void onStateChanged(int state) {
461                    if (mPaused || mPendingSwitchCameraId != -1) {
462                        return;
463                    }
464                    mPendingSwitchCameraId = state;
465
466                    Log.v(TAG, "Start to switch camera. cameraId=" + state);
467                    // We need to keep a preview frame for the animation before
468                    // releasing the camera. This will trigger
469                    // onPreviewTextureCopied.
470                    // TODO: Need to animate the camera switch
471                    switchCamera();
472                }
473            };
474
475    private final ButtonManager.ButtonCallback mHdrPlusCallback =
476            new ButtonManager.ButtonCallback() {
477                @Override
478                public void onStateChanged(int state) {
479                    if (GcamHelper.hasGcamCapture()) {
480                        // Set the camera setting to default backfacing.
481                        SettingsManager settingsManager = mActivity.getSettingsManager();
482                        settingsManager.setDefault(SettingsManager.SETTING_CAMERA_ID);
483                        mHandler.sendEmptyMessage(MSG_SWITCH_TO_GCAM_MODULE);
484                    } else {
485                        mSceneMode = CameraUtil.SCENE_MODE_HDR;
486                        updateParametersSceneMode();
487                    }
488                }
489            };
490
491    private final View.OnClickListener mCancelCallback = new View.OnClickListener() {
492        @Override
493        public void onClick(View v) {
494            onCaptureCancelled();
495        }
496    };
497
498    private final View.OnClickListener mDoneCallback = new View.OnClickListener() {
499        @Override
500        public void onClick(View v) {
501            onCaptureDone();
502        }
503    };
504
505    private final View.OnClickListener mRetakeCallback = new View.OnClickListener() {
506        @Override
507        public void onClick(View v) {
508            mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
509            onCaptureRetake();
510        }
511    };
512
513    @Override
514    public HardwareSpec getHardwareSpec() {
515        return (mParameters != null ? new HardwareSpecImpl(mParameters) : null);
516    }
517
518    @Override
519    public CameraAppUI.BottomBarUISpec getBottomBarSpec() {
520        CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
521
522        bottomBarSpec.enableCamera = true;
523        bottomBarSpec.cameraCallback = mCameraCallback;
524        bottomBarSpec.enableFlash = true;
525        bottomBarSpec.enableHdr = true;
526        bottomBarSpec.hdrCallback = mHdrPlusCallback;
527        bottomBarSpec.enableGridLines = true;
528
529        if (isImageCaptureIntent()) {
530            bottomBarSpec.showCancel = true;
531            bottomBarSpec.cancelCallback = mCancelCallback;
532            bottomBarSpec.showDone = true;
533            bottomBarSpec.doneCallback = mDoneCallback;
534            bottomBarSpec.showRetake = true;
535            bottomBarSpec.retakeCallback = mRetakeCallback;
536        }
537
538        return bottomBarSpec;
539    }
540
541    // either open a new camera or switch cameras
542    private void openCameraCommon() {
543        mUI.onCameraOpened(mParameters);
544        if (mIsImageCaptureIntent) {
545            // Set hdr plus to default: off.
546            SettingsManager settingsManager = mActivity.getSettingsManager();
547            settingsManager.setDefault(SettingsManager.SETTING_CAMERA_HDR_PLUS);
548        }
549        updateSceneMode();
550    }
551
552    @Override
553    public void updatePreviewAspectRatio(float aspectRatio) {
554        mAppController.updatePreviewAspectRatio(aspectRatio);
555    }
556
557    private void resetExposureCompensation() {
558        SettingsManager settingsManager = mActivity.getSettingsManager();
559        if (settingsManager == null) {
560            Log.e(TAG, "Settings manager is null!");
561            return;
562        }
563        settingsManager.setDefault(SettingsManager.SETTING_EXPOSURE);
564    }
565
566    // Snapshots can only be taken after this is called. It should be called
567    // once only. We could have done these things in onCreate() but we want to
568    // make preview screen appear as soon as possible.
569    private void initializeFirstTime() {
570        if (mFirstTimeInitialized || mPaused) {
571            return;
572        }
573
574        // Initialize location service.
575        mActivity.syncLocationManagerSetting();
576
577        mUI.initializeFirstTime();
578
579        // We set the listener only when both service and shutterbutton
580        // are initialized.
581        getServices().getMemoryManager().addListener(this);
582
583        mNamedImages = new NamedImages();
584
585        mFirstTimeInitialized = true;
586        addIdleHandler();
587
588        mActivity.updateStorageSpaceAndHint();
589    }
590
591    // If the activity is paused and resumed, this method will be called in
592    // onResume.
593    private void initializeSecondTime() {
594        // Start location update if needed.
595        mActivity.syncLocationManagerSetting();
596
597        getServices().getMemoryManager().addListener(this);
598        mNamedImages = new NamedImages();
599        mUI.initializeSecondTime(mParameters);
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    @Override
614    public void startFaceDetection() {
615        if (mFaceDetectionStarted) {
616            return;
617        }
618        if (mParameters.getMaxNumDetectedFaces() > 0) {
619            mFaceDetectionStarted = true;
620            CameraInfo info = mAppController.getCameraProvider().getCameraInfo()[mCameraId];
621            mUI.onStartFaceDetection(mDisplayOrientation,
622                    (info.facing == CameraInfo.CAMERA_FACING_FRONT));
623            mCameraDevice.setFaceDetectionCallback(mHandler, mUI);
624            mCameraDevice.startFaceDetection();
625        }
626    }
627
628    @Override
629    public void stopFaceDetection() {
630        if (!mFaceDetectionStarted) {
631            return;
632        }
633        if (mParameters.getMaxNumDetectedFaces() > 0) {
634            mFaceDetectionStarted = false;
635            mCameraDevice.setFaceDetectionCallback(null, null);
636            mCameraDevice.stopFaceDetection();
637            mUI.clearFaces();
638        }
639    }
640
641    private final class ShutterCallback
642            implements CameraShutterCallback {
643
644        private final boolean mNeedsAnimation;
645
646        public ShutterCallback(boolean needsAnimation) {
647            mNeedsAnimation = needsAnimation;
648        }
649
650        @Override
651        public void onShutter(CameraProxy camera) {
652            mShutterCallbackTime = System.currentTimeMillis();
653            mShutterLag = mShutterCallbackTime - mCaptureStartTime;
654            Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
655            if (mNeedsAnimation) {
656                mActivity.runOnUiThread(new Runnable() {
657                    @Override
658                    public void run() {
659                        animateAfterShutter();
660                    }
661                });
662            }
663        }
664    }
665
666    private final class PostViewPictureCallback
667            implements CameraPictureCallback {
668        @Override
669        public void onPictureTaken(byte[] data, CameraProxy camera) {
670            mPostViewPictureCallbackTime = System.currentTimeMillis();
671            Log.v(TAG, "mShutterToPostViewCallbackTime = "
672                    + (mPostViewPictureCallbackTime - mShutterCallbackTime)
673                    + "ms");
674        }
675    }
676
677    private final class RawPictureCallback
678            implements CameraPictureCallback {
679        @Override
680        public void onPictureTaken(byte[] rawData, CameraProxy camera) {
681            mRawPictureCallbackTime = System.currentTimeMillis();
682            Log.v(TAG, "mShutterToRawCallbackTime = "
683                    + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
684        }
685    }
686
687    private final class JpegPictureCallback
688            implements CameraPictureCallback {
689        Location mLocation;
690
691        public JpegPictureCallback(Location loc) {
692            mLocation = loc;
693        }
694
695        @Override
696        public void onPictureTaken(final byte[] jpegData, CameraProxy camera) {
697            setShutterEnabled(true);
698            if (mPaused) {
699                return;
700            }
701            if (mIsImageCaptureIntent) {
702                stopPreview();
703            }
704            if (mSceneMode == CameraUtil.SCENE_MODE_HDR) {
705                mUI.setSwipingEnabled(true);
706            }
707
708            mJpegPictureCallbackTime = System.currentTimeMillis();
709            // If postview callback has arrived, the captured image is displayed
710            // in postview callback. If not, the captured image is displayed in
711            // raw picture callback.
712            if (mPostViewPictureCallbackTime != 0) {
713                mShutterToPictureDisplayedTime =
714                        mPostViewPictureCallbackTime - mShutterCallbackTime;
715                mPictureDisplayedToJpegCallbackTime =
716                        mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
717            } else {
718                mShutterToPictureDisplayedTime =
719                        mRawPictureCallbackTime - mShutterCallbackTime;
720                mPictureDisplayedToJpegCallbackTime =
721                        mJpegPictureCallbackTime - mRawPictureCallbackTime;
722            }
723            Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
724                    + mPictureDisplayedToJpegCallbackTime + "ms");
725
726            mFocusManager.updateFocusUI(); // Ensure focus indicator is hidden.
727            if (!mIsImageCaptureIntent) {
728                setupPreview();
729            }
730
731            ExifInterface exif = Exif.getExif(jpegData);
732            int orientation = Exif.getOrientation(exif);
733
734            if (!mIsImageCaptureIntent) {
735                // Calculate the width and the height of the jpeg.
736                Size s = mParameters.getPictureSize();
737                int width, height;
738                if ((mJpegRotation + orientation) % 180 == 0) {
739                    width = s.width;
740                    height = s.height;
741                } else {
742                    width = s.height;
743                    height = s.width;
744                }
745                NamedEntity name = mNamedImages.getNextNameEntity();
746                String title = (name == null) ? null : name.title;
747                long date = (name == null) ? -1 : name.date;
748
749                // Handle debug mode outputs
750                if (mDebugUri != null) {
751                    // If using a debug uri, save jpeg there.
752                    saveToDebugUri(jpegData);
753
754                    // Adjust the title of the debug image shown in mediastore.
755                    if (title != null) {
756                        title = DEBUG_IMAGE_PREFIX + title;
757                    }
758                }
759
760                if (title == null) {
761                    Log.e(TAG, "Unbalanced name/data pair");
762                } else {
763                    if (date == -1) {
764                        date = mCaptureStartTime;
765                    }
766                    if (mHeading >= 0) {
767                        // heading direction has been updated by the sensor.
768                        ExifTag directionRefTag = exif.buildTag(
769                                ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
770                                ExifInterface.GpsTrackRef.MAGNETIC_DIRECTION);
771                        ExifTag directionTag = exif.buildTag(
772                                ExifInterface.TAG_GPS_IMG_DIRECTION,
773                                new Rational(mHeading, 1));
774                        exif.setTag(directionRefTag);
775                        exif.setTag(directionTag);
776                    }
777                    getServices().getMediaSaver().addImage(
778                            jpegData, title, date, mLocation, width, height,
779                            orientation, exif, mOnMediaSavedListener, mContentResolver);
780                }
781                // Animate capture with real jpeg data instead of a preview
782                // frame.
783                mUI.animateCapture(jpegData, orientation, mMirror);
784            } else {
785                mJpegImageData = jpegData;
786                if (!mQuickCapture) {
787                    mUI.showCapturedImageForReview(jpegData, orientation, mMirror);
788                } else {
789                    onCaptureDone();
790                }
791            }
792
793            // Check this in advance of each shot so we don't add to shutter
794            // latency. It's true that someone else could write to the SD card
795            // in the mean time and fill it, but that could have happened
796            // between the shutter press and saving the JPEG too.
797            mActivity.updateStorageSpaceAndHint();
798
799            long now = System.currentTimeMillis();
800            mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
801            Log.v(TAG, "mJpegCallbackFinishTime = "
802                    + mJpegCallbackFinishTime + "ms");
803            mJpegPictureCallbackTime = 0;
804        }
805    }
806
807    private final class AutoFocusCallback implements CameraAFCallback {
808        @Override
809        public void onAutoFocus(
810                boolean focused, CameraProxy camera) {
811            if (mPaused) {
812                return;
813            }
814
815            mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;
816            Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms");
817            setCameraState(IDLE);
818            mFocusManager.onAutoFocus(focused, mUI.isShutterPressed());
819        }
820    }
821
822    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
823    private final class AutoFocusMoveCallback
824            implements CameraAFMoveCallback {
825        @Override
826        public void onAutoFocusMoving(
827                boolean moving, CameraProxy camera) {
828            mFocusManager.onAutoFocusMoving(moving);
829        }
830    }
831
832    /**
833     * This class is just a thread-safe queue for name,date holder objects.
834     */
835    public static class NamedImages {
836        private final Vector<NamedEntity> mQueue;
837
838        public NamedImages() {
839            mQueue = new Vector<NamedEntity>();
840        }
841
842        public void nameNewImage(long date) {
843            NamedEntity r = new NamedEntity();
844            r.title = CameraUtil.createJpegName(date);
845            r.date = date;
846            mQueue.add(r);
847        }
848
849        public NamedEntity getNextNameEntity() {
850            synchronized (mQueue) {
851                if (!mQueue.isEmpty()) {
852                    return mQueue.remove(0);
853                }
854            }
855            return null;
856        }
857
858        public static class NamedEntity {
859            public String title;
860            public long date;
861        }
862    }
863
864    private void setCameraState(int state) {
865        mCameraState = state;
866        switch (state) {
867            case PhotoController.PREVIEW_STOPPED:
868            case PhotoController.SNAPSHOT_IN_PROGRESS:
869            case PhotoController.SWITCHING_CAMERA:
870                // TODO: Tell app UI to disable swipe
871                break;
872            case PhotoController.IDLE:
873                // TODO: Tell app UI to enable swipe
874                break;
875        }
876    }
877
878    private void animateAfterShutter() {
879        // Only animate when in full screen capture mode
880        // i.e. If monkey/a user swipes to the gallery during picture taking,
881        // don't show animation
882        if (!mIsImageCaptureIntent) {
883            mUI.animateFlash();
884        }
885    }
886
887    @Override
888    public boolean capture() {
889        // If we are already in the middle of taking a snapshot or the image
890        // save request is full then ignore.
891        if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS
892                || mCameraState == SWITCHING_CAMERA || !mShutterEnabled) {
893            return false;
894        }
895        mCaptureStartTime = System.currentTimeMillis();
896        mPostViewPictureCallbackTime = 0;
897        mJpegImageData = null;
898
899        final boolean animateBefore = (mSceneMode == CameraUtil.SCENE_MODE_HDR);
900
901        if (animateBefore) {
902            animateAfterShutter();
903        }
904
905        // Set rotation and gps data.
906        int orientation;
907
908        // We need to be consistent with the framework orientation (i.e. the
909        // orientation of the UI.) when the auto-rotate screen setting is on.
910        if (mActivity.isAutoRotateScreen()) {
911            orientation = (360 - mDisplayRotation) % 360;
912        } else {
913            orientation = mOrientation;
914        }
915        CameraInfo info = mActivity.getCameraProvider().getCameraInfo()[mCameraId];
916        mJpegRotation = CameraUtil.getJpegRotation(info, orientation);
917        mParameters.setRotation(mJpegRotation);
918        Location loc = mActivity.getLocationManager().getCurrentLocation();
919        CameraUtil.setGpsParameters(mParameters, loc);
920        mCameraDevice.setParameters(mParameters);
921
922        // We don't want user to press the button again while taking a
923        // multi-second HDR photo.
924        setShutterEnabled(false);
925        mCameraDevice.takePicture(mHandler,
926                new ShutterCallback(!animateBefore),
927                mRawPictureCallback, mPostViewPictureCallback,
928                new JpegPictureCallback(loc));
929
930        mNamedImages.nameNewImage(mCaptureStartTime);
931
932        mFaceDetectionStarted = false;
933        setCameraState(SNAPSHOT_IN_PROGRESS);
934        UsageStatistics.captureEvent(eventprotos.NavigationChange.Mode.PHOTO_CAPTURE,
935                UsageStatistics.hashFileName(mNamedImages.mQueue.lastElement().title + ".jpg"),
936                mParameters);
937        return true;
938    }
939
940    @Override
941    public void setFocusParameters() {
942        setCameraParameters(UPDATE_PARAM_PREFERENCE);
943    }
944
945    private void updateSceneMode() {
946        // If scene mode is set, we cannot set flash mode, white balance, and
947        // focus mode, instead, we read it from driver
948        if (!Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
949            overrideCameraSettings(mParameters.getFlashMode(),
950                    mParameters.getWhiteBalance(), mParameters.getFocusMode());
951        }
952    }
953
954    private void overrideCameraSettings(final String flashMode,
955            final String whiteBalance, final String focusMode) {
956        SettingsManager settingsManager = mActivity.getSettingsManager();
957        settingsManager.set(SettingsManager.SETTING_FLASH_MODE, flashMode);
958        settingsManager.set(SettingsManager.SETTING_WHITE_BALANCE, whiteBalance);
959        settingsManager.set(SettingsManager.SETTING_FOCUS_MODE, focusMode);
960    }
961
962    @Override
963    public void onOrientationChanged(int orientation) {
964        // We keep the last known orientation. So if the user first orient
965        // the camera then point the camera to floor or sky, we still have
966        // the correct orientation.
967        if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
968            return;
969        }
970        mOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
971
972        // Show the toast after getting the first orientation changed.
973        if (mHandler.hasMessages(MSG_SHOW_TAP_TO_FOCUS_TOAST)) {
974            mHandler.removeMessages(MSG_SHOW_TAP_TO_FOCUS_TOAST);
975            showTapToFocusToast();
976        }
977    }
978
979    @Override
980    public void onCameraAvailable(CameraProxy cameraProxy) {
981        if (mPaused) {
982            return;
983        }
984        mCameraDevice = cameraProxy;
985
986        resetExposureCompensation();
987        initializeCapabilities();
988
989        // Reset zoom value index.
990        mZoomValue = 0;
991        if (mFocusManager == null) {
992            initializeFocusManager();
993        }
994        mFocusManager.setParameters(mInitialParams);
995
996        // Do camera parameter dependent initialization.
997        mParameters = mCameraDevice.getParameters();
998        setCameraParameters(UPDATE_PARAM_ALL);
999        // Set a listener which updates camera parameters based
1000        // on changed settings.
1001        SettingsManager settingsManager = mActivity.getSettingsManager();
1002        settingsManager.addListener(this);
1003        mCameraPreviewParamsReady = true;
1004
1005        startPreview();
1006
1007        onCameraOpened();
1008    }
1009
1010    @Override
1011    public void onCaptureCancelled() {
1012        mActivity.setResultEx(Activity.RESULT_CANCELED, new Intent());
1013        mActivity.finish();
1014    }
1015
1016    @Override
1017    public void onCaptureRetake() {
1018        if (mPaused) {
1019            return;
1020        }
1021        mUI.hidePostCaptureAlert();
1022        setupPreview();
1023    }
1024
1025    @Override
1026    public void onCaptureDone() {
1027        if (mPaused) {
1028            return;
1029        }
1030
1031        byte[] data = mJpegImageData;
1032
1033        if (mCropValue == null) {
1034            // First handle the no crop case -- just return the value. If the
1035            // caller specifies a "save uri" then write the data to its
1036            // stream. Otherwise, pass back a scaled down version of the bitmap
1037            // directly in the extras.
1038            if (mSaveUri != null) {
1039                OutputStream outputStream = null;
1040                try {
1041                    outputStream = mContentResolver.openOutputStream(mSaveUri);
1042                    outputStream.write(data);
1043                    outputStream.close();
1044
1045                    mActivity.setResultEx(Activity.RESULT_OK);
1046                    mActivity.finish();
1047                } catch (IOException ex) {
1048                    // ignore exception
1049                } finally {
1050                    CameraUtil.closeSilently(outputStream);
1051                }
1052            } else {
1053                ExifInterface exif = Exif.getExif(data);
1054                int orientation = Exif.getOrientation(exif);
1055                Bitmap bitmap = CameraUtil.makeBitmap(data, 50 * 1024);
1056                bitmap = CameraUtil.rotate(bitmap, orientation);
1057                mActivity.setResultEx(Activity.RESULT_OK,
1058                        new Intent("inline-data").putExtra("data", bitmap));
1059                mActivity.finish();
1060            }
1061        } else {
1062            // Save the image to a temp file and invoke the cropper
1063            Uri tempUri = null;
1064            FileOutputStream tempStream = null;
1065            try {
1066                File path = mActivity.getFileStreamPath(sTempCropFilename);
1067                path.delete();
1068                tempStream = mActivity.openFileOutput(sTempCropFilename, 0);
1069                tempStream.write(data);
1070                tempStream.close();
1071                tempUri = Uri.fromFile(path);
1072            } catch (FileNotFoundException ex) {
1073                mActivity.setResultEx(Activity.RESULT_CANCELED);
1074                mActivity.finish();
1075                return;
1076            } catch (IOException ex) {
1077                mActivity.setResultEx(Activity.RESULT_CANCELED);
1078                mActivity.finish();
1079                return;
1080            } finally {
1081                CameraUtil.closeSilently(tempStream);
1082            }
1083
1084            Bundle newExtras = new Bundle();
1085            if (mCropValue.equals("circle")) {
1086                newExtras.putString("circleCrop", "true");
1087            }
1088            if (mSaveUri != null) {
1089                newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
1090            } else {
1091                newExtras.putBoolean(CameraUtil.KEY_RETURN_DATA, true);
1092            }
1093            if (mActivity.isSecureCamera()) {
1094                newExtras.putBoolean(CameraUtil.KEY_SHOW_WHEN_LOCKED, true);
1095            }
1096
1097            // TODO: Share this constant.
1098            final String CROP_ACTION = "com.android.camera.action.CROP";
1099            Intent cropIntent = new Intent(CROP_ACTION);
1100
1101            cropIntent.setData(tempUri);
1102            cropIntent.putExtras(newExtras);
1103
1104            mActivity.startActivityForResult(cropIntent, REQUEST_CROP);
1105        }
1106    }
1107
1108    @Override
1109    public void onShutterButtonFocus(boolean pressed) {
1110        // Do nothing. We don't support half-press to focus anymore.
1111    }
1112
1113    @Override
1114    public void onShutterButtonClick() {
1115        if (mPaused || (mCameraState == SWITCHING_CAMERA)
1116                || (mCameraState == PREVIEW_STOPPED)) {
1117            return;
1118        }
1119
1120        // Do not take the picture if there is not enough storage.
1121        if (mActivity.getStorageSpaceBytes() <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1122            Log.i(TAG, "Not enough space or storage not ready. remaining="
1123                    + mActivity.getStorageSpaceBytes());
1124            return;
1125        }
1126        Log.v(TAG, "onShutterButtonClick: mCameraState=" + mCameraState);
1127
1128        if (mSceneMode == CameraUtil.SCENE_MODE_HDR) {
1129            mUI.setSwipingEnabled(false);
1130        }
1131        // If the user wants to do a snapshot while the previous one is still
1132        // in progress, remember the fact and do it after we finish the previous
1133        // one and re-start the preview. Snapshot in progress also includes the
1134        // state that autofocus is focusing and a picture will be taken when
1135        // focus callback arrives.
1136        if ((mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS)) {
1137            if (!mIsImageCaptureIntent) {
1138                mSnapshotOnIdle = true;
1139            }
1140            return;
1141        }
1142
1143        mSnapshotOnIdle = false;
1144        mFocusManager.focusAndCapture();
1145    }
1146
1147    private void onResumeTasks() {
1148        Log.v(TAG, "Executing onResumeTasks.");
1149        mActivity.getCameraProvider().requestCamera(mCameraId);
1150
1151        mJpegPictureCallbackTime = 0;
1152        mZoomValue = 0;
1153
1154        mOnResumeTime = SystemClock.uptimeMillis();
1155        checkDisplayRotation();
1156
1157        // If first time initialization is not finished, put it in the
1158        // message queue.
1159        if (!mFirstTimeInitialized) {
1160            mHandler.sendEmptyMessage(MSG_FIRST_TIME_INIT);
1161        } else {
1162            initializeSecondTime();
1163        }
1164
1165        UsageStatistics.changeScreen(eventprotos.NavigationChange.Mode.PHOTO_CAPTURE,
1166                eventprotos.CameraEvent.InteractionCause.BUTTON);
1167
1168        Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
1169        if (gsensor != null) {
1170            mSensorManager.registerListener(this, gsensor, SensorManager.SENSOR_DELAY_NORMAL);
1171        }
1172
1173        Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
1174        if (msensor != null) {
1175            mSensorManager.registerListener(this, msensor, SensorManager.SENSOR_DELAY_NORMAL);
1176        }
1177    }
1178
1179    /**
1180     * The focus manager is the first UI related element to get initialized, and
1181     * it requires the RenderOverlay, so initialize it here
1182     */
1183    private void initializeFocusManager() {
1184        // Create FocusManager object. startPreview needs it.
1185        // if mFocusManager not null, reuse it
1186        // otherwise create a new instance
1187        if (mFocusManager != null) {
1188            mFocusManager.removeMessages();
1189        } else {
1190            CameraInfo info = mAppController.getCameraProvider().getCameraInfo()[mCameraId];
1191            mMirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
1192            String[] defaultFocusModes = mActivity.getResources().getStringArray(
1193                    R.array.pref_camera_focusmode_default_array);
1194            mFocusManager = new FocusOverlayManager(mActivity.getSettingsManager(),
1195                    defaultFocusModes,
1196                    mInitialParams, this, mMirror,
1197                    mActivity.getMainLooper(), mUI.getFocusUI());
1198        }
1199        mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1200    }
1201
1202    @Override
1203    public void resume() {
1204        mPaused = false;
1205        if (mFocusManager != null) {
1206            // If camera is not open when resume is called, focus manager will
1207            // not
1208            // be initialized yet, in which case it will start listening to
1209            // preview area size change later in the initialization.
1210            mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1211        }
1212
1213        if (mUI.getPreviewAreaSizeChangedListener() != null) {
1214            mAppController.addPreviewAreaSizeChangedListener(
1215                    mUI.getPreviewAreaSizeChangedListener());
1216        }
1217
1218        // Add delay on resume from lock screen only, in order to to speed up
1219        // the onResume --> onPause --> onResume cycle from lock screen.
1220        // Don't do always because letting go of thread can cause delay.
1221        String action = mActivity.getIntent().getAction();
1222        if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action)
1223                || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)) {
1224            Log.v(TAG, "On resume, from lock screen.");
1225            // Note: onPauseAfterSuper() will delete this runnable, so we will
1226            // at most have 1 copy queued up.
1227            mHandler.postDelayed(new Runnable() {
1228                @Override
1229                public void run() {
1230                    onResumeTasks();
1231                }
1232            }, ON_RESUME_TASKS_DELAY_MSEC);
1233        } else {
1234            Log.v(TAG, "On resume.");
1235            onResumeTasks();
1236        }
1237    }
1238
1239    @Override
1240    public void pause() {
1241        mPaused = true;
1242        Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
1243        if (gsensor != null) {
1244            mSensorManager.unregisterListener(this, gsensor);
1245        }
1246
1247        Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
1248        if (msensor != null) {
1249            mSensorManager.unregisterListener(this, msensor);
1250        }
1251
1252        // Reset the focus first. Camera CTS does not guarantee that
1253        // cancelAutoFocus is allowed after preview stops.
1254        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1255            mCameraDevice.cancelAutoFocus();
1256        }
1257
1258        // If the camera has not been opened asynchronously yet,
1259        // and startPreview hasn't been called, then this is a no-op.
1260        // (e.g. onResume -> onPause -> onResume).
1261        stopPreview();
1262
1263        mNamedImages = null;
1264
1265        if (mLocationManager != null) {
1266            mLocationManager.recordLocation(false);
1267        }
1268
1269        // If we are in an image capture intent and has taken
1270        // a picture, we just clear it in onPause.
1271        mJpegImageData = null;
1272
1273        // Remove the messages and runnables in the queue.
1274        mHandler.removeCallbacksAndMessages(null);
1275
1276        closeCamera();
1277        mActivity.enableKeepScreenOn(false);
1278        mUI.onPause();
1279
1280        mPendingSwitchCameraId = -1;
1281        if (mFocusManager != null) {
1282            mFocusManager.removeMessages();
1283        }
1284        getServices().getMemoryManager().removeListener(this);
1285        mAppController.removePreviewAreaSizeChangedListener(mFocusManager);
1286        if (mUI.getPreviewAreaSizeChangedListener() != null) {
1287            mAppController.removePreviewAreaSizeChangedListener(
1288                    mUI.getPreviewAreaSizeChangedListener());
1289        }
1290
1291        SettingsManager settingsManager = mActivity.getSettingsManager();
1292        settingsManager.removeListener(this);
1293    }
1294
1295    @Override
1296    public void destroy() {
1297        // TODO: implement this.
1298    }
1299
1300    @Override
1301    public void onLayoutOrientationChanged(boolean isLandscape) {
1302        setDisplayOrientation();
1303    }
1304
1305    @Override
1306    public void updateCameraOrientation() {
1307        if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
1308            setDisplayOrientation();
1309        }
1310    }
1311
1312    private boolean canTakePicture() {
1313        return isCameraIdle()
1314                && (mActivity.getStorageSpaceBytes() > Storage.LOW_STORAGE_THRESHOLD_BYTES);
1315    }
1316
1317    @Override
1318    public void autoFocus() {
1319        mFocusStartTime = System.currentTimeMillis();
1320        mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
1321        setCameraState(FOCUSING);
1322    }
1323
1324    @Override
1325    public void cancelAutoFocus() {
1326        mCameraDevice.cancelAutoFocus();
1327        setCameraState(IDLE);
1328        setCameraParameters(UPDATE_PARAM_PREFERENCE);
1329    }
1330
1331    @Override
1332    public void onSingleTapUp(View view, int x, int y) {
1333        if (mPaused || mCameraDevice == null || !mFirstTimeInitialized
1334                || mCameraState == SNAPSHOT_IN_PROGRESS
1335                || mCameraState == SWITCHING_CAMERA
1336                || mCameraState == PREVIEW_STOPPED) {
1337            return;
1338        }
1339
1340        // Check if metering area or focus area is supported.
1341        if (!mFocusAreaSupported && !mMeteringAreaSupported) {
1342            return;
1343        }
1344        mFocusManager.onSingleTapUp(x, y);
1345    }
1346
1347    @Override
1348    public boolean onBackPressed() {
1349        return mUI.onBackPressed();
1350    }
1351
1352    @Override
1353    public boolean onKeyDown(int keyCode, KeyEvent event) {
1354        switch (keyCode) {
1355            case KeyEvent.KEYCODE_VOLUME_UP:
1356            case KeyEvent.KEYCODE_VOLUME_DOWN:
1357            case KeyEvent.KEYCODE_FOCUS:
1358                if (/* TODO: mActivity.isInCameraApp() && */mFirstTimeInitialized) {
1359                    if (event.getRepeatCount() == 0) {
1360                        onShutterButtonFocus(true);
1361                    }
1362                    return true;
1363                }
1364                return false;
1365            case KeyEvent.KEYCODE_CAMERA:
1366                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1367                    onShutterButtonClick();
1368                }
1369                return true;
1370            case KeyEvent.KEYCODE_DPAD_CENTER:
1371                // If we get a dpad center event without any focused view, move
1372                // the focus to the shutter button and press it.
1373                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1374                    // Start auto-focus immediately to reduce shutter lag. After
1375                    // the shutter button gets the focus, onShutterButtonFocus()
1376                    // will be called again but it is fine.
1377                    onShutterButtonFocus(true);
1378                    mUI.pressShutterButton();
1379                }
1380                return true;
1381        }
1382        return false;
1383    }
1384
1385    @Override
1386    public boolean onKeyUp(int keyCode, KeyEvent event) {
1387        switch (keyCode) {
1388            case KeyEvent.KEYCODE_VOLUME_UP:
1389            case KeyEvent.KEYCODE_VOLUME_DOWN:
1390                if (/* mActivity.isInCameraApp() && */mFirstTimeInitialized) {
1391                    onShutterButtonClick();
1392                    return true;
1393                }
1394                return false;
1395            case KeyEvent.KEYCODE_FOCUS:
1396                if (mFirstTimeInitialized) {
1397                    onShutterButtonFocus(false);
1398                }
1399                return true;
1400        }
1401        return false;
1402    }
1403
1404    private void closeCamera() {
1405        if (mCameraDevice != null) {
1406            mCameraDevice.setZoomChangeListener(null);
1407            mCameraDevice.setFaceDetectionCallback(null, null);
1408            mCameraDevice.setErrorCallback(null);
1409
1410            mFaceDetectionStarted = false;
1411            mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
1412            mCameraDevice = null;
1413            setCameraState(PREVIEW_STOPPED);
1414            mFocusManager.onCameraReleased();
1415        }
1416    }
1417
1418    private void setDisplayOrientation() {
1419        mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
1420        mDisplayOrientation = CameraUtil.getDisplayOrientation(mDisplayRotation, mCameraId);
1421        mCameraDisplayOrientation = mDisplayOrientation;
1422        mUI.setDisplayOrientation(mDisplayOrientation);
1423        if (mFocusManager != null) {
1424            mFocusManager.setDisplayOrientation(mDisplayOrientation);
1425        }
1426        // Change the camera display orientation
1427        if (mCameraDevice != null) {
1428            mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
1429        }
1430    }
1431
1432    /** Only called by UI thread. */
1433    private void setupPreview() {
1434        mFocusManager.resetTouchFocus();
1435        startPreview();
1436    }
1437
1438    /**
1439     * Returns whether we can/should start the preview or not.
1440     */
1441    private boolean checkPreviewPreconditions() {
1442        if (mPaused) {
1443            return false;
1444        }
1445
1446        if (mCameraDevice == null) {
1447            Log.w(TAG, "startPreview: camera device not ready yet.");
1448            return false;
1449        }
1450
1451        SurfaceTexture st = mActivity.getCameraAppUI().getSurfaceTexture();
1452        if (st == null) {
1453            Log.w(TAG, "startPreview: surfaceTexture is not ready.");
1454            return false;
1455        }
1456
1457        if (!mCameraPreviewParamsReady) {
1458            Log.w(TAG, "startPreview: parameters for preview is not ready.");
1459            return false;
1460        }
1461        return true;
1462    }
1463
1464    /**
1465     * The start/stop preview should only run on the UI thread.
1466     */
1467    private void startPreview() {
1468        if (!checkPreviewPreconditions()) {
1469            return;
1470        }
1471
1472        mCameraDevice.setErrorCallback(mErrorCallback);
1473        // ICS camera frameworks has a bug. Face detection state is not cleared
1474        // after taking a picture. Stop the preview to work around it. The bug
1475        // was fixed in JB.
1476        if (mCameraState != PREVIEW_STOPPED) {
1477            stopPreview();
1478        }
1479
1480        setDisplayOrientation();
1481
1482        if (!mSnapshotOnIdle) {
1483            // If the focus mode is continuous autofocus, call cancelAutoFocus
1484            // to resume it because it may have been paused by autoFocus call.
1485            String focusMode = mFocusManager.getFocusMode();
1486            if (CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE.equals(focusMode)) {
1487                mCameraDevice.cancelAutoFocus();
1488            }
1489            mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
1490        }
1491        setCameraParameters(UPDATE_PARAM_ALL);
1492        // Let UI set its expected aspect ratio
1493        mCameraDevice.setPreviewTexture(mActivity.getCameraAppUI().getSurfaceTexture());
1494
1495        // This is to notify app controller that preview will start next, so app
1496        // controller can set preview callbacks if needed. This has to happen
1497        // before preview is started as a workaround of the framework bug
1498        // related to
1499        // preview callbacks at b/12591410.
1500        mAppController.onPreviewReadyToStart();
1501        Log.v(TAG, "startPreview");
1502        mCameraDevice.startPreview();
1503
1504        mFocusManager.onPreviewStarted();
1505        onPreviewStarted();
1506
1507        if (mSnapshotOnIdle) {
1508            mHandler.post(mDoSnapRunnable);
1509        }
1510    }
1511
1512    @Override
1513    public void stopPreview() {
1514        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1515            Log.v(TAG, "stopPreview");
1516            mCameraDevice.stopPreview();
1517            mFaceDetectionStarted = false;
1518        }
1519        setCameraState(PREVIEW_STOPPED);
1520        if (mFocusManager != null) {
1521            mFocusManager.onPreviewStopped();
1522        }
1523    }
1524
1525    @Override
1526    public void onSettingChanged(SettingsManager settingsManager, int id) {
1527        switch (id) {
1528            case SettingsManager.SETTING_FLASH_MODE: {
1529                updateParametersFlashMode();
1530                break;
1531            }
1532            default: {
1533                // Do nothing.
1534            }
1535        }
1536
1537        if (mCameraDevice != null) {
1538            mCameraDevice.setParameters(mParameters);
1539        }
1540    }
1541
1542    private void updateCameraParametersInitialize() {
1543        // Reset preview frame rate to the maximum because it may be lowered by
1544        // video camera application.
1545        int[] fpsRange = CameraUtil.getPhotoPreviewFpsRange(mParameters);
1546        if (fpsRange != null && fpsRange.length > 0) {
1547            mParameters.setPreviewFpsRange(
1548                    fpsRange[Parameters.PREVIEW_FPS_MIN_INDEX],
1549                    fpsRange[Parameters.PREVIEW_FPS_MAX_INDEX]);
1550        }
1551
1552        mParameters.set(CameraUtil.RECORDING_HINT, CameraUtil.FALSE);
1553
1554        // Disable video stabilization. Convenience methods not available in API
1555        // level <= 14
1556        String vstabSupported = mParameters.get("video-stabilization-supported");
1557        if ("true".equals(vstabSupported)) {
1558            mParameters.set("video-stabilization", "false");
1559        }
1560    }
1561
1562    private void updateCameraParametersZoom() {
1563        // Set zoom.
1564        if (mParameters.isZoomSupported()) {
1565            mParameters.setZoom(mZoomValue);
1566        }
1567    }
1568
1569    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1570    private void setAutoExposureLockIfSupported() {
1571        if (mAeLockSupported) {
1572            mParameters.setAutoExposureLock(mFocusManager.getAeAwbLock());
1573        }
1574    }
1575
1576    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1577    private void setAutoWhiteBalanceLockIfSupported() {
1578        if (mAwbLockSupported) {
1579            mParameters.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
1580        }
1581    }
1582
1583    private void setFocusAreasIfSupported() {
1584        if (mFocusAreaSupported) {
1585            mParameters.setFocusAreas(mFocusManager.getFocusAreas());
1586        }
1587    }
1588
1589    private void setMeteringAreasIfSupported() {
1590        if (mMeteringAreaSupported) {
1591            mParameters.setMeteringAreas(mFocusManager.getMeteringAreas());
1592        }
1593    }
1594
1595    private void updateCameraParametersPreference() {
1596        setAutoExposureLockIfSupported();
1597        setAutoWhiteBalanceLockIfSupported();
1598        setFocusAreasIfSupported();
1599        setMeteringAreasIfSupported();
1600
1601        // Initialize focus mode.
1602        mFocusManager.overrideFocusMode(null);
1603        mParameters.setFocusMode(mFocusManager.getFocusMode());
1604
1605        // Set picture size.
1606        updateParametersPictureSize();
1607
1608        // Set JPEG quality.
1609        updateParametersPictureQuality();
1610
1611        // For the following settings, we need to check if the settings are
1612        // still supported by latest driver, if not, ignore the settings.
1613
1614        // Set exposure compensation
1615        updateParametersExposureCompensation();
1616
1617        // Set the scene mode: also sets flash and white balance.
1618        updateParametersSceneMode();
1619
1620        if (mContinuousFocusSupported && ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK) {
1621            updateAutoFocusMoveCallback();
1622        }
1623    }
1624
1625    private void updateParametersPictureSize() {
1626        SettingsManager settingsManager = mActivity.getSettingsManager();
1627        String pictureSize = settingsManager.get(SettingsManager.SETTING_PICTURE_SIZE);
1628
1629        List<Size> supported = mParameters.getSupportedPictureSizes();
1630        SettingsUtil.setCameraPictureSize(pictureSize, supported, mParameters);
1631        Size size = mParameters.getPictureSize();
1632
1633        // Set a preview size that is closest to the viewfinder height and has
1634        // the right aspect ratio.
1635        List<Size> sizes = mParameters.getSupportedPreviewSizes();
1636        Size optimalSize = CameraUtil.getOptimalPreviewSize(mActivity, sizes,
1637                (double) size.width / size.height);
1638        Size original = mParameters.getPreviewSize();
1639        if (!original.equals(optimalSize)) {
1640            mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
1641
1642            // Zoom related settings will be changed for different preview
1643            // sizes, so set and read the parameters to get latest values
1644            if (mHandler.getLooper() == Looper.myLooper()) {
1645                // On UI thread only, not when camera starts up
1646                setupPreview();
1647            } else {
1648                mCameraDevice.setParameters(mParameters);
1649            }
1650            mParameters = mCameraDevice.getParameters();
1651        }
1652
1653        if (optimalSize.width != 0 && optimalSize.height != 0) {
1654            mUI.updatePreviewAspectRatio((float) optimalSize.width
1655                    / (float) optimalSize.height);
1656        }
1657        Log.v(TAG, "Preview size is " + optimalSize.width + "x" + optimalSize.height);
1658    }
1659
1660    private void updateParametersPictureQuality() {
1661        int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1662                CameraProfile.QUALITY_HIGH);
1663        mParameters.setJpegQuality(jpegQuality);
1664    }
1665
1666    private void updateParametersExposureCompensation() {
1667        SettingsManager settingsManager = mActivity.getSettingsManager();
1668
1669        int value = Integer.parseInt(settingsManager.get(SettingsManager.SETTING_EXPOSURE));
1670        int max = mParameters.getMaxExposureCompensation();
1671        int min = mParameters.getMinExposureCompensation();
1672        if (value >= min && value <= max) {
1673            mParameters.setExposureCompensation(value);
1674        } else {
1675            Log.w(TAG, "invalid exposure range: " + value);
1676        }
1677    }
1678
1679    private void updateParametersSceneMode() {
1680        SettingsManager settingsManager = mActivity.getSettingsManager();
1681
1682        mSceneMode = settingsManager.get(SettingsManager.SETTING_SCENE_MODE);
1683        if (CameraUtil.isSupported(mSceneMode, mParameters.getSupportedSceneModes())) {
1684            if (!mParameters.getSceneMode().equals(mSceneMode)) {
1685                mParameters.setSceneMode(mSceneMode);
1686
1687                // Setting scene mode will change the settings of flash mode,
1688                // white balance, and focus mode. Here we read back the
1689                // parameters, so we can know those settings.
1690                mCameraDevice.setParameters(mParameters);
1691                mParameters = mCameraDevice.getParameters();
1692            }
1693        } else {
1694            mSceneMode = mParameters.getSceneMode();
1695            if (mSceneMode == null) {
1696                mSceneMode = Parameters.SCENE_MODE_AUTO;
1697            }
1698        }
1699
1700        if (Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
1701            // Set flash mode.
1702            updateParametersFlashMode();
1703
1704            // Set white balance mode.
1705            updateParametersWhiteBalanceMode();
1706
1707            // Set focus mode.
1708            mFocusManager.overrideFocusMode(null);
1709            mParameters.setFocusMode(mFocusManager.getFocusMode());
1710        } else {
1711            mFocusManager.overrideFocusMode(mParameters.getFocusMode());
1712        }
1713    }
1714
1715    private void updateParametersFlashMode() {
1716        SettingsManager settingsManager = mActivity.getSettingsManager();
1717
1718        String flashMode = settingsManager.get(SettingsManager.SETTING_FLASH_MODE);
1719        List<String> supportedFlash = mParameters.getSupportedFlashModes();
1720        if (CameraUtil.isSupported(flashMode, supportedFlash)) {
1721            mParameters.setFlashMode(flashMode);
1722        }
1723    }
1724
1725    private void updateParametersWhiteBalanceMode() {
1726        SettingsManager settingsManager = mActivity.getSettingsManager();
1727
1728        // Set white balance parameter.
1729        String whiteBalance = settingsManager.get(SettingsManager.SETTING_WHITE_BALANCE);
1730        if (CameraUtil.isSupported(whiteBalance,
1731                mParameters.getSupportedWhiteBalance())) {
1732            mParameters.setWhiteBalance(whiteBalance);
1733        }
1734    }
1735
1736    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1737    private void updateAutoFocusMoveCallback() {
1738        if (mParameters.getFocusMode().equals(CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE)) {
1739            mCameraDevice.setAutoFocusMoveCallback(mHandler,
1740                    (CameraAFMoveCallback) mAutoFocusMoveCallback);
1741        } else {
1742            mCameraDevice.setAutoFocusMoveCallback(null, null);
1743        }
1744    }
1745
1746    // We separate the parameters into several subsets, so we can update only
1747    // the subsets actually need updating. The PREFERENCE set needs extra
1748    // locking because the preference can be changed from GLThread as well.
1749    private void setCameraParameters(int updateSet) {
1750        if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
1751            updateCameraParametersInitialize();
1752        }
1753
1754        if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
1755            updateCameraParametersZoom();
1756        }
1757
1758        if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
1759            updateCameraParametersPreference();
1760        }
1761
1762        mCameraDevice.setParameters(mParameters);
1763    }
1764
1765    // If the Camera is idle, update the parameters immediately, otherwise
1766    // accumulate them in mUpdateSet and update later.
1767    private void setCameraParametersWhenIdle(int additionalUpdateSet) {
1768        mUpdateSet |= additionalUpdateSet;
1769        if (mCameraDevice == null) {
1770            // We will update all the parameters when we open the device, so
1771            // we don't need to do anything now.
1772            mUpdateSet = 0;
1773            return;
1774        } else if (isCameraIdle()) {
1775            setCameraParameters(mUpdateSet);
1776            updateSceneMode();
1777            mUpdateSet = 0;
1778        } else {
1779            if (!mHandler.hasMessages(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
1780                mHandler.sendEmptyMessageDelayed(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
1781            }
1782        }
1783    }
1784
1785    @Override
1786    public boolean isCameraIdle() {
1787        return (mCameraState == IDLE) ||
1788                (mCameraState == PREVIEW_STOPPED) ||
1789                ((mFocusManager != null) && mFocusManager.isFocusCompleted()
1790                && (mCameraState != SWITCHING_CAMERA));
1791    }
1792
1793    @Override
1794    public boolean isImageCaptureIntent() {
1795        String action = mActivity.getIntent().getAction();
1796        return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
1797        || CameraActivity.ACTION_IMAGE_CAPTURE_SECURE.equals(action));
1798    }
1799
1800    private void setupCaptureParams() {
1801        Bundle myExtras = mActivity.getIntent().getExtras();
1802        if (myExtras != null) {
1803            mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1804            mCropValue = myExtras.getString("crop");
1805        }
1806    }
1807
1808    private void showTapToFocusToast() {
1809        // TODO: Use a toast?
1810        new RotateTextToast(mActivity, R.string.tap_to_focus, 0).show();
1811        // Clear the preference.
1812        SettingsManager settingsManager = mActivity.getSettingsManager();
1813        settingsManager.setBoolean(
1814                SettingsManager.SETTING_CAMERA_FIRST_USE_HINT_SHOWN, false);
1815    }
1816
1817    private void initializeCapabilities() {
1818        mInitialParams = mCameraDevice.getParameters();
1819        mFocusAreaSupported = CameraUtil.isFocusAreaSupported(mInitialParams);
1820        mMeteringAreaSupported = CameraUtil.isMeteringAreaSupported(mInitialParams);
1821        mAeLockSupported = CameraUtil.isAutoExposureLockSupported(mInitialParams);
1822        mAwbLockSupported = CameraUtil.isAutoWhiteBalanceLockSupported(mInitialParams);
1823        mContinuousFocusSupported = mInitialParams.getSupportedFocusModes().contains(
1824                CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE);
1825    }
1826
1827    private void setShutterEnabled(boolean enabled) {
1828        mShutterEnabled = enabled;
1829        mUI.enableShutter(enabled);
1830    }
1831
1832    // TODO: Remove this
1833    @Override
1834    public int onZoomChanged(int index) {
1835        // Not useful to change zoom value when the activity is paused.
1836        if (mPaused) {
1837            return index;
1838        }
1839        mZoomValue = index;
1840        if (mParameters == null || mCameraDevice == null) {
1841            return index;
1842        }
1843        // Set zoom parameters asynchronously
1844        mParameters.setZoom(mZoomValue);
1845        mCameraDevice.setParameters(mParameters);
1846        Parameters p = mCameraDevice.getParameters();
1847        if (p != null) {
1848            return p.getZoom();
1849        }
1850        return index;
1851    }
1852
1853    @Override
1854    public int getCameraState() {
1855        return mCameraState;
1856    }
1857
1858    @Override
1859    public void onMemoryStateChanged(int state) {
1860        setShutterEnabled(state == MemoryManager.STATE_OK);
1861    }
1862
1863    @Override
1864    public void onLowMemory() {
1865        // Not much we can do in the photo module.
1866    }
1867
1868    @Override
1869    public void onAccuracyChanged(Sensor sensor, int accuracy) {
1870    }
1871
1872    @Override
1873    public void onSensorChanged(SensorEvent event) {
1874        int type = event.sensor.getType();
1875        float[] data;
1876        if (type == Sensor.TYPE_ACCELEROMETER) {
1877            data = mGData;
1878        } else if (type == Sensor.TYPE_MAGNETIC_FIELD) {
1879            data = mMData;
1880        } else {
1881            // we should not be here.
1882            return;
1883        }
1884        for (int i = 0; i < 3; i++) {
1885            data[i] = event.values[i];
1886        }
1887        float[] orientation = new float[3];
1888        SensorManager.getRotationMatrix(mR, null, mGData, mMData);
1889        SensorManager.getOrientation(mR, orientation);
1890        mHeading = (int) (orientation[0] * 180f / Math.PI) % 360;
1891        if (mHeading < 0) {
1892            mHeading += 360;
1893        }
1894    }
1895
1896    // For debugging only.
1897    public void setDebugUri(Uri uri) {
1898        mDebugUri = uri;
1899    }
1900
1901    // For debugging only.
1902    private void saveToDebugUri(byte[] data) {
1903        if (mDebugUri != null) {
1904            OutputStream outputStream = null;
1905            try {
1906                outputStream = mContentResolver.openOutputStream(mDebugUri);
1907                outputStream.write(data);
1908                outputStream.close();
1909            } catch (IOException e) {
1910                Log.e(TAG, "Exception while writing debug jpeg file", e);
1911            } finally {
1912                CameraUtil.closeSilently(outputStream);
1913            }
1914        }
1915    }
1916}
1917