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