Camera.java revision c5bc067250fad1a246ea22604260d01aab627ba4
1/*
2 * Copyright (C) 2007 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 com.android.camera.ui.CameraPicker;
20import com.android.camera.ui.FaceView;
21import com.android.camera.ui.IndicatorControlContainer;
22import com.android.camera.ui.RotateImageView;
23import com.android.camera.ui.RotateLayout;
24import com.android.camera.ui.SharePopup;
25import com.android.camera.ui.ZoomControl;
26
27import android.app.Activity;
28import android.content.BroadcastReceiver;
29import android.content.ContentProviderClient;
30import android.content.ContentResolver;
31import android.content.Context;
32import android.content.Intent;
33import android.content.IntentFilter;
34import android.content.SharedPreferences.Editor;
35import android.graphics.Bitmap;
36import android.hardware.Camera.CameraInfo;
37import android.hardware.Camera.Face;
38import android.hardware.Camera.FaceDetectionListener;
39import android.hardware.Camera.Parameters;
40import android.hardware.Camera.PictureCallback;
41import android.hardware.Camera.Size;
42import android.location.Location;
43import android.media.CameraProfile;
44import android.net.Uri;
45import android.os.Bundle;
46import android.os.Handler;
47import android.os.Looper;
48import android.os.Message;
49import android.os.MessageQueue;
50import android.os.SystemClock;
51import android.provider.MediaStore;
52import android.util.Log;
53import android.view.GestureDetector;
54import android.view.Gravity;
55import android.view.KeyEvent;
56import android.view.Menu;
57import android.view.MenuItem;
58import android.view.MenuItem.OnMenuItemClickListener;
59import android.view.MotionEvent;
60import android.view.OrientationEventListener;
61import android.view.SurfaceHolder;
62import android.view.SurfaceView;
63import android.view.View;
64import android.view.WindowManager;
65import android.view.animation.AnimationUtils;
66import android.widget.TextView;
67import android.widget.Toast;
68
69import java.io.File;
70import java.io.FileNotFoundException;
71import java.io.FileOutputStream;
72import java.io.IOException;
73import java.io.OutputStream;
74import java.util.ArrayList;
75import java.util.Collections;
76import java.util.Formatter;
77import java.util.List;
78
79/** The Camera activity which can preview and take pictures. */
80public class Camera extends ActivityBase implements FocusManager.Listener,
81        View.OnTouchListener, ShutterButton.OnShutterButtonListener,
82        SurfaceHolder.Callback, ModePicker.OnModeChangeListener,
83        FaceDetectionListener, CameraPreference.OnPreferenceChangedListener,
84        LocationManager.Listener {
85
86    private static final String TAG = "camera";
87
88    private static final int CROP_MSG = 1;
89    private static final int FIRST_TIME_INIT = 2;
90    private static final int CLEAR_SCREEN_DELAY = 3;
91    private static final int SET_CAMERA_PARAMETERS_WHEN_IDLE = 4;
92    private static final int CHECK_DISPLAY_ROTATION = 5;
93    private static final int SHOW_TAP_TO_FOCUS_TOAST = 6;
94    private static final int DISMISS_TAP_TO_FOCUS_TOAST = 7;
95    private static final int UPDATE_THUMBNAIL = 8;
96
97    // The subset of parameters we need to update in setCameraParameters().
98    private static final int UPDATE_PARAM_INITIALIZE = 1;
99    private static final int UPDATE_PARAM_ZOOM = 2;
100    private static final int UPDATE_PARAM_PREFERENCE = 4;
101    private static final int UPDATE_PARAM_ALL = -1;
102
103    // When setCameraParametersWhenIdle() is called, we accumulate the subsets
104    // needed to be updated in mUpdateSet.
105    private int mUpdateSet;
106
107    private static final int SCREEN_DELAY = 2 * 60 * 1000;
108
109    private static final int ZOOM_STOPPED = 0;
110    private static final int ZOOM_START = 1;
111    private static final int ZOOM_STOPPING = 2;
112
113    private int mZoomState = ZOOM_STOPPED;
114    private boolean mSmoothZoomSupported = false;
115    private int mZoomValue;  // The current zoom value.
116    private int mZoomMax;
117    private int mTargetZoomValue;
118    private ZoomControl mZoomControl;
119
120    private Parameters mParameters;
121    private Parameters mInitialParams;
122    private boolean mFocusAreaSupported;
123    private boolean mMeteringAreaSupported;
124
125    private MyOrientationEventListener mOrientationListener;
126    // The degrees of the device rotated clockwise from its natural orientation.
127    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
128    // The orientation compensation for icons and thumbnails. Ex: if the value
129    // is 90, the UI components should be rotated 90 degrees counter-clockwise.
130    private int mOrientationCompensation = 0;
131    private ComboPreferences mPreferences;
132
133    private static final String sTempCropFilename = "crop-temp";
134
135    private android.hardware.Camera mCameraDevice;
136    private ContentProviderClient mMediaProviderClient;
137    private SurfaceHolder mSurfaceHolder = null;
138    private ShutterButton mShutterButton;
139    private GestureDetector mPopupGestureDetector;
140    private boolean mOpenCameraFail = false;
141    private boolean mCameraDisabled = false;
142
143    private View mPreviewPanel;  // The container of PreviewFrameLayout.
144    private PreviewFrameLayout mPreviewFrameLayout;
145    private View mPreviewFrame;  // Preview frame area.
146
147    // A popup window that contains a bigger thumbnail and a list of apps to share.
148    private SharePopup mSharePopup;
149    // The bitmap of the last captured picture thumbnail and the URI of the
150    // original picture.
151    private Thumbnail mThumbnail;
152    // An imageview showing showing the last captured picture thumbnail.
153    private RotateImageView mThumbnailView;
154    private ModePicker mModePicker;
155    private FaceView mFaceView;
156    private RotateLayout mFocusIndicator;
157
158    // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
159    private String mCropValue;
160    private Uri mSaveUri;
161
162    // On-screen indicator
163    private View mGpsNoSignalIndicator;
164    private View mGpsHasSignalIndicator;
165    private TextView mExposureIndicator;
166
167    // We use a thread in ImageSaver to do the work of saving images and
168    // generating thumbnails. This reduces the shot-to-shot time.
169    private ImageSaver mImageSaver;
170
171    private final StringBuilder mBuilder = new StringBuilder();
172    private final Formatter mFormatter = new Formatter(mBuilder);
173    private final Object[] mFormatterArgs = new Object[1];
174
175    /**
176     * An unpublished intent flag requesting to return as soon as capturing
177     * is completed.
178     *
179     * TODO: consider publishing by moving into MediaStore.
180     */
181    private final static String EXTRA_QUICK_CAPTURE =
182            "android.intent.extra.quickCapture";
183
184    // The display rotation in degrees. This is only valid when mCameraState is
185    // not PREVIEW_STOPPED.
186    private int mDisplayRotation;
187    // The value for android.hardware.Camera.setDisplayOrientation.
188    private int mDisplayOrientation;
189    private boolean mPausing;
190    private boolean mFirstTimeInitialized;
191    private boolean mIsImageCaptureIntent;
192
193    private static final int PREVIEW_STOPPED = 0;
194    private static final int IDLE = 1;  // preview is active
195    // Focus is in progress. The exact focus state is in Focus.java.
196    private static final int FOCUSING = 2;
197    private static final int SNAPSHOT_IN_PROGRESS = 3;
198    private int mCameraState = PREVIEW_STOPPED;
199
200    private ContentResolver mContentResolver;
201    private boolean mDidRegister = false;
202
203    private LocationManager mLocationManager;
204
205    private final ShutterCallback mShutterCallback = new ShutterCallback();
206    private final PostViewPictureCallback mPostViewPictureCallback =
207            new PostViewPictureCallback();
208    private final RawPictureCallback mRawPictureCallback =
209            new RawPictureCallback();
210    private final AutoFocusCallback mAutoFocusCallback =
211            new AutoFocusCallback();
212    private final ZoomListener mZoomListener = new ZoomListener();
213    private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
214
215    private long mFocusStartTime;
216    private long mCaptureStartTime;
217    private long mShutterCallbackTime;
218    private long mPostViewPictureCallbackTime;
219    private long mRawPictureCallbackTime;
220    private long mJpegPictureCallbackTime;
221    private long mOnResumeTime;
222    private long mPicturesRemaining;
223    private byte[] mJpegImageData;
224
225    // These latency time are for the CameraLatency test.
226    public long mAutoFocusTime;
227    public long mShutterLag;
228    public long mShutterToPictureDisplayedTime;
229    public long mPictureDisplayedToJpegCallbackTime;
230    public long mJpegCallbackFinishTime;
231
232    // This handles everything about focus.
233    private FocusManager mFocusManager;
234    private String mSceneMode;
235    private Toast mNotSelectableToast;
236    private Toast mNoShareToast;
237
238    private final Handler mHandler = new MainHandler();
239    private IndicatorControlContainer mIndicatorControlContainer;
240    private PreferenceGroup mPreferenceGroup;
241
242    // multiple cameras support
243    private int mNumberOfCameras;
244    private int mCameraId;
245    private int mFrontCameraId;
246    private int mBackCameraId;
247
248    private boolean mQuickCapture;
249
250    /**
251     * This Handler is used to post message back onto the main thread of the
252     * application
253     */
254    private class MainHandler extends Handler {
255        @Override
256        public void handleMessage(Message msg) {
257            switch (msg.what) {
258                case CLEAR_SCREEN_DELAY: {
259                    getWindow().clearFlags(
260                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
261                    break;
262                }
263
264                case FIRST_TIME_INIT: {
265                    initializeFirstTime();
266                    break;
267                }
268
269                case SET_CAMERA_PARAMETERS_WHEN_IDLE: {
270                    setCameraParametersWhenIdle(0);
271                    break;
272                }
273
274                case CHECK_DISPLAY_ROTATION: {
275                    // Set the display orientation if display rotation has changed.
276                    // Sometimes this happens when the device is held upside
277                    // down and camera app is opened. Rotation animation will
278                    // take some time and the rotation value we have got may be
279                    // wrong. Framework does not have a callback for this now.
280                    if (Util.getDisplayRotation(Camera.this) != mDisplayRotation) {
281                        setDisplayOrientation();
282                    }
283                    if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
284                        mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
285                    }
286                    break;
287                }
288
289                case SHOW_TAP_TO_FOCUS_TOAST: {
290                    showTapToFocusToast();
291                    break;
292                }
293
294                case DISMISS_TAP_TO_FOCUS_TOAST: {
295                    View v = findViewById(R.id.tap_to_focus_prompt);
296                    v.setVisibility(View.GONE);
297                    v.setAnimation(AnimationUtils.loadAnimation(Camera.this,
298                            R.anim.on_screen_hint_exit));
299                    break;
300                }
301
302                case UPDATE_THUMBNAIL: {
303                    mImageSaver.updateThumbnail();
304                    break;
305                }
306            }
307        }
308    }
309
310    private void resetExposureCompensation() {
311        String value = mPreferences.getString(CameraSettings.KEY_EXPOSURE,
312                CameraSettings.EXPOSURE_DEFAULT_VALUE);
313        if (!CameraSettings.EXPOSURE_DEFAULT_VALUE.equals(value)) {
314            Editor editor = mPreferences.edit();
315            editor.putString(CameraSettings.KEY_EXPOSURE, "0");
316            editor.apply();
317            if (mIndicatorControlContainer != null) {
318                mIndicatorControlContainer.reloadPreferences();
319            }
320        }
321    }
322
323    private void keepMediaProviderInstance() {
324        // We want to keep a reference to MediaProvider in camera's lifecycle.
325        // TODO: Utilize mMediaProviderClient instance to replace
326        // ContentResolver calls.
327        if (mMediaProviderClient == null) {
328            mMediaProviderClient = getContentResolver()
329                    .acquireContentProviderClient(MediaStore.AUTHORITY);
330        }
331    }
332
333    // Snapshots can only be taken after this is called. It should be called
334    // once only. We could have done these things in onCreate() but we want to
335    // make preview screen appear as soon as possible.
336    private void initializeFirstTime() {
337        if (mFirstTimeInitialized) return;
338
339        // Create orientation listenter. This should be done first because it
340        // takes some time to get first orientation.
341        mOrientationListener = new MyOrientationEventListener(Camera.this);
342        mOrientationListener.enable();
343
344        // Initialize location sevice.
345        boolean recordLocation = RecordLocationPreference.get(
346                mPreferences, getContentResolver());
347        initOnScreenIndicator();
348        mLocationManager.recordLocation(recordLocation);
349
350        keepMediaProviderInstance();
351        checkStorage();
352
353        // Initialize last picture button.
354        mContentResolver = getContentResolver();
355        if (!mIsImageCaptureIntent) {  // no thumbnail in image capture intent
356            initThumbnailButton();
357        }
358
359        // Initialize shutter button.
360        mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
361        mShutterButton.setOnShutterButtonListener(this);
362        mShutterButton.setVisibility(View.VISIBLE);
363
364        // Initialize focus UI.
365        mPreviewFrame = findViewById(R.id.camera_preview);
366        mPreviewFrame.setOnTouchListener(this);
367        mFocusIndicator = (RotateLayout) findViewById(R.id.focus_indicator_rotate_layout);
368        mFocusManager.initialize(mFocusIndicator, mPreviewFrame, mFaceView, this);
369        mFocusManager.initializeSoundPlayer(getResources().openRawResourceFd(R.raw.camera_focus));
370        mImageSaver = new ImageSaver();
371        Util.initializeScreenBrightness(getWindow(), getContentResolver());
372        installIntentFilter();
373        initializeZoom();
374        // Show the tap to focus toast if this is the first start.
375        if (mFocusAreaSupported &&
376                mPreferences.getBoolean(CameraSettings.KEY_TAP_TO_FOCUS_PROMPT_SHOWN, true)) {
377            // Delay the toast for one second to wait for orientation.
378            mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_FOCUS_TOAST, 1000);
379        }
380
381        mFirstTimeInitialized = true;
382        addIdleHandler();
383    }
384
385    private void addIdleHandler() {
386        MessageQueue queue = Looper.myQueue();
387        queue.addIdleHandler(new MessageQueue.IdleHandler() {
388            public boolean queueIdle() {
389                Storage.ensureOSXCompatible();
390                return false;
391            }
392        });
393    }
394
395    private void initThumbnailButton() {
396        // Load the thumbnail from the disk.
397        mThumbnail = Thumbnail.loadFrom(new File(getFilesDir(), Thumbnail.LAST_THUMB_FILENAME));
398        updateThumbnailButton();
399    }
400
401    private void updateThumbnailButton() {
402        // Update last image if URI is invalid and the storage is ready.
403        if ((mThumbnail == null || !Util.isUriValid(mThumbnail.getUri(), mContentResolver))
404                && mPicturesRemaining >= 0) {
405            mThumbnail = Thumbnail.getLastThumbnail(mContentResolver);
406        }
407        if (mThumbnail != null) {
408            mThumbnailView.setBitmap(mThumbnail.getBitmap());
409        } else {
410            mThumbnailView.setBitmap(null);
411        }
412    }
413
414    // If the activity is paused and resumed, this method will be called in
415    // onResume.
416    private void initializeSecondTime() {
417        // Start orientation listener as soon as possible because it takes
418        // some time to get first orientation.
419        mOrientationListener.enable();
420
421        // Start location update if needed.
422        boolean recordLocation = RecordLocationPreference.get(
423                mPreferences, getContentResolver());
424        mLocationManager.recordLocation(recordLocation);
425
426        installIntentFilter();
427        mFocusManager.initializeSoundPlayer(getResources().openRawResourceFd(R.raw.camera_focus));
428        mImageSaver = new ImageSaver();
429        initializeZoom();
430        keepMediaProviderInstance();
431        checkStorage();
432        hidePostCaptureAlert();
433
434        if (!mIsImageCaptureIntent) {
435            updateThumbnailButton();
436            mModePicker.setCurrentMode(ModePicker.MODE_CAMERA);
437        }
438    }
439
440    private class ZoomChangeListener implements ZoomControl.OnZoomChangedListener {
441        // only for immediate zoom
442        @Override
443        public void onZoomValueChanged(int index) {
444            Camera.this.onZoomValueChanged(index);
445        }
446
447        // only for smooth zoom
448        @Override
449        public void onZoomStateChanged(int state) {
450            if (mPausing) return;
451
452            Log.v(TAG, "zoom picker state=" + state);
453            if (state == ZoomControl.ZOOM_IN) {
454                Camera.this.onZoomValueChanged(mZoomMax);
455            } else if (state == ZoomControl.ZOOM_OUT) {
456                Camera.this.onZoomValueChanged(0);
457            } else {
458                mTargetZoomValue = -1;
459                if (mZoomState == ZOOM_START) {
460                    mZoomState = ZOOM_STOPPING;
461                    mCameraDevice.stopSmoothZoom();
462                }
463            }
464        }
465    }
466
467    private void initializeZoom() {
468        // Get the parameter to make sure we have the up-to-date zoom value.
469        mParameters = mCameraDevice.getParameters();
470        if (!mParameters.isZoomSupported()) return;
471        mZoomMax = mParameters.getMaxZoom();
472        // Currently we use immediate zoom for fast zooming to get better UX and
473        // there is no plan to take advantage of the smooth zoom.
474        mZoomControl.setZoomMax(mZoomMax);
475        mZoomControl.setZoomIndex(mParameters.getZoom());
476        mZoomControl.setSmoothZoomSupported(mSmoothZoomSupported);
477        mZoomControl.setOnZoomChangeListener(new ZoomChangeListener());
478        mCameraDevice.setZoomChangeListener(mZoomListener);
479    }
480
481    private void onZoomValueChanged(int index) {
482        // Not useful to change zoom value when the activity is paused.
483        if (mPausing) return;
484
485        if (mSmoothZoomSupported) {
486            if (mTargetZoomValue != index && mZoomState != ZOOM_STOPPED) {
487                mTargetZoomValue = index;
488                if (mZoomState == ZOOM_START) {
489                    mZoomState = ZOOM_STOPPING;
490                    mCameraDevice.stopSmoothZoom();
491                }
492            } else if (mZoomState == ZOOM_STOPPED && mZoomValue != index) {
493                mTargetZoomValue = index;
494                mCameraDevice.startSmoothZoom(index);
495                mZoomState = ZOOM_START;
496            }
497        } else {
498            mZoomValue = index;
499            setCameraParametersWhenIdle(UPDATE_PARAM_ZOOM);
500        }
501    }
502
503    @Override
504    public void startFaceDetection() {
505        if (mParameters.getMaxNumDetectedFaces() > 0) {
506            mFaceView = (FaceView) findViewById(R.id.face_view);
507            mFaceView.clear();
508            mFaceView.setVisibility(View.VISIBLE);
509            mFaceView.setDisplayOrientation(mDisplayOrientation);
510            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
511            mFaceView.setMirror(info.facing == CameraInfo.CAMERA_FACING_FRONT);
512            mFaceView.resume();
513            mCameraDevice.setFaceDetectionListener(this);
514            mCameraDevice.startFaceDetection();
515        }
516    }
517
518    @Override
519    public void stopFaceDetection() {
520        if (mParameters.getMaxNumDetectedFaces() > 0) {
521            mCameraDevice.setFaceDetectionListener(null);
522            mCameraDevice.stopFaceDetection();
523            if (mFaceView != null) mFaceView.clear();
524        }
525    }
526
527    private class PopupGestureListener
528            extends GestureDetector.SimpleOnGestureListener {
529        @Override
530        public boolean onDown(MotionEvent e) {
531            // Check if the popup window is visible.
532            View popup = mIndicatorControlContainer.getActiveSettingPopup();
533            if (popup == null) return false;
534
535
536            // Let popup window, indicator control or preview frame handle the
537            // event by themselves. Dismiss the popup window if users touch on
538            // other areas.
539            if (!Util.pointInView(e.getX(), e.getY(), popup)
540                    && !Util.pointInView(e.getX(), e.getY(), mIndicatorControlContainer)
541                    && !Util.pointInView(e.getX(), e.getY(), mPreviewFrame)) {
542                mIndicatorControlContainer.dismissSettingPopup();
543                // Let event fall through.
544            }
545            return false;
546        }
547    }
548
549    @Override
550    public boolean dispatchTouchEvent(MotionEvent m) {
551        // Check if the popup window should be dismissed first.
552        if (mPopupGestureDetector != null && mPopupGestureDetector.onTouchEvent(m)) {
553            return true;
554        }
555
556        return super.dispatchTouchEvent(m);
557    }
558
559    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
560        @Override
561        public void onReceive(Context context, Intent intent) {
562            String action = intent.getAction();
563            Log.d(TAG, "Received intent action=" + action);
564            if (action.equals(Intent.ACTION_MEDIA_MOUNTED)
565                    || action.equals(Intent.ACTION_MEDIA_UNMOUNTED)
566                    || action.equals(Intent.ACTION_MEDIA_CHECKING)) {
567                checkStorage();
568            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
569                checkStorage();
570                if (!mIsImageCaptureIntent) {
571                    updateThumbnailButton();
572                }
573            }
574        }
575    };
576
577    private void initOnScreenIndicator() {
578        mGpsNoSignalIndicator = findViewById(R.id.onscreen_gps_indicator_no_signal);
579        mGpsHasSignalIndicator = findViewById(R.id.onscreen_gps_indicator_on);
580        mExposureIndicator = (TextView) findViewById(R.id.onscreen_exposure_indicator);
581    }
582
583    @Override
584    public void showGpsOnScreenIndicator(boolean hasSignal) {
585        if (hasSignal) {
586            if (mGpsNoSignalIndicator != null) {
587                mGpsNoSignalIndicator.setVisibility(View.GONE);
588            }
589            if (mGpsHasSignalIndicator != null) {
590                mGpsHasSignalIndicator.setVisibility(View.VISIBLE);
591            }
592        } else {
593            if (mGpsNoSignalIndicator != null) {
594                mGpsNoSignalIndicator.setVisibility(View.VISIBLE);
595            }
596            if (mGpsHasSignalIndicator != null) {
597                mGpsHasSignalIndicator.setVisibility(View.GONE);
598            }
599        }
600    }
601
602    @Override
603    public void hideGpsOnScreenIndicator() {
604        if (mGpsNoSignalIndicator != null) mGpsNoSignalIndicator.setVisibility(View.GONE);
605        if (mGpsHasSignalIndicator != null) mGpsHasSignalIndicator.setVisibility(View.GONE);
606    }
607
608    private void updateExposureOnScreenIndicator(int value) {
609        if (mExposureIndicator == null) return;
610
611        if (value == 0) {
612            mExposureIndicator.setText("");
613            mExposureIndicator.setVisibility(View.GONE);
614        } else {
615            float step = mParameters.getExposureCompensationStep();
616            mFormatterArgs[0] = value * step;
617            mBuilder.delete(0, mBuilder.length());
618            mFormatter.format("%+1.1f", mFormatterArgs);
619            String exposure = mFormatter.toString();
620            mExposureIndicator.setText(exposure);
621            mExposureIndicator.setVisibility(View.VISIBLE);
622        }
623    }
624
625    private final class ShutterCallback
626            implements android.hardware.Camera.ShutterCallback {
627        public void onShutter() {
628            mShutterCallbackTime = System.currentTimeMillis();
629            mShutterLag = mShutterCallbackTime - mCaptureStartTime;
630            Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
631            mFocusManager.onShutter();
632        }
633    }
634
635    private final class PostViewPictureCallback implements PictureCallback {
636        public void onPictureTaken(
637                byte [] data, android.hardware.Camera camera) {
638            mPostViewPictureCallbackTime = System.currentTimeMillis();
639            Log.v(TAG, "mShutterToPostViewCallbackTime = "
640                    + (mPostViewPictureCallbackTime - mShutterCallbackTime)
641                    + "ms");
642        }
643    }
644
645    private final class RawPictureCallback implements PictureCallback {
646        public void onPictureTaken(
647                byte [] rawData, android.hardware.Camera camera) {
648            mRawPictureCallbackTime = System.currentTimeMillis();
649            Log.v(TAG, "mShutterToRawCallbackTime = "
650                    + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
651        }
652    }
653
654    private final class JpegPictureCallback implements PictureCallback {
655        Location mLocation;
656
657        public JpegPictureCallback(Location loc) {
658            mLocation = loc;
659        }
660
661        public void onPictureTaken(
662                final byte [] jpegData, final android.hardware.Camera camera) {
663            if (mPausing) {
664                return;
665            }
666
667            mJpegPictureCallbackTime = System.currentTimeMillis();
668            // If postview callback has arrived, the captured image is displayed
669            // in postview callback. If not, the captured image is displayed in
670            // raw picture callback.
671            if (mPostViewPictureCallbackTime != 0) {
672                mShutterToPictureDisplayedTime =
673                        mPostViewPictureCallbackTime - mShutterCallbackTime;
674                mPictureDisplayedToJpegCallbackTime =
675                        mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
676            } else {
677                mShutterToPictureDisplayedTime =
678                        mRawPictureCallbackTime - mShutterCallbackTime;
679                mPictureDisplayedToJpegCallbackTime =
680                        mJpegPictureCallbackTime - mRawPictureCallbackTime;
681            }
682            Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
683                    + mPictureDisplayedToJpegCallbackTime + "ms");
684
685            if (!mIsImageCaptureIntent) {
686                enableCameraControls(true);
687
688                startPreview();
689            }
690
691            if (!mIsImageCaptureIntent) {
692                Size s = mParameters.getPictureSize();
693                mImageSaver.addImage(jpegData, mLocation, s.width, s.height);
694            } else {
695                mJpegImageData = jpegData;
696                if (!mQuickCapture) {
697                    showPostCaptureAlert();
698                } else {
699                    doAttach();
700                }
701            }
702
703            // Check this in advance of each shot so we don't add to shutter
704            // latency. It's true that someone else could write to the SD card in
705            // the mean time and fill it, but that could have happened between the
706            // shutter press and saving the JPEG too.
707            checkStorage();
708
709            long now = System.currentTimeMillis();
710            mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
711            Log.v(TAG, "mJpegCallbackFinishTime = "
712                    + mJpegCallbackFinishTime + "ms");
713            mJpegPictureCallbackTime = 0;
714        }
715    }
716
717    private final class AutoFocusCallback
718            implements android.hardware.Camera.AutoFocusCallback {
719        public void onAutoFocus(
720                boolean focused, android.hardware.Camera camera) {
721            if (mPausing) return;
722
723            mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;
724            Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms");
725            mFocusManager.onAutoFocus(focused);
726            // If focus completes and the snapshot is not started, enable the
727            // controls.
728            if (mFocusManager.isFocusCompleted()) {
729                enableCameraControls(true);
730            }
731        }
732    }
733
734    private final class ZoomListener
735            implements android.hardware.Camera.OnZoomChangeListener {
736        @Override
737        public void onZoomChange(
738                int value, boolean stopped, android.hardware.Camera camera) {
739            Log.v(TAG, "Zoom changed: value=" + value + ". stopped=" + stopped);
740            mZoomValue = value;
741
742            // Update the UI when we get zoom value.
743            mZoomControl.setZoomIndex(value);
744
745            // Keep mParameters up to date. We do not getParameter again in
746            // takePicture. If we do not do this, wrong zoom value will be set.
747            mParameters.setZoom(value);
748
749            if (stopped && mZoomState != ZOOM_STOPPED) {
750                if (mTargetZoomValue != -1 && value != mTargetZoomValue) {
751                    mCameraDevice.startSmoothZoom(mTargetZoomValue);
752                    mZoomState = ZOOM_START;
753                } else {
754                    mZoomState = ZOOM_STOPPED;
755                }
756            }
757        }
758    }
759
760    // Each SaveRequest remembers the data needed to save an image.
761    private static class SaveRequest {
762        byte[] data;
763        Location loc;
764        int width, height;
765        long dateTaken;
766        int previewWidth;
767    }
768
769    // We use a queue to store the SaveRequests that have not been completed
770    // yet. The main thread puts the request into the queue. The saver thread
771    // gets it from the queue, does the work, and removes it from the queue.
772    //
773    // There are several cases the main thread needs to wait for the saver
774    // thread to finish all the work in the queue:
775    // (1) When the activity's onPause() is called, we need to finish all the
776    // work, so other programs (like Gallery) can see all the images.
777    // (2) When we need to show the SharePop, we need to finish all the work
778    // too, because we want to show the thumbnail of the last image taken.
779    //
780    // If the queue becomes too long, adding a new request will block the main
781    // thread until the queue length drops below the threshold (QUEUE_LIMIT).
782    // If we don't do this, we may face several problems: (1) We may OOM
783    // because we are holding all the jpeg data in memory. (2) We may ANR
784    // when we need to wait for saver thread finishing all the work (in
785    // onPause() or showSharePopup()) because the time to finishing a long queue
786    // of work may be too long.
787    private class ImageSaver extends Thread {
788        private static final int QUEUE_LIMIT = 3;
789
790        private ArrayList<SaveRequest> mQueue;
791        private Thumbnail mPendingThumbnail;
792        private Object mUpdateThumbnailLock = new Object();
793        private boolean mStop;
794
795        // Runs in main thread
796        public ImageSaver() {
797            mQueue = new ArrayList<SaveRequest>();
798            start();
799        }
800
801        // Runs in main thread
802        public void addImage(final byte[] data, Location loc, int width,
803                int height) {
804            SaveRequest r = new SaveRequest();
805            r.data = data;
806            r.loc = (loc == null) ? null : new Location(loc);  // make a copy
807            r.width = width;
808            r.height = height;
809            r.dateTaken = System.currentTimeMillis();
810            r.previewWidth = mPreviewFrameLayout.getWidth();
811            synchronized (this) {
812                while (mQueue.size() >= QUEUE_LIMIT) {
813                    try {
814                        wait();
815                    } catch (InterruptedException ex) {
816                        // ignore.
817                    }
818                }
819                mQueue.add(r);
820                notifyAll();  // Tell saver thread there is new work to do.
821            }
822        }
823
824        // Runs in saver thread
825        @Override
826        public void run() {
827            while (true) {
828                SaveRequest r;
829                synchronized (this) {
830                    if (mQueue.isEmpty()) {
831                        notifyAll();  // notify main thread in waitDone
832
833                        // Note that we can only stop after we saved all images
834                        // in the queue.
835                        if (mStop) break;
836
837                        try {
838                            wait();
839                        } catch (InterruptedException ex) {
840                            // ignore.
841                        }
842                        continue;
843                    }
844                    r = mQueue.get(0);
845                }
846                storeImage(r.data, r.loc, r.width, r.height, r.dateTaken,
847                        r.previewWidth);
848                synchronized(this) {
849                    mQueue.remove(0);
850                    notifyAll();  // the main thread may wait in addImage
851                }
852            }
853        }
854
855        // Runs in main thread
856        public void waitDone() {
857            synchronized (this) {
858                while (!mQueue.isEmpty()) {
859                    try {
860                        wait();
861                    } catch (InterruptedException ex) {
862                        // ignore.
863                    }
864                }
865            }
866            updateThumbnail();
867        }
868
869        // Runs in main thread
870        public void finish() {
871            waitDone();
872            synchronized (this) {
873                mStop = true;
874                notifyAll();
875            }
876            try {
877                join();
878            } catch (InterruptedException ex) {
879                // ignore.
880            }
881        }
882
883        // Runs in main thread (because we need to update mThumbnailView in the
884        // main thread)
885        public void updateThumbnail() {
886            Thumbnail t;
887            synchronized (mUpdateThumbnailLock) {
888                mHandler.removeMessages(UPDATE_THUMBNAIL);
889                t = mPendingThumbnail;
890                mPendingThumbnail = null;
891            }
892
893            if (t != null) {
894                mThumbnail = t;
895                mThumbnailView.setBitmap(mThumbnail.getBitmap());
896            }
897            // Share popup may still have the reference to the old thumbnail. Clear it.
898            mSharePopup = null;
899        }
900
901        // Runs in saver thread
902        private void storeImage(final byte[] data, Location loc, int width,
903                int height, long dateTaken, int previewWidth) {
904            String title = Util.createJpegName(dateTaken);
905            int orientation = Exif.getOrientation(data);
906            Uri uri = Storage.addImage(mContentResolver, title, dateTaken,
907                    loc, orientation, data, width, height);
908            if (uri != null) {
909                boolean needThumbnail;
910                synchronized (this) {
911                    // If the number of requests in the queue (include the
912                    // current one) is greater than 1, we don't need to generate
913                    // thumbnail for this image. Because we'll soon replace it
914                    // with the thumbnail for some image later in the queue.
915                    needThumbnail = (mQueue.size() <= 1);
916                }
917                if (needThumbnail) {
918                    // Create a thumbnail whose width is equal or bigger than
919                    // that of the preview.
920                    int ratio = (int) Math.ceil((double) width / previewWidth);
921                    int inSampleSize = Integer.highestOneBit(ratio);
922                    Thumbnail t = Thumbnail.createThumbnail(
923                                data, orientation, inSampleSize, uri);
924                    synchronized (mUpdateThumbnailLock) {
925                        // We need to update the thumbnail in the main thread,
926                        // so send a message to run updateThumbnail().
927                        mPendingThumbnail = t;
928                        mHandler.sendEmptyMessage(UPDATE_THUMBNAIL);
929                    }
930                }
931                Util.broadcastNewPicture(Camera.this, uri);
932            }
933        }
934    }
935
936    @Override
937    public boolean capture() {
938        // If we are already in the middle of taking a snapshot then ignore.
939        if (mCameraState == SNAPSHOT_IN_PROGRESS || mCameraDevice == null) {
940            return false;
941        }
942        mCaptureStartTime = System.currentTimeMillis();
943        mPostViewPictureCallbackTime = 0;
944        enableCameraControls(false);
945        mJpegImageData = null;
946
947        // Set rotation and gps data.
948        Util.setRotationParameter(mParameters, mCameraId, mOrientation);
949        Location loc = mLocationManager.getCurrentLocation();
950        Util.setGpsParameters(mParameters, loc);
951        mCameraDevice.setParameters(mParameters);
952
953        mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback,
954                mPostViewPictureCallback, new JpegPictureCallback(loc));
955        mCameraState = SNAPSHOT_IN_PROGRESS;
956        return true;
957    }
958
959    @Override
960    public void setFocusParameters() {
961        setCameraParameters(UPDATE_PARAM_PREFERENCE);
962    }
963
964    private boolean saveDataToFile(String filePath, byte[] data) {
965        FileOutputStream f = null;
966        try {
967            f = new FileOutputStream(filePath);
968            f.write(data);
969        } catch (IOException e) {
970            return false;
971        } finally {
972            Util.closeSilently(f);
973        }
974        return true;
975    }
976
977    @Override
978    public void onCreate(Bundle icicle) {
979        super.onCreate(icicle);
980
981        mIsImageCaptureIntent = isImageCaptureIntent();
982        setContentView(R.layout.camera);
983        if (mIsImageCaptureIntent) {
984            findViewById(R.id.btn_cancel).setVisibility(View.VISIBLE);
985        } else {
986            mThumbnailView = (RotateImageView) findViewById(R.id.thumbnail);
987            mThumbnailView.setVisibility(View.VISIBLE);
988        }
989
990        mPreferences = new ComboPreferences(this);
991        CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
992        mFocusManager = new FocusManager(mPreferences,
993                getString(R.string.pref_camera_focusmode_default));
994
995        mCameraId = CameraSettings.readPreferredCameraId(mPreferences);
996
997        // Testing purpose. Launch a specific camera through the intent extras.
998        int intentCameraId = Util.getCameraFacingIntentExtras(this);
999        if (intentCameraId != -1) {
1000            mCameraId = intentCameraId;
1001        }
1002
1003        mPreferences.setLocalId(this, mCameraId);
1004        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
1005
1006        mNumberOfCameras = CameraHolder.instance().getNumberOfCameras();
1007        mQuickCapture = getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
1008
1009        // we need to reset exposure for the preview
1010        resetExposureCompensation();
1011
1012        /*
1013         * To reduce startup time, we start the preview in another thread.
1014         * We make sure the preview is started at the end of onCreate.
1015         */
1016        Thread startPreviewThread = new Thread(new Runnable() {
1017            public void run() {
1018                try {
1019                    mCameraDevice = Util.openCamera(Camera.this, mCameraId);
1020                    initializeCapabilities();
1021                    startPreview();
1022                } catch (CameraHardwareException e) {
1023                    mOpenCameraFail = true;
1024                } catch (CameraDisabledException e) {
1025                    mCameraDisabled = true;
1026                }
1027            }
1028        });
1029        startPreviewThread.start();
1030
1031        Util.enterLightsOutMode(getWindow());
1032
1033        // don't set mSurfaceHolder here. We have it set ONLY within
1034        // surfaceChanged / surfaceDestroyed, other parts of the code
1035        // assume that when it is set, the surface is also set.
1036        SurfaceView preview = (SurfaceView) findViewById(R.id.camera_preview);
1037        SurfaceHolder holder = preview.getHolder();
1038        holder.addCallback(this);
1039        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
1040
1041        if (mIsImageCaptureIntent) {
1042            setupCaptureParams();
1043        } else {
1044            mModePicker = (ModePicker) findViewById(R.id.mode_picker);
1045            mModePicker.setVisibility(View.VISIBLE);
1046            mModePicker.setOnModeChangeListener(this);
1047            mModePicker.setCurrentMode(ModePicker.MODE_CAMERA);
1048        }
1049
1050        mZoomControl = (ZoomControl) findViewById(R.id.zoom_control);
1051        mLocationManager = new LocationManager(this, this);
1052
1053        // Make sure preview is started.
1054        try {
1055            startPreviewThread.join();
1056            if (mOpenCameraFail) {
1057                Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
1058                return;
1059            } else if (mCameraDisabled) {
1060                Util.showErrorAndFinish(this, R.string.camera_disabled);
1061                return;
1062            }
1063        } catch (InterruptedException ex) {
1064            // ignore
1065        }
1066
1067        mBackCameraId = CameraHolder.instance().getBackCameraId();
1068        mFrontCameraId = CameraHolder.instance().getFrontCameraId();
1069
1070        // Do this after starting preview because it depends on camera
1071        // parameters.
1072        initializeIndicatorControl();
1073    }
1074
1075    private void overrideCameraSettings(final String flashMode,
1076            final String whiteBalance, final String focusMode) {
1077        if (mIndicatorControlContainer != null) {
1078            mIndicatorControlContainer.overrideSettings(
1079                    CameraSettings.KEY_FLASH_MODE, flashMode,
1080                    CameraSettings.KEY_WHITE_BALANCE, whiteBalance,
1081                    CameraSettings.KEY_FOCUS_MODE, focusMode);
1082        }
1083    }
1084
1085    private void updateSceneModeUI() {
1086        // If scene mode is set, we cannot set flash mode, white balance, and
1087        // focus mode, instead, we read it from driver
1088        if (!Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
1089            overrideCameraSettings(mParameters.getFlashMode(),
1090                    mParameters.getWhiteBalance(), mParameters.getFocusMode());
1091        } else {
1092            overrideCameraSettings(null, null, null);
1093        }
1094    }
1095
1096    private void loadCameraPreferences() {
1097        CameraSettings settings = new CameraSettings(this, mInitialParams,
1098                mCameraId, CameraHolder.instance().getCameraInfo());
1099        mPreferenceGroup = settings.getPreferenceGroup(R.xml.camera_preferences);
1100    }
1101
1102    private void initializeIndicatorControl() {
1103        // setting the indicator buttons.
1104        mIndicatorControlContainer =
1105                (IndicatorControlContainer) findViewById(R.id.indicator_control);
1106        if (mIndicatorControlContainer == null) return;
1107        loadCameraPreferences();
1108        final String[] SETTING_KEYS = {
1109                CameraSettings.KEY_FLASH_MODE,
1110                CameraSettings.KEY_WHITE_BALANCE,
1111                CameraSettings.KEY_EXPOSURE,
1112                CameraSettings.KEY_SCENE_MODE};
1113        final String[] OTHER_SETTING_KEYS = {
1114                CameraSettings.KEY_RECORD_LOCATION,
1115                CameraSettings.KEY_PICTURE_SIZE,
1116                CameraSettings.KEY_FOCUS_MODE};
1117
1118        CameraPicker.setImageResourceId(R.drawable.ic_switch_photo_facing_holo_light);
1119        mIndicatorControlContainer.initialize(this, mPreferenceGroup,
1120                mParameters.isZoomSupported(),
1121                SETTING_KEYS, OTHER_SETTING_KEYS);
1122        updateSceneModeUI();
1123        mIndicatorControlContainer.setListener(this);
1124    }
1125
1126    private boolean collapseCameraControls() {
1127        if ((mIndicatorControlContainer != null)
1128                && mIndicatorControlContainer.dismissSettingPopup()) {
1129            return true;
1130        }
1131        return false;
1132    }
1133
1134    private void enableCameraControls(boolean enable) {
1135        if (mIndicatorControlContainer != null) {
1136            mIndicatorControlContainer.setEnabled(enable);
1137        }
1138        if (mModePicker != null) mModePicker.setEnabled(enable);
1139        if (mZoomControl != null) mZoomControl.setEnabled(enable);
1140    }
1141
1142    public static int roundOrientation(int orientation) {
1143        return ((orientation + 45) / 90 * 90) % 360;
1144    }
1145
1146    private class MyOrientationEventListener
1147            extends OrientationEventListener {
1148        public MyOrientationEventListener(Context context) {
1149            super(context);
1150        }
1151
1152        @Override
1153        public void onOrientationChanged(int orientation) {
1154            // We keep the last known orientation. So if the user first orient
1155            // the camera then point the camera to floor or sky, we still have
1156            // the correct orientation.
1157            if (orientation == ORIENTATION_UNKNOWN) return;
1158            mOrientation = roundOrientation(orientation);
1159            // When the screen is unlocked, display rotation may change. Always
1160            // calculate the up-to-date orientationCompensation.
1161            int orientationCompensation = mOrientation
1162                    + Util.getDisplayRotation(Camera.this);
1163            if (mOrientationCompensation != orientationCompensation) {
1164                mOrientationCompensation = orientationCompensation;
1165                setOrientationIndicator(mOrientationCompensation);
1166            }
1167
1168            // Show the toast after getting the first orientation changed.
1169            if (mHandler.hasMessages(SHOW_TAP_TO_FOCUS_TOAST)) {
1170                mHandler.removeMessages(SHOW_TAP_TO_FOCUS_TOAST);
1171                showTapToFocusToast();
1172            }
1173        }
1174    }
1175
1176    private void setOrientationIndicator(int degree) {
1177        if (mThumbnailView != null) mThumbnailView.setDegree(degree);
1178        if (mModePicker != null) mModePicker.setDegree(degree);
1179        if (mSharePopup != null) mSharePopup.setOrientation(degree);
1180        if (mIndicatorControlContainer != null) mIndicatorControlContainer.setDegree(degree);
1181        if (mZoomControl != null) mZoomControl.setDegree(degree);
1182        if (mFocusIndicator != null) mFocusIndicator.setOrientation(degree);
1183        if (mFaceView != null) mFaceView.setOrientation(degree);
1184    }
1185
1186    @Override
1187    public void onStop() {
1188        super.onStop();
1189        if (mMediaProviderClient != null) {
1190            mMediaProviderClient.release();
1191            mMediaProviderClient = null;
1192        }
1193    }
1194
1195    private void checkStorage() {
1196        mPicturesRemaining = Storage.getAvailableSpace();
1197        if (mPicturesRemaining > 0) {
1198            mPicturesRemaining /= 1500000;
1199        }
1200        updateStorageHint();
1201    }
1202
1203    @OnClickAttr
1204    public void onThumbnailClicked(View v) {
1205        if (isCameraIdle() && mThumbnail != null) {
1206            showSharePopup();
1207        }
1208    }
1209
1210    @OnClickAttr
1211    public void onRetakeButtonClicked(View v) {
1212        hidePostCaptureAlert();
1213        startPreview();
1214    }
1215
1216    @OnClickAttr
1217    public void onDoneButtonClicked(View v) {
1218        doAttach();
1219    }
1220
1221    @OnClickAttr
1222    public void onCancelButtonClicked(View v) {
1223        doCancel();
1224    }
1225
1226    private void doAttach() {
1227        if (mPausing) {
1228            return;
1229        }
1230
1231        byte[] data = mJpegImageData;
1232
1233        if (mCropValue == null) {
1234            // First handle the no crop case -- just return the value.  If the
1235            // caller specifies a "save uri" then write the data to it's
1236            // stream. Otherwise, pass back a scaled down version of the bitmap
1237            // directly in the extras.
1238            if (mSaveUri != null) {
1239                OutputStream outputStream = null;
1240                try {
1241                    outputStream = mContentResolver.openOutputStream(mSaveUri);
1242                    outputStream.write(data);
1243                    outputStream.close();
1244
1245                    setResultEx(RESULT_OK);
1246                    finish();
1247                } catch (IOException ex) {
1248                    // ignore exception
1249                } finally {
1250                    Util.closeSilently(outputStream);
1251                }
1252            } else {
1253                int orientation = Exif.getOrientation(data);
1254                Bitmap bitmap = Util.makeBitmap(data, 50 * 1024);
1255                bitmap = Util.rotate(bitmap, orientation);
1256                setResultEx(RESULT_OK,
1257                        new Intent("inline-data").putExtra("data", bitmap));
1258                finish();
1259            }
1260        } else {
1261            // Save the image to a temp file and invoke the cropper
1262            Uri tempUri = null;
1263            FileOutputStream tempStream = null;
1264            try {
1265                File path = getFileStreamPath(sTempCropFilename);
1266                path.delete();
1267                tempStream = openFileOutput(sTempCropFilename, 0);
1268                tempStream.write(data);
1269                tempStream.close();
1270                tempUri = Uri.fromFile(path);
1271            } catch (FileNotFoundException ex) {
1272                setResultEx(Activity.RESULT_CANCELED);
1273                finish();
1274                return;
1275            } catch (IOException ex) {
1276                setResultEx(Activity.RESULT_CANCELED);
1277                finish();
1278                return;
1279            } finally {
1280                Util.closeSilently(tempStream);
1281            }
1282
1283            Bundle newExtras = new Bundle();
1284            if (mCropValue.equals("circle")) {
1285                newExtras.putString("circleCrop", "true");
1286            }
1287            if (mSaveUri != null) {
1288                newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
1289            } else {
1290                newExtras.putBoolean("return-data", true);
1291            }
1292
1293            Intent cropIntent = new Intent("com.android.camera.action.CROP");
1294
1295            cropIntent.setData(tempUri);
1296            cropIntent.putExtras(newExtras);
1297
1298            startActivityForResult(cropIntent, CROP_MSG);
1299        }
1300    }
1301
1302    private void doCancel() {
1303        setResultEx(RESULT_CANCELED, new Intent());
1304        finish();
1305    }
1306
1307    public void onShutterButtonFocus(ShutterButton button, boolean pressed) {
1308        switch (button.getId()) {
1309            case R.id.shutter_button:
1310                doFocus(pressed);
1311                break;
1312        }
1313    }
1314
1315    public void onShutterButtonClick(ShutterButton button) {
1316        switch (button.getId()) {
1317            case R.id.shutter_button:
1318                doSnap();
1319                break;
1320        }
1321    }
1322
1323    private OnScreenHint mStorageHint;
1324
1325    private void updateStorageHint() {
1326        String noStorageText = null;
1327
1328        if (mPicturesRemaining == Storage.UNAVAILABLE) {
1329            noStorageText = getString(R.string.no_storage);
1330        } else if (mPicturesRemaining == Storage.PREPARING) {
1331            noStorageText = getString(R.string.preparing_sd);
1332        } else if (mPicturesRemaining == Storage.UNKNOWN_SIZE) {
1333            noStorageText = getString(R.string.access_sd_fail);
1334        } else if (mPicturesRemaining < 1L) {
1335            noStorageText = getString(R.string.not_enough_space);
1336        }
1337
1338        if (noStorageText != null) {
1339            if (mStorageHint == null) {
1340                mStorageHint = OnScreenHint.makeText(this, noStorageText);
1341            } else {
1342                mStorageHint.setText(noStorageText);
1343            }
1344            mStorageHint.show();
1345        } else if (mStorageHint != null) {
1346            mStorageHint.cancel();
1347            mStorageHint = null;
1348        }
1349    }
1350
1351    private void installIntentFilter() {
1352        // install an intent filter to receive SD card related events.
1353        IntentFilter intentFilter =
1354                new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
1355        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
1356        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
1357        intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
1358        intentFilter.addDataScheme("file");
1359        registerReceiver(mReceiver, intentFilter);
1360        mDidRegister = true;
1361    }
1362
1363    @Override
1364    protected void onResume() {
1365        super.onResume();
1366        mPausing = false;
1367        if (mOpenCameraFail || mCameraDisabled) return;
1368
1369        mJpegPictureCallbackTime = 0;
1370        mZoomValue = 0;
1371
1372        // Start the preview if it is not started.
1373        if (mCameraState == PREVIEW_STOPPED) {
1374            try {
1375                mCameraDevice = Util.openCamera(this, mCameraId);
1376                initializeCapabilities();
1377                resetExposureCompensation();
1378                startPreview();
1379            } catch (CameraHardwareException e) {
1380                Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
1381                return;
1382            } catch (CameraDisabledException e) {
1383                Util.showErrorAndFinish(this, R.string.camera_disabled);
1384                return;
1385            }
1386        }
1387
1388        if (mSurfaceHolder != null) {
1389            // If first time initialization is not finished, put it in the
1390            // message queue.
1391            if (!mFirstTimeInitialized) {
1392                mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1393            } else {
1394                initializeSecondTime();
1395            }
1396        }
1397        keepScreenOnAwhile();
1398
1399        if (mCameraState == IDLE) {
1400            mOnResumeTime = SystemClock.uptimeMillis();
1401            mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
1402        }
1403    }
1404
1405    @Override
1406    protected void onPause() {
1407        mPausing = true;
1408        stopPreview();
1409        // Close the camera now because other activities may need to use it.
1410        closeCamera();
1411        resetScreenOn();
1412
1413        // Clear UI.
1414        collapseCameraControls();
1415        if (mSharePopup != null) mSharePopup.dismiss();
1416        if (mFaceView != null) mFaceView.clear();
1417
1418        if (mFirstTimeInitialized) {
1419            mOrientationListener.disable();
1420            mImageSaver.finish();
1421            mImageSaver = null;
1422            if (!mIsImageCaptureIntent && mThumbnail != null && !mThumbnail.fromFile()) {
1423                mThumbnail.saveTo(new File(getFilesDir(), Thumbnail.LAST_THUMB_FILENAME));
1424            }
1425        }
1426
1427        if (mDidRegister) {
1428            unregisterReceiver(mReceiver);
1429            mDidRegister = false;
1430        }
1431        mLocationManager.recordLocation(false);
1432        updateExposureOnScreenIndicator(0);
1433
1434        mFocusManager.releaseSoundPlayer();
1435
1436        if (mStorageHint != null) {
1437            mStorageHint.cancel();
1438            mStorageHint = null;
1439        }
1440
1441        // If we are in an image capture intent and has taken
1442        // a picture, we just clear it in onPause.
1443        mJpegImageData = null;
1444
1445        // Remove the messages in the event queue.
1446        mHandler.removeMessages(FIRST_TIME_INIT);
1447        mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
1448        mFocusManager.removeMessages();
1449
1450        super.onPause();
1451    }
1452
1453    @Override
1454    protected void onActivityResult(
1455            int requestCode, int resultCode, Intent data) {
1456        switch (requestCode) {
1457            case CROP_MSG: {
1458                Intent intent = new Intent();
1459                if (data != null) {
1460                    Bundle extras = data.getExtras();
1461                    if (extras != null) {
1462                        intent.putExtras(extras);
1463                    }
1464                }
1465                setResultEx(resultCode, intent);
1466                finish();
1467
1468                File path = getFileStreamPath(sTempCropFilename);
1469                path.delete();
1470
1471                break;
1472            }
1473        }
1474    }
1475
1476    private boolean canTakePicture() {
1477        return isCameraIdle() && (mPicturesRemaining > 0);
1478    }
1479
1480    @Override
1481    public void autoFocus() {
1482        mFocusStartTime = System.currentTimeMillis();
1483        mCameraDevice.autoFocus(mAutoFocusCallback);
1484        mCameraState = FOCUSING;
1485        enableCameraControls(false);
1486    }
1487
1488    @Override
1489    public void cancelAutoFocus() {
1490        mCameraDevice.cancelAutoFocus();
1491        mCameraState = IDLE;
1492        enableCameraControls(true);
1493        setCameraParameters(UPDATE_PARAM_PREFERENCE);
1494    }
1495
1496    // Preview area is touched. Handle touch focus.
1497    @Override
1498    public boolean onTouch(View v, MotionEvent e) {
1499        if (mPausing || mCameraDevice == null || !mFirstTimeInitialized
1500                || mCameraState == SNAPSHOT_IN_PROGRESS) {
1501            return false;
1502        }
1503
1504        // Do not trigger touch focus if popup window is opened.
1505        if (collapseCameraControls()) return false;
1506
1507        // Check if metering area or focus area is supported.
1508        if (!mFocusAreaSupported && !mMeteringAreaSupported) return false;
1509
1510        return mFocusManager.onTouch(e);
1511    }
1512
1513    @Override
1514    public void onBackPressed() {
1515        if (!isCameraIdle()) {
1516            // ignore backs while we're taking a picture
1517            return;
1518        } else if (!collapseCameraControls()) {
1519            super.onBackPressed();
1520        }
1521    }
1522
1523    @Override
1524    public boolean onKeyDown(int keyCode, KeyEvent event) {
1525        switch (keyCode) {
1526            case KeyEvent.KEYCODE_FOCUS:
1527                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1528                    doFocus(true);
1529                }
1530                return true;
1531            case KeyEvent.KEYCODE_CAMERA:
1532                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1533                    doSnap();
1534                }
1535                return true;
1536            case KeyEvent.KEYCODE_DPAD_CENTER:
1537                // If we get a dpad center event without any focused view, move
1538                // the focus to the shutter button and press it.
1539                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1540                    // Start auto-focus immediately to reduce shutter lag. After
1541                    // the shutter button gets the focus, doFocus() will be
1542                    // called again but it is fine.
1543                    if (collapseCameraControls()) return true;
1544                    doFocus(true);
1545                    if (mShutterButton.isInTouchMode()) {
1546                        mShutterButton.requestFocusFromTouch();
1547                    } else {
1548                        mShutterButton.requestFocus();
1549                    }
1550                    mShutterButton.setPressed(true);
1551                }
1552                return true;
1553        }
1554
1555        return super.onKeyDown(keyCode, event);
1556    }
1557
1558    @Override
1559    public boolean onKeyUp(int keyCode, KeyEvent event) {
1560        switch (keyCode) {
1561            case KeyEvent.KEYCODE_FOCUS:
1562                if (mFirstTimeInitialized) {
1563                    doFocus(false);
1564                }
1565                return true;
1566        }
1567        return super.onKeyUp(keyCode, event);
1568    }
1569
1570    private void doSnap() {
1571        if (mPausing || collapseCameraControls()) return;
1572
1573        // Do not take the picture if there is not enough storage.
1574        if (mPicturesRemaining <= 0) {
1575            Log.i(TAG, "Not enough space or storage not ready. remaining=" + mPicturesRemaining);
1576            return;
1577        }
1578
1579        Log.v(TAG, "doSnap: mCameraState=" + mCameraState);
1580        mFocusManager.doSnap();
1581    }
1582
1583    private void doFocus(boolean pressed) {
1584        if (mPausing || collapseCameraControls() || mCameraState == SNAPSHOT_IN_PROGRESS) return;
1585
1586        // Do not do focus if there is not enough storage.
1587        if (pressed && !canTakePicture()) return;
1588
1589        mFocusManager.doFocus(pressed);
1590    }
1591
1592    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
1593        // Make sure we have a surface in the holder before proceeding.
1594        if (holder.getSurface() == null) {
1595            Log.d(TAG, "holder.getSurface() == null");
1596            return;
1597        }
1598
1599        Log.v(TAG, "surfaceChanged. w=" + w + ". h=" + h);
1600
1601        // We need to save the holder for later use, even when the mCameraDevice
1602        // is null. This could happen if onResume() is invoked after this
1603        // function.
1604        mSurfaceHolder = holder;
1605
1606        // The mCameraDevice will be null if it fails to connect to the camera
1607        // hardware. In this case we will show a dialog and then finish the
1608        // activity, so it's OK to ignore it.
1609        if (mCameraDevice == null) return;
1610
1611        // Sometimes surfaceChanged is called after onPause or before onResume.
1612        // Ignore it.
1613        if (mPausing || isFinishing()) return;
1614
1615        // Set preview display if the surface is being created. Preview was
1616        // already started. Also restart the preview if display rotation has
1617        // changed. Sometimes this happens when the device is held in portrait
1618        // and camera app is opened. Rotation animation takes some time and
1619        // display rotation in onCreate may not be what we want.
1620        if (mCameraState == PREVIEW_STOPPED) {
1621            startPreview();
1622        } else {
1623            if (Util.getDisplayRotation(this) != mDisplayRotation) {
1624                setDisplayOrientation();
1625            }
1626            if (holder.isCreating()) {
1627                // Set preview display if the surface is being created and preview
1628                // was already started. That means preview display was set to null
1629                // and we need to set it now.
1630                setPreviewDisplay(holder);
1631            }
1632        }
1633
1634        // If first time initialization is not finished, send a message to do
1635        // it later. We want to finish surfaceChanged as soon as possible to let
1636        // user see preview first.
1637        if (!mFirstTimeInitialized) {
1638            mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1639        } else {
1640            initializeSecondTime();
1641        }
1642    }
1643
1644    public void surfaceCreated(SurfaceHolder holder) {
1645    }
1646
1647    public void surfaceDestroyed(SurfaceHolder holder) {
1648        stopPreview();
1649        mSurfaceHolder = null;
1650    }
1651
1652    private void closeCamera() {
1653        if (mCameraDevice != null) {
1654            mCameraDevice.cancelAutoFocus(); // Reset the focus.
1655            CameraHolder.instance().release();
1656            mCameraDevice.setZoomChangeListener(null);
1657            mCameraDevice.setFaceDetectionListener(null);
1658            mCameraDevice.setErrorCallback(null);
1659            mCameraDevice = null;
1660            mCameraState = PREVIEW_STOPPED;
1661            mFocusManager.onCameraReleased();
1662        }
1663    }
1664
1665    private void setPreviewDisplay(SurfaceHolder holder) {
1666        try {
1667            mCameraDevice.setPreviewDisplay(holder);
1668        } catch (Throwable ex) {
1669            closeCamera();
1670            throw new RuntimeException("setPreviewDisplay failed", ex);
1671        }
1672    }
1673
1674    private void setDisplayOrientation() {
1675        mDisplayRotation = Util.getDisplayRotation(this);
1676        mDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
1677        mCameraDevice.setDisplayOrientation(mDisplayOrientation);
1678        if (mFaceView != null) {
1679            mFaceView.setDisplayOrientation(mDisplayOrientation);
1680        }
1681    }
1682
1683    private void startPreview() {
1684        if (mPausing || isFinishing()) return;
1685
1686        mFocusManager.resetTouchFocus();
1687
1688        mCameraDevice.setErrorCallback(mErrorCallback);
1689
1690        // If we're previewing already, stop the preview first (this will blank
1691        // the screen).
1692        if (mCameraState != PREVIEW_STOPPED) stopPreview();
1693
1694        setPreviewDisplay(mSurfaceHolder);
1695        setDisplayOrientation();
1696        setCameraParameters(UPDATE_PARAM_ALL);
1697        // If the focus mode is continuous autofocus, call cancelAutoFocus to
1698        // resume it because it may have been paused by autoFocus call.
1699        if (Parameters.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mParameters.getFocusMode())) {
1700            mCameraDevice.cancelAutoFocus();
1701        }
1702
1703        try {
1704            Log.v(TAG, "startPreview");
1705            mCameraDevice.startPreview();
1706        } catch (Throwable ex) {
1707            closeCamera();
1708            throw new RuntimeException("startPreview failed", ex);
1709        }
1710
1711        startFaceDetection();
1712        mZoomState = ZOOM_STOPPED;
1713        mCameraState = IDLE;
1714        mFocusManager.onPreviewStarted();
1715    }
1716
1717    private void stopPreview() {
1718        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1719            Log.v(TAG, "stopPreview");
1720            mCameraDevice.stopPreview();
1721        }
1722        mCameraState = PREVIEW_STOPPED;
1723        mFocusManager.onPreviewStopped();
1724    }
1725
1726    private static boolean isSupported(String value, List<String> supported) {
1727        return supported == null ? false : supported.indexOf(value) >= 0;
1728    }
1729
1730    private void updateCameraParametersInitialize() {
1731        // Reset preview frame rate to the maximum because it may be lowered by
1732        // video camera application.
1733        List<Integer> frameRates = mParameters.getSupportedPreviewFrameRates();
1734        if (frameRates != null) {
1735            Integer max = Collections.max(frameRates);
1736            mParameters.setPreviewFrameRate(max);
1737        }
1738
1739        mParameters.setRecordingHint(false);
1740    }
1741
1742    private void updateCameraParametersZoom() {
1743        // Set zoom.
1744        if (mParameters.isZoomSupported()) {
1745            mParameters.setZoom(mZoomValue);
1746        }
1747    }
1748
1749    private void updateCameraParametersPreference() {
1750        if (mFocusAreaSupported) {
1751            mParameters.setFocusAreas(mFocusManager.getFocusAreas());
1752        }
1753
1754        if (mMeteringAreaSupported) {
1755            // Use the same area for focus and metering.
1756            mParameters.setMeteringAreas(mFocusManager.getMeteringAreas());
1757        }
1758
1759        // Set picture size.
1760        String pictureSize = mPreferences.getString(
1761                CameraSettings.KEY_PICTURE_SIZE, null);
1762        if (pictureSize == null) {
1763            CameraSettings.initialCameraPictureSize(this, mParameters);
1764        } else {
1765            List<Size> supported = mParameters.getSupportedPictureSizes();
1766            CameraSettings.setCameraPictureSize(
1767                    pictureSize, supported, mParameters);
1768        }
1769
1770        // Set the preview frame aspect ratio according to the picture size.
1771        Size size = mParameters.getPictureSize();
1772
1773        mPreviewPanel = findViewById(R.id.frame_layout);
1774        mPreviewFrameLayout = (PreviewFrameLayout) findViewById(R.id.frame);
1775        mPreviewFrameLayout.setAspectRatio((double) size.width / size.height);
1776
1777        // Set a preview size that is closest to the viewfinder height and has
1778        // the right aspect ratio.
1779        List<Size> sizes = mParameters.getSupportedPreviewSizes();
1780        Size optimalSize = Util.getOptimalPreviewSize(this,
1781                sizes, (double) size.width / size.height);
1782        Size original = mParameters.getPreviewSize();
1783        if (!original.equals(optimalSize)) {
1784            mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
1785
1786            // Zoom related settings will be changed for different preview
1787            // sizes, so set and read the parameters to get lastest values
1788            mCameraDevice.setParameters(mParameters);
1789            mParameters = mCameraDevice.getParameters();
1790        }
1791        Log.v(TAG, "Preview size is " + optimalSize.width + "x" + optimalSize.height);
1792
1793        // Since change scene mode may change supported values,
1794        // Set scene mode first,
1795        mSceneMode = mPreferences.getString(
1796                CameraSettings.KEY_SCENE_MODE,
1797                getString(R.string.pref_camera_scenemode_default));
1798        if (isSupported(mSceneMode, mParameters.getSupportedSceneModes())) {
1799            if (!mParameters.getSceneMode().equals(mSceneMode)) {
1800                mParameters.setSceneMode(mSceneMode);
1801                mCameraDevice.setParameters(mParameters);
1802
1803                // Setting scene mode will change the settings of flash mode,
1804                // white balance, and focus mode. Here we read back the
1805                // parameters, so we can know those settings.
1806                mParameters = mCameraDevice.getParameters();
1807            }
1808        } else {
1809            mSceneMode = mParameters.getSceneMode();
1810            if (mSceneMode == null) {
1811                mSceneMode = Parameters.SCENE_MODE_AUTO;
1812            }
1813        }
1814
1815        // Set JPEG quality.
1816        int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1817                CameraProfile.QUALITY_HIGH);
1818        mParameters.setJpegQuality(jpegQuality);
1819
1820        // For the following settings, we need to check if the settings are
1821        // still supported by latest driver, if not, ignore the settings.
1822
1823        // Set exposure compensation
1824        int value = CameraSettings.readExposure(mPreferences);
1825        int max = mParameters.getMaxExposureCompensation();
1826        int min = mParameters.getMinExposureCompensation();
1827        if (value >= min && value <= max) {
1828            mParameters.setExposureCompensation(value);
1829        } else {
1830            Log.w(TAG, "invalid exposure range: " + value);
1831        }
1832
1833        if (Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
1834            // Set flash mode.
1835            String flashMode = mPreferences.getString(
1836                    CameraSettings.KEY_FLASH_MODE,
1837                    getString(R.string.pref_camera_flashmode_default));
1838            List<String> supportedFlash = mParameters.getSupportedFlashModes();
1839            if (isSupported(flashMode, supportedFlash)) {
1840                mParameters.setFlashMode(flashMode);
1841            } else {
1842                flashMode = mParameters.getFlashMode();
1843                if (flashMode == null) {
1844                    flashMode = getString(
1845                            R.string.pref_camera_flashmode_no_flash);
1846                }
1847            }
1848
1849            // Set white balance parameter.
1850            String whiteBalance = mPreferences.getString(
1851                    CameraSettings.KEY_WHITE_BALANCE,
1852                    getString(R.string.pref_camera_whitebalance_default));
1853            if (isSupported(whiteBalance,
1854                    mParameters.getSupportedWhiteBalance())) {
1855                mParameters.setWhiteBalance(whiteBalance);
1856            } else {
1857                whiteBalance = mParameters.getWhiteBalance();
1858                if (whiteBalance == null) {
1859                    whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1860                }
1861            }
1862
1863            // Set focus mode.
1864            mFocusManager.overrideFocusMode(null);
1865            mParameters.setFocusMode(mFocusManager.getFocusMode());
1866        } else {
1867            mFocusManager.overrideFocusMode(mParameters.getFocusMode());
1868        }
1869    }
1870
1871    // We separate the parameters into several subsets, so we can update only
1872    // the subsets actually need updating. The PREFERENCE set needs extra
1873    // locking because the preference can be changed from GLThread as well.
1874    private void setCameraParameters(int updateSet) {
1875        mParameters = mCameraDevice.getParameters();
1876
1877        if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
1878            updateCameraParametersInitialize();
1879        }
1880
1881        if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
1882            updateCameraParametersZoom();
1883        }
1884
1885        if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
1886            updateCameraParametersPreference();
1887        }
1888
1889        mCameraDevice.setParameters(mParameters);
1890    }
1891
1892    // If the Camera is idle, update the parameters immediately, otherwise
1893    // accumulate them in mUpdateSet and update later.
1894    private void setCameraParametersWhenIdle(int additionalUpdateSet) {
1895        mUpdateSet |= additionalUpdateSet;
1896        if (mCameraDevice == null) {
1897            // We will update all the parameters when we open the device, so
1898            // we don't need to do anything now.
1899            mUpdateSet = 0;
1900            return;
1901        } else if (isCameraIdle()) {
1902            setCameraParameters(mUpdateSet);
1903            updateSceneModeUI();
1904            mUpdateSet = 0;
1905        } else {
1906            if (!mHandler.hasMessages(SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
1907                mHandler.sendEmptyMessageDelayed(
1908                        SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
1909            }
1910        }
1911    }
1912
1913    private void gotoGallery() {
1914        MenuHelper.gotoCameraImageGallery(this);
1915    }
1916
1917    private boolean isCameraIdle() {
1918        return (mCameraState == IDLE) || (mFocusManager.isFocusCompleted());
1919    }
1920
1921    private boolean isImageCaptureIntent() {
1922        String action = getIntent().getAction();
1923        return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action));
1924    }
1925
1926    private void setupCaptureParams() {
1927        Bundle myExtras = getIntent().getExtras();
1928        if (myExtras != null) {
1929            mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1930            mCropValue = myExtras.getString("crop");
1931        }
1932    }
1933
1934    private void showPostCaptureAlert() {
1935        if (mIsImageCaptureIntent) {
1936            Util.fadeOut(mIndicatorControlContainer);
1937            Util.fadeOut(mShutterButton);
1938
1939            int[] pickIds = {R.id.btn_retake, R.id.btn_done};
1940            for (int id : pickIds) {
1941                Util.fadeIn(findViewById(id));
1942            }
1943        }
1944    }
1945
1946    private void hidePostCaptureAlert() {
1947        if (mIsImageCaptureIntent) {
1948            enableCameraControls(true);
1949
1950            int[] pickIds = {R.id.btn_retake, R.id.btn_done};
1951            for (int id : pickIds) {
1952                Util.fadeOut(findViewById(id));
1953            }
1954
1955            Util.fadeIn(mShutterButton);
1956            Util.fadeIn(mIndicatorControlContainer);
1957        }
1958    }
1959
1960    @Override
1961    public boolean onPrepareOptionsMenu(Menu menu) {
1962        super.onPrepareOptionsMenu(menu);
1963        // Only show the menu when camera is idle.
1964        for (int i = 0; i < menu.size(); i++) {
1965            menu.getItem(i).setVisible(isCameraIdle());
1966        }
1967
1968        return true;
1969    }
1970
1971    @Override
1972    public boolean onCreateOptionsMenu(Menu menu) {
1973        super.onCreateOptionsMenu(menu);
1974
1975        if (mIsImageCaptureIntent) {
1976            // No options menu for attach mode.
1977            return false;
1978        } else {
1979            addBaseMenuItems(menu);
1980        }
1981        return true;
1982    }
1983
1984    private void addBaseMenuItems(Menu menu) {
1985        MenuHelper.addSwitchModeMenuItem(menu, ModePicker.MODE_VIDEO, new Runnable() {
1986            public void run() {
1987                switchToOtherMode(ModePicker.MODE_VIDEO);
1988            }
1989        });
1990        MenuHelper.addSwitchModeMenuItem(menu, ModePicker.MODE_PANORAMA, new Runnable() {
1991            public void run() {
1992                switchToOtherMode(ModePicker.MODE_PANORAMA);
1993            }
1994        });
1995
1996        if (mNumberOfCameras > 1) {
1997            menu.add(R.string.switch_camera_id)
1998                    .setOnMenuItemClickListener(new OnMenuItemClickListener() {
1999                public boolean onMenuItemClick(MenuItem item) {
2000                    CameraSettings.writePreferredCameraId(mPreferences,
2001                            ((mCameraId == mFrontCameraId)
2002                            ? mBackCameraId : mFrontCameraId));
2003                    onSharedPreferenceChanged();
2004                    return true;
2005                }
2006            }).setIcon(android.R.drawable.ic_menu_camera);
2007        }
2008    }
2009
2010    private boolean switchToOtherMode(int mode) {
2011        if (isFinishing()) return false;
2012        mImageSaver.waitDone();
2013        MenuHelper.gotoMode(mode, Camera.this);
2014        mHandler.removeMessages(FIRST_TIME_INIT);
2015        finish();
2016        return true;
2017    }
2018
2019    public boolean onModeChanged(int mode) {
2020        if (mode != ModePicker.MODE_CAMERA) {
2021            return switchToOtherMode(mode);
2022        } else {
2023            return true;
2024        }
2025    }
2026
2027    public void onSharedPreferenceChanged() {
2028        // ignore the events after "onPause()"
2029        if (mPausing) return;
2030
2031        boolean recordLocation = RecordLocationPreference.get(
2032                mPreferences, getContentResolver());
2033        mLocationManager.recordLocation(recordLocation);
2034
2035        int cameraId = CameraSettings.readPreferredCameraId(mPreferences);
2036        if (mCameraId != cameraId) {
2037            // Restart the activity to have a crossfade animation.
2038            // TODO: Use SurfaceTexture to implement a better and faster
2039            // animation.
2040            if (mIsImageCaptureIntent) {
2041                // If the intent is camera capture, stay in camera capture mode.
2042                MenuHelper.gotoCameraMode(this, getIntent());
2043            } else {
2044                MenuHelper.gotoCameraMode(this);
2045            }
2046
2047            finish();
2048        } else {
2049            setCameraParametersWhenIdle(UPDATE_PARAM_PREFERENCE);
2050        }
2051
2052        int exposureValue = CameraSettings.readExposure(mPreferences);
2053        updateExposureOnScreenIndicator(exposureValue);
2054    }
2055
2056    @Override
2057    public void onUserInteraction() {
2058        super.onUserInteraction();
2059        keepScreenOnAwhile();
2060    }
2061
2062    private void resetScreenOn() {
2063        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
2064        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2065    }
2066
2067    private void keepScreenOnAwhile() {
2068        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
2069        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2070        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
2071    }
2072
2073    public void onRestorePreferencesClicked() {
2074        if (mPausing) return;
2075        Runnable runnable = new Runnable() {
2076            public void run() {
2077                restorePreferences();
2078            }
2079        };
2080        MenuHelper.confirmAction(this,
2081                getString(R.string.confirm_restore_title),
2082                getString(R.string.confirm_restore_message),
2083                runnable);
2084    }
2085
2086    private void restorePreferences() {
2087        // Reset the zoom. Zoom value is not stored in preference.
2088        if (mParameters.isZoomSupported()) {
2089            mZoomValue = 0;
2090            setCameraParametersWhenIdle(UPDATE_PARAM_ZOOM);
2091            mZoomControl.setZoomIndex(0);
2092        }
2093        if (mIndicatorControlContainer != null) {
2094            mIndicatorControlContainer.dismissSettingPopup();
2095            CameraSettings.restorePreferences(Camera.this, mPreferences,
2096                    mParameters);
2097            mIndicatorControlContainer.reloadPreferences();
2098            onSharedPreferenceChanged();
2099        }
2100    }
2101
2102    public void onOverriddenPreferencesClicked() {
2103        if (mPausing) return;
2104        if (mNotSelectableToast == null) {
2105            String str = getResources().getString(R.string.not_selectable_in_scene_mode);
2106            mNotSelectableToast = Toast.makeText(Camera.this, str, Toast.LENGTH_SHORT);
2107        }
2108        mNotSelectableToast.show();
2109    }
2110
2111    private void showSharePopup() {
2112        mImageSaver.waitDone();
2113        Uri uri = mThumbnail.getUri();
2114        if (mSharePopup == null || !uri.equals(mSharePopup.getUri())) {
2115            // SharePopup window takes the mPreviewPanel as its size reference.
2116            mSharePopup = new SharePopup(this, uri, mThumbnail.getBitmap(),
2117                    mOrientationCompensation, mPreviewPanel);
2118        }
2119        mSharePopup.showAtLocation(mThumbnailView, Gravity.NO_GRAVITY, 0, 0);
2120    }
2121
2122    @Override
2123    public void onFaceDetection(Face[] faces, android.hardware.Camera camera) {
2124        mFaceView.setFaces(faces);
2125    }
2126
2127    private void showTapToFocusToast() {
2128        // Show the toast.
2129        RotateLayout v = (RotateLayout) findViewById(R.id.tap_to_focus_prompt);
2130        v.setOrientation(mOrientationCompensation);
2131        v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.on_screen_hint_enter));
2132        v.setVisibility(View.VISIBLE);
2133        mHandler.sendEmptyMessageDelayed(DISMISS_TAP_TO_FOCUS_TOAST, 5000);
2134        // Clear the preference.
2135        Editor editor = mPreferences.edit();
2136        editor.putBoolean(CameraSettings.KEY_TAP_TO_FOCUS_PROMPT_SHOWN, false);
2137        editor.apply();
2138    }
2139
2140    private void initializeCapabilities() {
2141        mInitialParams = mCameraDevice.getParameters();
2142        mFocusManager.initializeParameters(mInitialParams);
2143        mFocusAreaSupported = (mInitialParams.getMaxNumFocusAreas() > 0
2144                && isSupported(Parameters.FOCUS_MODE_AUTO,
2145                        mInitialParams.getSupportedFocusModes()));
2146        mMeteringAreaSupported = (mInitialParams.getMaxNumMeteringAreas() > 0);
2147    }
2148}
2149