VideoCamera.java revision fe370991104ad90b0656d5cc897171cb5cb8780b
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.camera;
18
19import com.android.camera.ui.CameraPicker;
20import com.android.camera.ui.IndicatorControlContainer;
21import com.android.camera.ui.IndicatorControlWheelContainer;
22import com.android.camera.ui.RotateImageView;
23import com.android.camera.ui.SharePopup;
24import com.android.camera.ui.ZoomControl;
25
26import android.content.ActivityNotFoundException;
27import android.content.BroadcastReceiver;
28import android.content.ContentResolver;
29import android.content.ContentValues;
30import android.content.Context;
31import android.content.Intent;
32import android.content.IntentFilter;
33import android.content.res.Configuration;
34import android.graphics.Bitmap;
35import android.hardware.Camera.CameraInfo;
36import android.hardware.Camera.Parameters;
37import android.hardware.Camera.PictureCallback;
38import android.hardware.Camera.Size;
39import android.location.Location;
40import android.media.CamcorderProfile;
41import android.media.CameraProfile;
42import android.media.MediaRecorder;
43import android.net.Uri;
44import android.os.Bundle;
45import android.os.Handler;
46import android.os.Message;
47import android.os.ParcelFileDescriptor;
48import android.os.SystemClock;
49import android.provider.MediaStore;
50import android.provider.MediaStore.Video;
51import android.provider.Settings;
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.Window;
65import android.view.WindowManager;
66import android.widget.Button;
67import android.widget.ImageView;
68import android.widget.TextView;
69import android.widget.Toast;
70
71import java.io.File;
72import java.io.IOException;
73import java.text.SimpleDateFormat;
74import java.util.ArrayList;
75import java.util.Date;
76import java.util.HashMap;
77import java.util.Iterator;
78import java.util.List;
79
80/**
81 * The Camcorder activity.
82 */
83public class VideoCamera extends ActivityBase
84        implements CameraPreference.OnPreferenceChangedListener,
85        ShutterButton.OnShutterButtonListener, SurfaceHolder.Callback,
86        MediaRecorder.OnErrorListener, MediaRecorder.OnInfoListener,
87        ModePicker.OnModeChangeListener, View.OnTouchListener {
88
89    private static final String TAG = "videocamera";
90
91    private static final String LAST_THUMB_FILENAME = "video_last_thumb";
92
93    private static final int CHECK_DISPLAY_ROTATION = 3;
94    private static final int CLEAR_SCREEN_DELAY = 4;
95    private static final int UPDATE_RECORD_TIME = 5;
96    private static final int ENABLE_SHUTTER_BUTTON = 6;
97
98    private static final int SCREEN_DELAY = 2 * 60 * 1000;
99
100    // The brightness settings used when it is set to automatic in the system.
101    // The reason why it is set to 0.7 is just because 1.0 is too bright.
102    private static final float DEFAULT_CAMERA_BRIGHTNESS = 0.7f;
103
104    private static final long LOW_STORAGE_THRESHOLD = 512L * 1024L;
105
106    private static final boolean SWITCH_CAMERA = true;
107    private static final boolean SWITCH_VIDEO = false;
108
109    private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
110
111    private static final int[] TIME_LAPSE_VIDEO_QUALITY = {
112            CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
113            CamcorderProfile.QUALITY_TIME_LAPSE_720P,
114            CamcorderProfile.QUALITY_TIME_LAPSE_480P,
115            CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
116            CamcorderProfile.QUALITY_TIME_LAPSE_QCIF};
117
118    private static final int[] VIDEO_QUALITY = {
119            CamcorderProfile.QUALITY_1080P,
120            CamcorderProfile.QUALITY_720P,
121            CamcorderProfile.QUALITY_480P,
122            CamcorderProfile.QUALITY_CIF,
123            CamcorderProfile.QUALITY_QCIF};
124
125    /**
126     * An unpublished intent flag requesting to start recording straight away
127     * and return as soon as recording is stopped.
128     * TODO: consider publishing by moving into MediaStore.
129     */
130    private final static String EXTRA_QUICK_CAPTURE =
131            "android.intent.extra.quickCapture";
132
133    private boolean mSnapshotInProgress = false;
134    private PictureCallback mJpegPictureCallback;
135
136    private android.hardware.Camera mCameraDevice;
137    private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
138
139    private ComboPreferences mPreferences;
140    private PreferenceGroup mPreferenceGroup;
141
142    private PreviewFrameLayout mPreviewFrameLayout;
143    private SurfaceHolder mSurfaceHolder = null;
144    private IndicatorControlContainer mIndicatorControlContainer;
145    private View mReviewControl;
146
147    private Toast mNoShareToast;
148    // An review image having same size as preview. It is displayed when
149    // recording is stopped in capture intent.
150    private ImageView mReviewImage;
151    // A popup window that contains a bigger thumbnail and a list of apps to share.
152    private SharePopup mSharePopup;
153    // The bitmap of the last captured video thumbnail and the URI of the
154    // original video.
155    private Thumbnail mThumbnail;
156    // An imageview showing showing the last captured picture thumbnail.
157    private RotateImageView mThumbnailView;
158    private ModePicker mModePicker;
159    private ShutterButton mShutterButton;
160    private TextView mRecordingTimeView;
161
162    private boolean mIsVideoCaptureIntent;
163    private boolean mQuickCapture;
164
165    private boolean mOpenCameraFail = false;
166    private boolean mCameraDisabled = false;
167
168    private long mStorageSpace;
169
170    private MediaRecorder mMediaRecorder;
171    private boolean mMediaRecorderRecording = false;
172    private long mRecordingStartTime;
173    private boolean mRecordingTimeCountsDown = false;
174    private long mOnResumeTime;
175    // The video file that the hardware camera is about to record into
176    // (or is recording into.)
177    private String mVideoFilename;
178    private ParcelFileDescriptor mVideoFileDescriptor;
179
180    // The video file that has already been recorded, and that is being
181    // examined by the user.
182    private String mCurrentVideoFilename;
183    private Uri mCurrentVideoUri;
184    private ContentValues mCurrentVideoValues;
185
186    private CamcorderProfile mProfile;
187    // The array of video quality profiles supported by each camera(s). Here the
188    // cameraId is the index of the array to get the profile map which contain
189    // the set of quality string and its real quality of a camera.
190    private HashMap<String, Integer>[] mProfileQuality;
191    private HashMap<String, Integer>[] mTimeLapseProfileQuality;
192
193    // The video duration limit. 0 menas no limit.
194    private int mMaxVideoDurationInMs;
195
196    // Time Lapse parameters.
197    private boolean mCaptureTimeLapse = false;
198    // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
199    private int mTimeBetweenTimeLapseFrameCaptureMs = 0;
200    private View mTimeLapseLabel;
201    private View mPreviewBorder;
202
203    private int mDesiredPreviewWidth;
204    private int mDesiredPreviewHeight;
205
206    boolean mPausing = false;
207    boolean mPreviewing = false; // True if preview is started.
208    // The display rotation in degrees. This is only valid when mPreviewing is
209    // true.
210    private int mDisplayRotation;
211
212    private ContentResolver mContentResolver;
213
214    private final ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>();
215
216    private final Handler mHandler = new MainHandler();
217    private Parameters mParameters;
218
219    // multiple cameras support
220    private int mNumberOfCameras;
221    private int mCameraId;
222    private int mFrontCameraId;
223    private int mBackCameraId;
224
225    private GestureDetector mPopupGestureDetector;
226
227    private MyOrientationEventListener mOrientationListener;
228    // The degrees of the device rotated clockwise from its natural orientation.
229    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
230    // The orientation compensation for icons and thumbnails. Ex: if the value
231    // is 90, the UI components should be rotated 90 degrees counter-clockwise.
232    private int mOrientationCompensation = 0;
233    private int mOrientationHint; // the orientation hint for video playback
234
235    private static final int ZOOM_STOPPED = 0;
236    private static final int ZOOM_START = 1;
237    private static final int ZOOM_STOPPING = 2;
238
239    private int mZoomState = ZOOM_STOPPED;
240    private boolean mSmoothZoomSupported = false;
241    private int mZoomValue;  // The current zoom value.
242    private int mZoomMax;
243    private int mTargetZoomValue;
244    private ZoomControl mZoomControl;
245    private final ZoomListener mZoomListener = new ZoomListener();
246
247    // This Handler is used to post message back onto the main thread of the
248    // application
249    private class MainHandler extends Handler {
250        @Override
251        public void handleMessage(Message msg) {
252            switch (msg.what) {
253
254                case ENABLE_SHUTTER_BUTTON:
255                    mShutterButton.setEnabled(true);
256                    break;
257
258                case CLEAR_SCREEN_DELAY: {
259                    getWindow().clearFlags(
260                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
261                    break;
262                }
263
264                case UPDATE_RECORD_TIME: {
265                    updateRecordingTime();
266                    break;
267                }
268
269                case CHECK_DISPLAY_ROTATION: {
270                    // Restart the preview if display rotation has changed.
271                    // Sometimes this happens when the device is held upside
272                    // down and camera app is opened. Rotation animation will
273                    // take some time and the rotation value we have got may be
274                    // wrong. Framework does not have a callback for this now.
275                    if ((Util.getDisplayRotation(VideoCamera.this) != mDisplayRotation)
276                            && !mMediaRecorderRecording) {
277                        startPreview();
278                    }
279                    if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
280                        mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
281                    }
282                    break;
283                }
284
285                default:
286                    Log.v(TAG, "Unhandled message: " + msg.what);
287                    break;
288            }
289        }
290    }
291
292    private BroadcastReceiver mReceiver = null;
293
294    private class MyBroadcastReceiver extends BroadcastReceiver {
295        @Override
296        public void onReceive(Context context, Intent intent) {
297            String action = intent.getAction();
298            if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
299                updateAndShowStorageHint();
300                stopVideoRecording();
301            } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
302                updateAndShowStorageHint();
303                updateThumbnailButton();
304            } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
305                // SD card unavailable
306                // handled in ACTION_MEDIA_EJECT
307            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
308                Toast.makeText(VideoCamera.this,
309                        getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
310            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
311                updateAndShowStorageHint();
312            }
313        }
314    }
315
316    private String createName(long dateTaken) {
317        Date date = new Date(dateTaken);
318        SimpleDateFormat dateFormat = new SimpleDateFormat(
319                getString(R.string.video_file_name_format));
320
321        return dateFormat.format(date);
322    }
323
324    @Override
325    public void onCreate(Bundle icicle) {
326        super.onCreate(icicle);
327
328        Window win = getWindow();
329
330        // Overright the brightness settings if it is automatic
331        int mode = Settings.System.getInt(
332                getContentResolver(),
333                Settings.System.SCREEN_BRIGHTNESS_MODE,
334                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
335        if (mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) {
336            WindowManager.LayoutParams winParams = win.getAttributes();
337            winParams.screenBrightness = DEFAULT_CAMERA_BRIGHTNESS;
338            win.setAttributes(winParams);
339        }
340
341        mPreferences = new ComboPreferences(this);
342        CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
343        mCameraId = CameraSettings.readPreferredCameraId(mPreferences);
344
345        //Testing purpose. Launch a specific camera through the intent extras.
346        int intentCameraId = Util.getCameraFacingIntentExtras(this);
347        if (intentCameraId != -1) {
348            mCameraId = intentCameraId;
349        }
350
351        mPreferences.setLocalId(this, mCameraId);
352        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
353
354        mNumberOfCameras = CameraHolder.instance().getNumberOfCameras();
355
356        /*
357         * To reduce startup time, we start the preview in another thread.
358         * We make sure the preview is started at the end of onCreate.
359         */
360        Thread startPreviewThread = new Thread(new Runnable() {
361            public void run() {
362                try {
363                    mCameraDevice = Util.openCamera(VideoCamera.this, mCameraId);
364                    readVideoPreferences();
365                    startPreview();
366                } catch(CameraHardwareException e) {
367                    mOpenCameraFail = true;
368                } catch(CameraDisabledException e) {
369                    mCameraDisabled = true;
370                }
371            }
372        });
373        startPreviewThread.start();
374
375        mContentResolver = getContentResolver();
376
377        requestWindowFeature(Window.FEATURE_PROGRESS);
378        mIsVideoCaptureIntent = isVideoCaptureIntent();
379        setContentView(R.layout.video_camera);
380        if (mIsVideoCaptureIntent) {
381            findViewById(R.id.btn_cancel).setVisibility(View.VISIBLE);
382        } else {
383            initThumbnailButton();
384            mModePicker = (ModePicker) findViewById(R.id.mode_picker);
385            mModePicker.setVisibility(View.VISIBLE);
386            mModePicker.setOnModeChangeListener(this);
387        }
388
389        mPreviewFrameLayout = (PreviewFrameLayout)
390                findViewById(R.id.frame_layout);
391        mReviewImage = (ImageView) findViewById(R.id.review_image);
392        mModePicker = (ModePicker) findViewById(R.id.mode_picker);
393
394        // don't set mSurfaceHolder here. We have it set ONLY within
395        // surfaceCreated / surfaceDestroyed, other parts of the code
396        // assume that when it is set, the surface is also set.
397        SurfaceView preview = (SurfaceView) findViewById(R.id.camera_preview);
398        SurfaceHolder holder = preview.getHolder();
399        holder.addCallback(this);
400        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
401
402        mQuickCapture = getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
403
404        mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
405        mShutterButton.setBackgroundResource(R.drawable.btn_shutter_video);
406        mShutterButton.setOnShutterButtonListener(this);
407        mShutterButton.requestFocus();
408
409        mRecordingTimeView = (TextView) findViewById(R.id.recording_time);
410        mOrientationListener = new MyOrientationEventListener(VideoCamera.this);
411        mTimeLapseLabel = findViewById(R.id.time_lapse_label);
412        mPreviewBorder = findViewById(R.id.preview_border);
413
414        // Make sure preview is started.
415        try {
416            startPreviewThread.join();
417            if (mOpenCameraFail) {
418                Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
419                return;
420            } else if (mCameraDisabled) {
421                Util.showErrorAndFinish(this, R.string.camera_disabled);
422                return;
423            }
424        } catch (InterruptedException ex) {
425            // ignore
426        }
427
428        showTimeLapseUI(mCaptureTimeLapse);
429        resizeForPreviewAspectRatio();
430
431        mBackCameraId = CameraHolder.instance().getBackCameraId();
432        mFrontCameraId = CameraHolder.instance().getFrontCameraId();
433
434        // Initialize after startPreview becuase this need mParameters.
435        initializeZoomControl();
436        initializeIndicatorControl();
437        if ("true".equals(mParameters.get("video-snapshot-supported")) &&
438                !mIsVideoCaptureIntent) {
439            preview.setOnTouchListener(this);
440            mJpegPictureCallback = new JpegPictureCallback();
441        }
442    }
443
444    private void loadCameraPreferences() {
445        CameraSettings settings = new CameraSettings(this, mParameters,
446                mCameraId, CameraHolder.instance().getCameraInfo());
447        mPreferenceGroup = settings.getPreferenceGroup(R.xml.video_preferences);
448    }
449
450    private boolean collapseCameraControls() {
451        if ((mIndicatorControlContainer != null)
452                && mIndicatorControlContainer.dismissSettingPopup()) {
453            return true;
454        }
455        return false;
456    }
457
458    private void enableCameraControls(boolean enable) {
459        if (mIndicatorControlContainer != null) {
460            mIndicatorControlContainer.setEnabled(enable);
461        }
462        if (mModePicker != null) mModePicker.setEnabled(enable);
463    }
464
465    private void initializeIndicatorControl() {
466        mIndicatorControlContainer =
467                (IndicatorControlContainer) findViewById(R.id.indicator_control);
468        if (mIndicatorControlContainer == null) return;
469        loadCameraPreferences();
470
471        final String[] SETTING_KEYS = {
472                    CameraSettings.KEY_VIDEO_EFFECT,
473                    CameraSettings.KEY_WHITE_BALANCE,
474                    CameraSettings.KEY_VIDEO_QUALITY};
475        final String[] OTHER_SETTING_KEYS = {
476                    CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL};
477
478        CameraPicker.setImageResourceId(R.drawable.ic_switch_video_facing_holo_light);
479        mIndicatorControlContainer.initialize(this, mPreferenceGroup,
480                CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
481                mParameters.isZoomSupported(), SETTING_KEYS, OTHER_SETTING_KEYS);
482        mIndicatorControlContainer.setListener(this);
483        mPopupGestureDetector = new GestureDetector(this,
484                new PopupGestureListener());
485    }
486
487    public static int roundOrientation(int orientation) {
488        return ((orientation + 45) / 90 * 90) % 360;
489    }
490
491    private class MyOrientationEventListener
492            extends OrientationEventListener {
493        public MyOrientationEventListener(Context context) {
494            super(context);
495        }
496
497        @Override
498        public void onOrientationChanged(int orientation) {
499            if (mMediaRecorderRecording) return;
500            // We keep the last known orientation. So if the user first orient
501            // the camera then point the camera to floor or sky, we still have
502            // the correct orientation.
503            if (orientation == ORIENTATION_UNKNOWN) return;
504            mOrientation = roundOrientation(orientation);
505            // When the screen is unlocked, display rotation may change. Always
506            // calculate the up-to-date orientationCompensation.
507            int orientationCompensation = mOrientation
508                    + Util.getDisplayRotation(VideoCamera.this);
509            if (mOrientationCompensation != orientationCompensation) {
510                mOrientationCompensation = orientationCompensation;
511                setOrientationIndicator(mOrientationCompensation);
512            }
513        }
514    }
515
516    private void setOrientationIndicator(int degree) {
517        if (mThumbnailView != null) mThumbnailView.setDegree(degree);
518        if (mModePicker != null) mModePicker.setDegree(degree);
519        if (mSharePopup != null) mSharePopup.setOrientation(degree);
520        if (mIndicatorControlContainer != null) mIndicatorControlContainer.setDegree(degree);
521    }
522
523    private void startPlayVideoActivity() {
524        Intent intent = new Intent(Intent.ACTION_VIEW, mCurrentVideoUri);
525        try {
526            startActivity(intent);
527        } catch (ActivityNotFoundException ex) {
528            Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
529        }
530    }
531
532    @OnClickAttr
533    public void onThumbnailClicked(View v) {
534        if (!mMediaRecorderRecording && mThumbnail != null) {
535            showSharePopup();
536        }
537    }
538
539    @OnClickAttr
540    public void onRetakeButtonClicked(View v) {
541        deleteCurrentVideo();
542        hideAlert();
543    }
544
545    @OnClickAttr
546    public void onPlayButtonClicked(View v) {
547        startPlayVideoActivity();
548    }
549
550    @OnClickAttr
551    public void onDoneButtonClicked(View v) {
552        doReturnToCaller(true);
553    }
554
555    @OnClickAttr
556    public void onCancelButtonClicked(View v) {
557        stopVideoRecording();
558        doReturnToCaller(false);
559    }
560
561    public void onShutterButtonFocus(ShutterButton button, boolean pressed) {
562        // Do nothing (everything happens in onShutterButtonClick).
563    }
564
565    private void onStopVideoRecording(boolean valid) {
566        stopVideoRecording();
567        if (mIsVideoCaptureIntent) {
568            if (mQuickCapture) {
569                doReturnToCaller(valid);
570            } else {
571                showAlert();
572            }
573        } else {
574            getThumbnail();
575        }
576    }
577
578    public void onShutterButtonClick(ShutterButton button) {
579        switch (button.getId()) {
580            case R.id.shutter_button:
581                if (collapseCameraControls()) return;
582                boolean stop = mMediaRecorderRecording;
583
584                if (stop) {
585                    onStopVideoRecording(true);
586                } else {
587                    startVideoRecording();
588                }
589                mShutterButton.setEnabled(false);
590
591                // Keep the shutter button disabled when in video capture intent
592                // mode and recording is stopped. It'll be re-enabled when
593                // re-take button is clicked.
594                if (!(mIsVideoCaptureIntent && stop)) {
595                    mHandler.sendEmptyMessageDelayed(
596                            ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
597                }
598                break;
599        }
600    }
601
602    private OnScreenHint mStorageHint;
603
604    private void updateAndShowStorageHint() {
605        mStorageSpace = Storage.getAvailableSpace();
606        showStorageHint();
607    }
608
609    private void showStorageHint() {
610        String errorMessage = null;
611        if (mStorageSpace == Storage.UNAVAILABLE) {
612            errorMessage = getString(R.string.no_storage);
613        } else if (mStorageSpace == Storage.PREPARING) {
614            errorMessage = getString(R.string.preparing_sd);
615        } else if (mStorageSpace == Storage.UNKNOWN_SIZE) {
616            errorMessage = getString(R.string.access_sd_fail);
617        } else if (mStorageSpace < LOW_STORAGE_THRESHOLD) {
618            errorMessage = getString(R.string.spaceIsLow_content);
619        }
620
621        if (errorMessage != null) {
622            if (mStorageHint == null) {
623                mStorageHint = OnScreenHint.makeText(this, errorMessage);
624            } else {
625                mStorageHint.setText(errorMessage);
626            }
627            mStorageHint.show();
628        } else if (mStorageHint != null) {
629            mStorageHint.cancel();
630            mStorageHint = null;
631        }
632    }
633
634    private void readVideoPreferences() {
635        String quality = mPreferences.getString(
636                CameraSettings.KEY_VIDEO_QUALITY,
637                CameraSettings.DEFAULT_VIDEO_QUALITY_VALUE);
638        boolean videoQualityHigh =
639                CameraSettings.getVideoQuality(this, quality);
640
641        // Set video quality.
642        Intent intent = getIntent();
643        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
644            int extraVideoQuality =
645                    intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
646            if (extraVideoQuality > 0) {
647                quality = getString(R.string.pref_video_quality_high);
648            } else {  // 0 is mms.
649                quality = getString(R.string.pref_video_quality_mms);
650            }
651        }
652
653        // Set video duration limit. The limit is read from the preference,
654        // unless it is specified in the intent.
655        if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
656            int seconds =
657                    intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
658            mMaxVideoDurationInMs = 1000 * seconds;
659        } else {
660            mMaxVideoDurationInMs =
661                    CameraSettings.getVideoDurationInMillis(this, quality, mCameraId);
662        }
663
664        // Read time lapse recording interval.
665        String frameIntervalStr = mPreferences.getString(
666                CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
667                getString(R.string.pref_video_time_lapse_frame_interval_default));
668        mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
669
670        mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
671        int profileQuality = getProfileQuality(mCameraId, quality, mCaptureTimeLapse);
672        mProfile = CamcorderProfile.get(mCameraId, profileQuality);
673        getDesiredPreviewSize();
674    }
675
676    int getProfileQuality(int cameraId, String quality, boolean captureTimeLapse) {
677        HashMap<String, Integer>[] qualityMap;
678        if (captureTimeLapse) {
679            if (mTimeLapseProfileQuality == null) {
680                mTimeLapseProfileQuality = new HashMap[
681                        CameraHolder.instance().getNumberOfCameras()];
682            }
683            qualityMap = mTimeLapseProfileQuality;
684            if (qualityMap[cameraId] == null) {
685                qualityMap[cameraId] = buildProfileQuality(cameraId, TIME_LAPSE_VIDEO_QUALITY);
686            }
687        } else {
688            if (mProfileQuality == null) {
689                mProfileQuality = new HashMap[
690                        CameraHolder.instance().getNumberOfCameras()];
691            }
692            qualityMap = mProfileQuality;
693            if (qualityMap[cameraId] == null) {
694                qualityMap[cameraId] = buildProfileQuality(cameraId, VIDEO_QUALITY);
695            }
696        }
697        return qualityMap[cameraId].get(quality);
698    }
699
700    HashMap<String, Integer> buildProfileQuality(int cameraId,
701            int qualityList[]) {
702        HashMap<String, Integer> qualityMap = new HashMap<String, Integer>();
703        int highestQuality = -1, secondHighestQuality = -1,
704                lastEffectiveQuality = -1;
705        for (int i = 0; i < qualityList.length; i++) {
706            if (CamcorderProfile.hasProfile(cameraId, qualityList[i])) {
707                if (highestQuality == -1) {
708                    highestQuality = qualityList[i];
709                } else if (secondHighestQuality == -1) {
710                    secondHighestQuality = qualityList[i];
711                }
712                lastEffectiveQuality = qualityList[i];
713            }
714        }
715        if (secondHighestQuality == -1) {
716            secondHighestQuality = highestQuality;
717        }
718        qualityMap.put(getString(R.string.pref_video_quality_high), highestQuality);
719        qualityMap.put(getString(R.string.pref_video_quality_low), secondHighestQuality);
720        qualityMap.put(getString(R.string.pref_video_quality_youtube), highestQuality);
721        qualityMap.put(getString(R.string.pref_video_quality_mms), lastEffectiveQuality);
722        return qualityMap;
723    }
724
725    private void getDesiredPreviewSize() {
726        mParameters = mCameraDevice.getParameters();
727        if (mParameters.getSupportedVideoSizes() == null) {
728            mDesiredPreviewWidth = mProfile.videoFrameWidth;
729            mDesiredPreviewHeight = mProfile.videoFrameHeight;
730        } else {  // Driver supports separates outputs for preview and video.
731            List<Size> sizes = mParameters.getSupportedPreviewSizes();
732            Size preferred = mParameters.getPreferredPreviewSizeForVideo();
733            int product = preferred.width * preferred.height;
734            Iterator it = sizes.iterator();
735            // Remove the preview sizes that are not preferred.
736            while (it.hasNext()) {
737                Size size = (Size) it.next();
738                if (size.width * size.height > product) {
739                    it.remove();
740                }
741            }
742            Size optimalSize = Util.getOptimalPreviewSize(this, sizes,
743                (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
744            mDesiredPreviewWidth = optimalSize.width;
745            mDesiredPreviewHeight = optimalSize.height;
746        }
747        Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
748                ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
749    }
750
751    private void resizeForPreviewAspectRatio() {
752        mPreviewFrameLayout.setAspectRatio(
753                (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
754    }
755
756    @Override
757    protected void onResume() {
758        super.onResume();
759        mPausing = false;
760        if (mOpenCameraFail || mCameraDisabled) return;
761        mZoomValue = 0;
762
763        mReviewImage.setVisibility(View.GONE);
764
765        // Start orientation listener as soon as possible because it takes
766        // some time to get first orientation.
767        mOrientationListener.enable();
768        if (!mPreviewing) {
769            try {
770                mCameraDevice = Util.openCamera(this, mCameraId);
771                readVideoPreferences();
772                resizeForPreviewAspectRatio();
773                startPreview();
774            } catch(CameraHardwareException e) {
775                Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
776                return;
777            } catch(CameraDisabledException e) {
778                Util.showErrorAndFinish(this, R.string.camera_disabled);
779                return;
780            }
781        }
782        keepScreenOnAwhile();
783
784        // install an intent filter to receive SD card related events.
785        IntentFilter intentFilter =
786                new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
787        intentFilter.addAction(Intent.ACTION_MEDIA_EJECT);
788        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
789        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
790        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
791        intentFilter.addDataScheme("file");
792        mReceiver = new MyBroadcastReceiver();
793        registerReceiver(mReceiver, intentFilter);
794        mStorageSpace = Storage.getAvailableSpace();
795
796        mHandler.postDelayed(new Runnable() {
797            public void run() {
798                showStorageHint();
799            }
800        }, 200);
801
802        if (!mIsVideoCaptureIntent) {
803            updateThumbnailButton();  // Update the last video thumbnail.
804            mModePicker.setCurrentMode(ModePicker.MODE_VIDEO);
805        }
806
807        if (mPreviewing) {
808            mOnResumeTime = SystemClock.uptimeMillis();
809            mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
810        }
811    }
812
813    private void setPreviewDisplay(SurfaceHolder holder) {
814        try {
815            mCameraDevice.setPreviewDisplay(holder);
816        } catch (Throwable ex) {
817            closeCamera();
818            throw new RuntimeException("setPreviewDisplay failed", ex);
819        }
820    }
821
822    private void startPreview() {
823        Log.v(TAG, "startPreview");
824        mCameraDevice.setErrorCallback(mErrorCallback);
825
826        if (mPreviewing == true) {
827            mCameraDevice.stopPreview();
828            mPreviewing = false;
829        }
830        setPreviewDisplay(mSurfaceHolder);
831        mDisplayRotation = Util.getDisplayRotation(this);
832        int orientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
833        mCameraDevice.setDisplayOrientation(orientation);
834        setCameraParameters();
835
836        try {
837            mCameraDevice.startPreview();
838        } catch (Throwable ex) {
839            closeCamera();
840            throw new RuntimeException("startPreview failed", ex);
841        }
842        mZoomState = ZOOM_STOPPED;
843        mPreviewing = true;
844    }
845
846    private void closeCamera() {
847        Log.v(TAG, "closeCamera");
848        if (mCameraDevice == null) {
849            Log.d(TAG, "already stopped.");
850            return;
851        }
852        CameraHolder.instance().release();
853        mCameraDevice = null;
854        mPreviewing = false;
855    }
856
857    private void finishRecorderAndCloseCamera() {
858        // This is similar to what mShutterButton.performClick() does,
859        // but not quite the same.
860        if (mMediaRecorderRecording) {
861            if (mIsVideoCaptureIntent) {
862                stopVideoRecording();
863                showAlert();
864            } else {
865                stopVideoRecording();
866                getThumbnail();
867            }
868        } else {
869            stopVideoRecording();
870        }
871        closeCamera();
872    }
873
874    @Override
875    protected void onPause() {
876        super.onPause();
877        mPausing = true;
878
879        if (mIndicatorControlContainer != null) {
880            mIndicatorControlContainer.dismissSettingPopup();
881        }
882
883        finishRecorderAndCloseCamera();
884
885        if (mSharePopup != null) mSharePopup.dismiss();
886
887        if (mReceiver != null) {
888            unregisterReceiver(mReceiver);
889            mReceiver = null;
890        }
891        resetScreenOn();
892
893        if (!mIsVideoCaptureIntent && mThumbnail != null) {
894            mThumbnail.saveTo(new File(getFilesDir(), LAST_THUMB_FILENAME));
895        }
896
897        if (mStorageHint != null) {
898            mStorageHint.cancel();
899            mStorageHint = null;
900        }
901
902        mOrientationListener.disable();
903
904        mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
905    }
906
907    @Override
908    public void onUserInteraction() {
909        super.onUserInteraction();
910        if (!mMediaRecorderRecording) keepScreenOnAwhile();
911    }
912
913    @Override
914    public void onBackPressed() {
915        if (mPausing) return;
916        if (mMediaRecorderRecording) {
917            onStopVideoRecording(false);
918        } else if (!collapseCameraControls()) {
919            super.onBackPressed();
920        }
921    }
922
923    @Override
924    public boolean onKeyDown(int keyCode, KeyEvent event) {
925        // Do not handle any key if the activity is paused.
926        if (mPausing) {
927            return true;
928        }
929
930        switch (keyCode) {
931            case KeyEvent.KEYCODE_CAMERA:
932                if (event.getRepeatCount() == 0) {
933                    mShutterButton.performClick();
934                    return true;
935                }
936                break;
937            case KeyEvent.KEYCODE_DPAD_CENTER:
938                if (event.getRepeatCount() == 0) {
939                    mShutterButton.performClick();
940                    return true;
941                }
942                break;
943            case KeyEvent.KEYCODE_MENU:
944                if (mMediaRecorderRecording) return true;
945                break;
946        }
947
948        return super.onKeyDown(keyCode, event);
949    }
950
951    @Override
952    public boolean onKeyUp(int keyCode, KeyEvent event) {
953        switch (keyCode) {
954            case KeyEvent.KEYCODE_CAMERA:
955                mShutterButton.setPressed(false);
956                return true;
957        }
958        return super.onKeyUp(keyCode, event);
959    }
960
961    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
962        // Make sure we have a surface in the holder before proceeding.
963        if (holder.getSurface() == null) {
964            Log.d(TAG, "holder.getSurface() == null");
965            return;
966        }
967
968        Log.v(TAG, "surfaceChanged. w=" + w + ". h=" + h);
969
970        mSurfaceHolder = holder;
971
972        if (mPausing) {
973            // We're pausing, the screen is off and we already stopped
974            // video recording. We don't want to start the camera again
975            // in this case in order to conserve power.
976            // The fact that surfaceChanged is called _after_ an onPause appears
977            // to be legitimate since in that case the lockscreen always returns
978            // to portrait orientation possibly triggering the notification.
979            return;
980        }
981
982        // The mCameraDevice will be null if it is fail to connect to the
983        // camera hardware. In this case we will show a dialog and then
984        // finish the activity, so it's OK to ignore it.
985        if (mCameraDevice == null) return;
986
987        // Set preview display if the surface is being created. Preview was
988        // already started. Also restart the preview if display rotation has
989        // changed. Sometimes this happens when the device is held in portrait
990        // and camera app is opened. Rotation animation takes some time and
991        // display rotation in onCreate may not be what we want.
992        if (mPreviewing && (Util.getDisplayRotation(this) == mDisplayRotation)
993                && holder.isCreating()) {
994            setPreviewDisplay(holder);
995        } else {
996            stopVideoRecording();
997            startPreview();
998        }
999    }
1000
1001    public void surfaceCreated(SurfaceHolder holder) {
1002    }
1003
1004    public void surfaceDestroyed(SurfaceHolder holder) {
1005        mSurfaceHolder = null;
1006    }
1007
1008    private void gotoGallery() {
1009        MenuHelper.gotoCameraVideoGallery(this);
1010    }
1011
1012    @Override
1013    public boolean onCreateOptionsMenu(Menu menu) {
1014        super.onCreateOptionsMenu(menu);
1015
1016        if (mIsVideoCaptureIntent) {
1017            // No options menu for attach mode.
1018            return false;
1019        } else {
1020            addBaseMenuItems(menu);
1021        }
1022        return true;
1023    }
1024
1025    private boolean isVideoCaptureIntent() {
1026        String action = getIntent().getAction();
1027        return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
1028    }
1029
1030    private void doReturnToCaller(boolean valid) {
1031        Intent resultIntent = new Intent();
1032        int resultCode;
1033        if (valid) {
1034            resultCode = RESULT_OK;
1035            resultIntent.setData(mCurrentVideoUri);
1036        } else {
1037            resultCode = RESULT_CANCELED;
1038        }
1039        setResultEx(resultCode, resultIntent);
1040        finish();
1041    }
1042
1043    private void cleanupEmptyFile() {
1044        if (mVideoFilename != null) {
1045            File f = new File(mVideoFilename);
1046            if (f.length() == 0 && f.delete()) {
1047                Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
1048                mVideoFilename = null;
1049            }
1050        }
1051    }
1052
1053    // Prepares media recorder.
1054    private void initializeRecorder() {
1055        Log.v(TAG, "initializeRecorder");
1056        // If the mCameraDevice is null, then this activity is going to finish
1057        if (mCameraDevice == null) return;
1058
1059        if (mSurfaceHolder == null) {
1060            Log.v(TAG, "Surface holder is null. Wait for surface changed.");
1061            return;
1062        }
1063
1064        Intent intent = getIntent();
1065        Bundle myExtras = intent.getExtras();
1066
1067        long requestedSizeLimit = 0;
1068        if (mIsVideoCaptureIntent && myExtras != null) {
1069            Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1070            if (saveUri != null) {
1071                try {
1072                    mVideoFileDescriptor =
1073                            mContentResolver.openFileDescriptor(saveUri, "rw");
1074                    mCurrentVideoUri = saveUri;
1075                } catch (java.io.FileNotFoundException ex) {
1076                    // invalid uri
1077                    Log.e(TAG, ex.toString());
1078                }
1079            }
1080            requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1081        }
1082        mMediaRecorder = new MediaRecorder();
1083
1084        // Unlock the camera object before passing it to media recorder.
1085        mCameraDevice.unlock();
1086        mMediaRecorder.setCamera(mCameraDevice);
1087        if (!mCaptureTimeLapse) {
1088            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
1089        }
1090        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
1091        mMediaRecorder.setProfile(mProfile);
1092        mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
1093        if (mCaptureTimeLapse) {
1094            mMediaRecorder.setCaptureRate((1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs));
1095        }
1096
1097        // Set output file.
1098        // Try Uri in the intent first. If it doesn't exist, use our own
1099        // instead.
1100        if (mVideoFileDescriptor != null) {
1101            mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1102        } else {
1103            generateVideoFilename(mProfile.fileFormat);
1104            mMediaRecorder.setOutputFile(mVideoFilename);
1105        }
1106
1107        mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
1108
1109        // Set maximum file size.
1110        // remaining >= LOW_STORAGE_THRESHOLD at this point, reserve a quarter
1111        // of that to make it more likely that recording can complete
1112        // successfully.
1113        long maxFileSize = mStorageSpace - LOW_STORAGE_THRESHOLD / 4;
1114        if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1115            maxFileSize = requestedSizeLimit;
1116        }
1117
1118        try {
1119            mMediaRecorder.setMaxFileSize(maxFileSize);
1120        } catch (RuntimeException exception) {
1121            // We are going to ignore failure of setMaxFileSize here, as
1122            // a) The composer selected may simply not support it, or
1123            // b) The underlying media framework may not handle 64-bit range
1124            // on the size restriction.
1125        }
1126
1127        // See android.hardware.Camera.Parameters.setRotation for
1128        // documentation.
1129        int rotation = 0;
1130        if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1131            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1132            if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
1133                rotation = (info.orientation - mOrientation + 360) % 360;
1134            } else {  // back-facing camera
1135                rotation = (info.orientation + mOrientation) % 360;
1136            }
1137        }
1138        mMediaRecorder.setOrientationHint(rotation);
1139        mOrientationHint = rotation;
1140
1141        try {
1142            mMediaRecorder.prepare();
1143        } catch (IOException e) {
1144            Log.e(TAG, "prepare failed for " + mVideoFilename, e);
1145            releaseMediaRecorder();
1146            throw new RuntimeException(e);
1147        }
1148
1149        mMediaRecorder.setOnErrorListener(this);
1150        mMediaRecorder.setOnInfoListener(this);
1151    }
1152
1153    private void releaseMediaRecorder() {
1154        Log.v(TAG, "Releasing media recorder.");
1155        if (mMediaRecorder != null) {
1156            cleanupEmptyFile();
1157            mMediaRecorder.reset();
1158            mMediaRecorder.release();
1159            mMediaRecorder = null;
1160        }
1161        mVideoFilename = null;
1162        if (mVideoFileDescriptor != null) {
1163            try {
1164                mVideoFileDescriptor.close();
1165            } catch (IOException e) {
1166                Log.e(TAG, "Fail to close fd", e);
1167            }
1168            mVideoFileDescriptor = null;
1169        }
1170    }
1171
1172    private void generateVideoFilename(int outputFileFormat) {
1173        long dateTaken = System.currentTimeMillis();
1174        String title = createName(dateTaken);
1175        String filename = title + ".3gp"; // Used when emailing.
1176        String mime = "video/3gpp";
1177        if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1178            filename = title + ".mp4";
1179            mime = "video/mp4";
1180        }
1181        mVideoFilename = Storage.DIRECTORY + '/' + filename;
1182        mCurrentVideoValues = new ContentValues(7);
1183        mCurrentVideoValues.put(Video.Media.TITLE, title);
1184        mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
1185        mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
1186        mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
1187        mCurrentVideoValues.put(Video.Media.DATA, mVideoFilename);
1188        Log.v(TAG, "New video filename: " + mVideoFilename);
1189    }
1190
1191    private void addVideoToMediaStore() {
1192        if (mVideoFileDescriptor == null) {
1193            Uri videoTable = Uri.parse("content://media/external/video/media");
1194            mCurrentVideoValues.put(Video.Media.SIZE,
1195                    new File(mCurrentVideoFilename).length());
1196            long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
1197            if (duration > 0) {
1198                if (mCaptureTimeLapse) {
1199                    duration = getTimeLapseVideoLength(duration);
1200                }
1201                mCurrentVideoValues.put(Video.Media.DURATION, duration);
1202            } else {
1203                Log.w(TAG, "Video duration <= 0 : " + duration);
1204            }
1205            try {
1206                mCurrentVideoUri = mContentResolver.insert(videoTable,
1207                        mCurrentVideoValues);
1208                sendBroadcast(new Intent(android.hardware.Camera.ACTION_NEW_VIDEO,
1209                        mCurrentVideoUri));
1210            } catch (Exception e) {
1211                // We failed to insert into the database. This can happen if
1212                // the SD card is unmounted.
1213                mCurrentVideoUri = null;
1214                mCurrentVideoFilename = null;
1215            } finally {
1216                Log.v(TAG, "Current video URI: " + mCurrentVideoUri);
1217            }
1218        }
1219        mCurrentVideoValues = null;
1220    }
1221
1222    private void deleteCurrentVideo() {
1223        // Remove the video and the uri if the uri is not passed in by intent.
1224        if (mCurrentVideoFilename != null) {
1225            deleteVideoFile(mCurrentVideoFilename);
1226            mCurrentVideoFilename = null;
1227            if (mCurrentVideoUri != null) {
1228                mContentResolver.delete(mCurrentVideoUri, null, null);
1229                mCurrentVideoUri = null;
1230            }
1231        }
1232        updateAndShowStorageHint();
1233    }
1234
1235    private void deleteVideoFile(String fileName) {
1236        Log.v(TAG, "Deleting video " + fileName);
1237        File f = new File(fileName);
1238        if (!f.delete()) {
1239            Log.v(TAG, "Could not delete " + fileName);
1240        }
1241    }
1242
1243    private void addBaseMenuItems(Menu menu) {
1244        MenuHelper.addSwitchModeMenuItem(menu, ModePicker.MODE_CAMERA, new Runnable() {
1245            public void run() {
1246                switchToOtherMode(ModePicker.MODE_CAMERA);
1247            }
1248        });
1249        MenuHelper.addSwitchModeMenuItem(menu, ModePicker.MODE_PANORAMA, new Runnable() {
1250            public void run() {
1251                switchToOtherMode(ModePicker.MODE_PANORAMA);
1252            }
1253        });
1254        MenuItem gallery = menu.add(R.string.camera_gallery_photos_text)
1255                .setOnMenuItemClickListener(
1256                    new OnMenuItemClickListener() {
1257                        public boolean onMenuItemClick(MenuItem item) {
1258                            gotoGallery();
1259                            return true;
1260                        }
1261                    });
1262        gallery.setIcon(android.R.drawable.ic_menu_gallery);
1263        mGalleryItems.add(gallery);
1264
1265        if (mNumberOfCameras > 1) {
1266            menu.add(R.string.switch_camera_id)
1267                    .setOnMenuItemClickListener(new OnMenuItemClickListener() {
1268                public boolean onMenuItemClick(MenuItem item) {
1269                    CameraSettings.writePreferredCameraId(mPreferences,
1270                            ((mCameraId == mFrontCameraId)
1271                            ? mBackCameraId : mFrontCameraId));
1272                    onSharedPreferenceChanged();
1273                    return true;
1274                }
1275            }).setIcon(android.R.drawable.ic_menu_camera);
1276        }
1277    }
1278
1279    private PreferenceGroup filterPreferenceScreenByIntent(
1280            PreferenceGroup screen) {
1281        Intent intent = getIntent();
1282        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
1283            CameraSettings.removePreferenceFromScreen(screen,
1284                    CameraSettings.KEY_VIDEO_QUALITY);
1285        }
1286
1287        if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
1288            CameraSettings.removePreferenceFromScreen(screen,
1289                    CameraSettings.KEY_VIDEO_QUALITY);
1290        }
1291        return screen;
1292    }
1293
1294    // from MediaRecorder.OnErrorListener
1295    public void onError(MediaRecorder mr, int what, int extra) {
1296        Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
1297        if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1298            // We may have run out of space on the sdcard.
1299            stopVideoRecording();
1300            updateAndShowStorageHint();
1301        }
1302    }
1303
1304    // from MediaRecorder.OnInfoListener
1305    public void onInfo(MediaRecorder mr, int what, int extra) {
1306        if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
1307            if (mMediaRecorderRecording) onStopVideoRecording(true);
1308        } else if (what
1309                == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
1310            if (mMediaRecorderRecording) onStopVideoRecording(true);
1311
1312            // Show the toast.
1313            Toast.makeText(VideoCamera.this, R.string.video_reach_size_limit,
1314                           Toast.LENGTH_LONG).show();
1315        }
1316    }
1317
1318    /*
1319     * Make sure we're not recording music playing in the background, ask the
1320     * MediaPlaybackService to pause playback.
1321     */
1322    private void pauseAudioPlayback() {
1323        // Shamelessly copied from MediaPlaybackService.java, which
1324        // should be public, but isn't.
1325        Intent i = new Intent("com.android.music.musicservicecommand");
1326        i.putExtra("command", "pause");
1327
1328        sendBroadcast(i);
1329    }
1330
1331    // For testing.
1332    public boolean isRecording() {
1333        return mMediaRecorderRecording;
1334    }
1335
1336    private void startVideoRecording() {
1337        Log.v(TAG, "startVideoRecording");
1338
1339        updateAndShowStorageHint();
1340        if (mStorageSpace < LOW_STORAGE_THRESHOLD) {
1341            Log.v(TAG, "Storage issue, ignore the start request");
1342            return;
1343        }
1344
1345        initializeRecorder();
1346        if (mMediaRecorder == null) {
1347            Log.e(TAG, "Fail to initialize media recorder");
1348            return;
1349        }
1350
1351        pauseAudioPlayback();
1352
1353        try {
1354            mMediaRecorder.start(); // Recording is now started
1355        } catch (RuntimeException e) {
1356            Log.e(TAG, "Could not start media recorder. ", e);
1357            releaseMediaRecorder();
1358            // If start fails, frameworks will not lock the camera for us.
1359            mCameraDevice.lock();
1360            return;
1361        }
1362        enableCameraControls(false);
1363
1364        mMediaRecorderRecording = true;
1365        mRecordingStartTime = SystemClock.uptimeMillis();
1366        showRecordingUI(true);
1367
1368        updateRecordingTime();
1369        keepScreenOn();
1370    }
1371
1372    private void showRecordingUI(boolean recording) {
1373        if (recording) {
1374            mShutterButton.setBackgroundResource(R.drawable.btn_shutter_video_recording);
1375            mRecordingTimeView.setText("");
1376            mRecordingTimeView.setVisibility(View.VISIBLE);
1377            if (mReviewControl != null) mReviewControl.setVisibility(View.GONE);
1378            if (mCaptureTimeLapse) {
1379                if (Util.isTabletUI()) {
1380                    ((IndicatorControlWheelContainer) mIndicatorControlContainer)
1381                            .startTimeLapseAnimation(
1382                                    mTimeBetweenTimeLapseFrameCaptureMs,
1383                                    mRecordingStartTime);
1384                }
1385            }
1386        } else {
1387            mShutterButton.setBackgroundResource(R.drawable.btn_shutter_video);
1388            mRecordingTimeView.setVisibility(View.GONE);
1389            if (mReviewControl != null) mReviewControl.setVisibility(View.VISIBLE);
1390            if (mCaptureTimeLapse) {
1391                if (Util.isTabletUI()) {
1392                    ((IndicatorControlWheelContainer) mIndicatorControlContainer)
1393                            .stopTimeLapseAnimation();
1394                }
1395            }
1396        }
1397    }
1398
1399    private void getThumbnail() {
1400        if (mCurrentVideoUri != null) {
1401            Bitmap videoFrame = Thumbnail.createVideoThumbnail(mCurrentVideoFilename,
1402                    mPreviewFrameLayout.getWidth());
1403            if (videoFrame != null) {
1404                mThumbnail = new Thumbnail(mCurrentVideoUri, videoFrame, 0);
1405                mThumbnailView.setBitmap(mThumbnail.getBitmap());
1406            }
1407        }
1408    }
1409
1410    private void showAlert() {
1411        if (mCurrentVideoFilename != null) {
1412            Bitmap bitmap = Thumbnail.createVideoThumbnail(mCurrentVideoFilename,
1413                    mPreviewFrameLayout.getWidth());
1414            if (bitmap != null) {
1415                // MetadataRetriever already rotates the thumbnail. We should rotate
1416                // it back (and mirror if it is front-facing camera).
1417                CameraInfo[] info = CameraHolder.instance().getCameraInfo();
1418                if (info[mCameraId].facing == CameraInfo.CAMERA_FACING_BACK) {
1419                    bitmap = Util.rotateAndMirror(bitmap, -mOrientationHint, false);
1420                } else {
1421                    bitmap = Util.rotateAndMirror(bitmap, -mOrientationHint, true);
1422                }
1423                mReviewImage.setImageBitmap(bitmap);
1424                mReviewImage.setVisibility(View.VISIBLE);
1425            }
1426        }
1427
1428        mShutterButton.setVisibility(View.GONE);
1429        mIndicatorControlContainer.setVisibility(View.GONE);
1430        int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play};
1431        for (int id : pickIds) {
1432            Util.fadeIn(findViewById(id));
1433        }
1434    }
1435
1436    private void hideAlert() {
1437        mReviewImage.setVisibility(View.INVISIBLE);
1438        mShutterButton.setEnabled(true);
1439        enableCameraControls(true);
1440
1441        int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play};
1442        for (int id : pickIds) {
1443            (findViewById(id)).setVisibility(View.GONE);
1444        }
1445        Util.fadeIn(mShutterButton);
1446        Util.fadeIn(mIndicatorControlContainer);
1447
1448        if (mCaptureTimeLapse) {
1449            showTimeLapseUI(true);
1450        }
1451    }
1452
1453    private boolean isAlertVisible() {
1454        return this.mReviewImage.getVisibility() == View.VISIBLE;
1455    }
1456
1457    private void stopVideoRecording() {
1458        Log.v(TAG, "stopVideoRecording");
1459        if (mMediaRecorderRecording) {
1460            boolean shouldAddToMediaStore = false;
1461            mMediaRecorder.setOnErrorListener(null);
1462            mMediaRecorder.setOnInfoListener(null);
1463            try {
1464                mMediaRecorder.stop();
1465                mCurrentVideoFilename = mVideoFilename;
1466                Log.v(TAG, "Setting current video filename: "
1467                        + mCurrentVideoFilename);
1468                shouldAddToMediaStore = true;
1469            } catch (RuntimeException e) {
1470                Log.e(TAG, "stop fail",  e);
1471                if (mVideoFilename != null) deleteVideoFile(mVideoFilename);
1472            }
1473            mMediaRecorderRecording = false;
1474            showRecordingUI(false);
1475            if (!mIsVideoCaptureIntent) {
1476                enableCameraControls(true);
1477            }
1478            keepScreenOnAwhile();
1479            if (shouldAddToMediaStore && mStorageSpace >= LOW_STORAGE_THRESHOLD) {
1480                addVideoToMediaStore();
1481            }
1482        }
1483        releaseMediaRecorder();  // always release media recorder
1484    }
1485
1486    private void resetScreenOn() {
1487        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1488        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1489    }
1490
1491    private void keepScreenOnAwhile() {
1492        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1493        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1494        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1495    }
1496
1497    private void keepScreenOn() {
1498        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1499        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1500    }
1501
1502    private void initThumbnailButton() {
1503        mThumbnailView = (RotateImageView) findViewById(R.id.thumbnail);
1504        mThumbnailView.setVisibility(View.VISIBLE);
1505        // Load the thumbnail from the disk.
1506        mThumbnail = Thumbnail.loadFrom(new File(getFilesDir(), LAST_THUMB_FILENAME));
1507    }
1508
1509    private void updateThumbnailButton() {
1510        if (mThumbnail == null || !Util.isUriValid(mThumbnail.getUri(), mContentResolver)) {
1511            mThumbnail = Thumbnail.getLastVideoThumbnail(mContentResolver);
1512        }
1513        if (mThumbnail != null) {
1514            mThumbnailView.setBitmap(mThumbnail.getBitmap());
1515        } else {
1516            mThumbnailView.setBitmap(null);
1517        }
1518    }
1519
1520    private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
1521        long seconds = milliSeconds / 1000; // round down to compute seconds
1522        long minutes = seconds / 60;
1523        long hours = minutes / 60;
1524        long remainderMinutes = minutes - (hours * 60);
1525        long remainderSeconds = seconds - (minutes * 60);
1526
1527        StringBuilder timeStringBuilder = new StringBuilder();
1528
1529        // Hours
1530        if (hours > 0) {
1531            if (hours < 10) {
1532                timeStringBuilder.append('0');
1533            }
1534            timeStringBuilder.append(hours);
1535
1536            timeStringBuilder.append(':');
1537        }
1538
1539        // Minutes
1540        if (remainderMinutes < 10) {
1541            timeStringBuilder.append('0');
1542        }
1543        timeStringBuilder.append(remainderMinutes);
1544        timeStringBuilder.append(':');
1545
1546        // Seconds
1547        if (remainderSeconds < 10) {
1548            timeStringBuilder.append('0');
1549        }
1550        timeStringBuilder.append(remainderSeconds);
1551
1552        // Centi seconds
1553        if (displayCentiSeconds) {
1554            timeStringBuilder.append('.');
1555            long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
1556            if (remainderCentiSeconds < 10) {
1557                timeStringBuilder.append('0');
1558            }
1559            timeStringBuilder.append(remainderCentiSeconds);
1560        }
1561
1562        return timeStringBuilder.toString();
1563    }
1564
1565    private long getTimeLapseVideoLength(long deltaMs) {
1566        // For better approximation calculate fractional number of frames captured.
1567        // This will update the video time at a higher resolution.
1568        double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
1569        return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
1570    }
1571
1572    private void updateRecordingTime() {
1573        if (!mMediaRecorderRecording) {
1574            return;
1575        }
1576        long now = SystemClock.uptimeMillis();
1577        long delta = now - mRecordingStartTime;
1578
1579        // Starting a minute before reaching the max duration
1580        // limit, we'll countdown the remaining time instead.
1581        boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1582                && delta >= mMaxVideoDurationInMs - 60000);
1583
1584        long deltaAdjusted = delta;
1585        if (countdownRemainingTime) {
1586            deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
1587        }
1588        String text;
1589
1590        long targetNextUpdateDelay;
1591        if (!mCaptureTimeLapse) {
1592            text = millisecondToTimeString(deltaAdjusted, false);
1593            targetNextUpdateDelay = 1000;
1594        } else {
1595            // The length of time lapse video is different from the length
1596            // of the actual wall clock time elapsed. Display the video length
1597            // only in format hh:mm:ss.dd, where dd are the centi seconds.
1598            text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
1599            targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
1600        }
1601
1602        mRecordingTimeView.setText(text);
1603
1604        if (mRecordingTimeCountsDown != countdownRemainingTime) {
1605            // Avoid setting the color on every update, do it only
1606            // when it needs changing.
1607            mRecordingTimeCountsDown = countdownRemainingTime;
1608
1609            int color = getResources().getColor(countdownRemainingTime
1610                    ? R.color.recording_time_remaining_text
1611                    : R.color.recording_time_elapsed_text);
1612
1613            mRecordingTimeView.setTextColor(color);
1614        }
1615
1616        long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
1617        mHandler.sendEmptyMessageDelayed(
1618                UPDATE_RECORD_TIME, actualNextUpdateDelay);
1619    }
1620
1621    private static boolean isSupported(String value, List<String> supported) {
1622        return supported == null ? false : supported.indexOf(value) >= 0;
1623    }
1624
1625    private void setCameraParameters() {
1626        mParameters = mCameraDevice.getParameters();
1627
1628        mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
1629        mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
1630
1631        // Set flash mode.
1632        String flashMode = mPreferences.getString(
1633                CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
1634                getString(R.string.pref_camera_video_flashmode_default));
1635        List<String> supportedFlash = mParameters.getSupportedFlashModes();
1636        if (isSupported(flashMode, supportedFlash)) {
1637            mParameters.setFlashMode(flashMode);
1638        } else {
1639            flashMode = mParameters.getFlashMode();
1640            if (flashMode == null) {
1641                flashMode = getString(
1642                        R.string.pref_camera_flashmode_no_flash);
1643            }
1644        }
1645
1646        // Set white balance parameter.
1647        String whiteBalance = mPreferences.getString(
1648                CameraSettings.KEY_WHITE_BALANCE,
1649                getString(R.string.pref_camera_whitebalance_default));
1650        if (isSupported(whiteBalance,
1651                mParameters.getSupportedWhiteBalance())) {
1652            mParameters.setWhiteBalance(whiteBalance);
1653        } else {
1654            whiteBalance = mParameters.getWhiteBalance();
1655            if (whiteBalance == null) {
1656                whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1657            }
1658        }
1659
1660        // Set zoom.
1661        if (mParameters.isZoomSupported()) {
1662            mParameters.setZoom(mZoomValue);
1663        }
1664
1665        // Set continuous autofocus.
1666        List<String> supportedFocus = mParameters.getSupportedFocusModes();
1667        if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
1668            mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
1669        }
1670
1671        mParameters.setRecordingHint(true);
1672
1673        // Set JPEG quality.
1674        int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1675                CameraProfile.QUALITY_HIGH);
1676        mParameters.setJpegQuality(jpegQuality);
1677
1678        mCameraDevice.setParameters(mParameters);
1679        // Keep preview size up to date.
1680        mParameters = mCameraDevice.getParameters();
1681    }
1682
1683    private boolean switchToOtherMode(int mode) {
1684        if (isFinishing() || mMediaRecorderRecording) return false;
1685        MenuHelper.gotoMode(mode, VideoCamera.this);
1686        finish();
1687        return true;
1688    }
1689
1690    public boolean onModeChanged(int mode) {
1691        if (mode != ModePicker.MODE_VIDEO) {
1692            return switchToOtherMode(mode);
1693        } else {
1694            return true;
1695        }
1696    }
1697
1698    @Override
1699    public void onConfigurationChanged(Configuration config) {
1700        super.onConfigurationChanged(config);
1701    }
1702
1703    public void onOverriddenPreferencesClicked() {
1704    }
1705
1706    public void onRestorePreferencesClicked() {
1707        Runnable runnable = new Runnable() {
1708            public void run() {
1709                restorePreferences();
1710            }
1711        };
1712        MenuHelper.confirmAction(this,
1713                getString(R.string.confirm_restore_title),
1714                getString(R.string.confirm_restore_message),
1715                runnable);
1716    }
1717
1718    private void restorePreferences() {
1719        // Reset the zoom. Zoom value is not stored in preference.
1720        if (mParameters.isZoomSupported()) {
1721            mZoomValue = 0;
1722            setCameraParameters();
1723            mZoomControl.setZoomIndex(0);
1724        }
1725
1726        if (mIndicatorControlContainer != null) {
1727            mIndicatorControlContainer.dismissSettingPopup();
1728            CameraSettings.restorePreferences(VideoCamera.this, mPreferences,
1729                    mParameters);
1730            mIndicatorControlContainer.reloadPreferences();
1731            onSharedPreferenceChanged();
1732        }
1733    }
1734
1735    public void onSharedPreferenceChanged() {
1736        // ignore the events after "onPause()" or preview has not started yet
1737        if (mPausing) return;
1738        synchronized (mPreferences) {
1739            // If mCameraDevice is not ready then we can set the parameter in
1740            // startPreview().
1741            if (mCameraDevice == null) return;
1742
1743            // TODO: apply goofy face effect here.
1744
1745            // Check if camera id is changed.
1746            int cameraId = CameraSettings.readPreferredCameraId(mPreferences);
1747            if (mCameraId != cameraId) {
1748                // Restart the activity to have a crossfade animation.
1749                // TODO: Use SurfaceTexture to implement a better and faster
1750                // animation.
1751                if (mIsVideoCaptureIntent) {
1752                    // If the intent is video capture, stay in video capture mode.
1753                    MenuHelper.gotoVideoMode(this, getIntent());
1754                } else {
1755                    MenuHelper.gotoVideoMode(this);
1756                }
1757                finish();
1758            } else {
1759                readVideoPreferences();
1760                // We need to restart the preview if preview size is changed.
1761                Size size = mParameters.getPreviewSize();
1762                if (size.width != mDesiredPreviewWidth
1763                        || size.height != mDesiredPreviewHeight) {
1764                    mCameraDevice.stopPreview();
1765                    resizeForPreviewAspectRatio();
1766                    startPreview(); // Parameters will be set in startPreview().
1767                } else {
1768                    setCameraParameters();
1769                }
1770            }
1771            showTimeLapseUI(mCaptureTimeLapse);
1772        }
1773    }
1774
1775    private void showTimeLapseUI(boolean enable) {
1776        if (mTimeLapseLabel != null) {
1777            mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.GONE);
1778        }
1779        if (mPreviewBorder != null) {
1780            mPreviewBorder.setBackgroundResource(enable
1781                    ? R.drawable.border_preview_time_lapse
1782                    : R.drawable.border_preview);
1783        }
1784
1785    }
1786
1787    private void showSharePopup() {
1788        Uri uri = mThumbnail.getUri();
1789        if (mSharePopup == null || !uri.equals(mSharePopup.getUri())) {
1790            mSharePopup = new SharePopup(this, uri, mThumbnail.getBitmap(),
1791                    mOrientationCompensation, mPreviewFrameLayout);
1792        }
1793        mSharePopup.showAtLocation(mThumbnailView, Gravity.NO_GRAVITY, 0, 0);
1794    }
1795
1796    @Override
1797    public boolean dispatchTouchEvent(MotionEvent m) {
1798        // Check if the popup window should be dismissed first.
1799        if (mPopupGestureDetector != null && mPopupGestureDetector.onTouchEvent(m)) {
1800            return true;
1801        }
1802
1803        return super.dispatchTouchEvent(m);
1804    }
1805
1806    private class PopupGestureListener extends
1807            GestureDetector.SimpleOnGestureListener {
1808        @Override
1809        public boolean onDown(MotionEvent e) {
1810            // Check if the popup window is visible.
1811            View popup = mIndicatorControlContainer.getActiveSettingPopup();
1812            if (popup == null) return false;
1813
1814            // Let popup window or indicator wheel handle the event by
1815            // themselves. Dismiss the popup window if users touch on other
1816            // areas.
1817            if (!Util.pointInView(e.getX(), e.getY(), popup)
1818                    && !Util.pointInView(e.getX(), e.getY(), mIndicatorControlContainer)) {
1819                mIndicatorControlContainer.dismissSettingPopup();
1820                // Let event fall through.
1821            }
1822            return false;
1823        }
1824    }
1825
1826    private class ZoomChangeListener implements ZoomControl.OnZoomChangedListener {
1827        // only for immediate zoom
1828        @Override
1829        public void onZoomValueChanged(int index) {
1830            VideoCamera.this.onZoomValueChanged(index);
1831        }
1832
1833        // only for smooth zoom
1834        @Override
1835        public void onZoomStateChanged(int state) {
1836            if (mPausing) return;
1837
1838            Log.v(TAG, "zoom picker state=" + state);
1839            if (state == ZoomControl.ZOOM_IN) {
1840                VideoCamera.this.onZoomValueChanged(mZoomMax);
1841            } else if (state == ZoomControl.ZOOM_OUT){
1842                VideoCamera.this.onZoomValueChanged(0);
1843            } else {
1844                mTargetZoomValue = -1;
1845                if (mZoomState == ZOOM_START) {
1846                    mZoomState = ZOOM_STOPPING;
1847                    mCameraDevice.stopSmoothZoom();
1848                }
1849            }
1850        }
1851    }
1852
1853    private void initializeZoomControl() {
1854        mZoomControl = (ZoomControl) findViewById(R.id.zoom_control);
1855        if (!mParameters.isZoomSupported()) return;
1856
1857        mZoomMax = mParameters.getMaxZoom();
1858        mSmoothZoomSupported = mParameters.isSmoothZoomSupported();
1859        mZoomControl.setZoomMax(mZoomMax);
1860        mZoomControl.setZoomIndex(mParameters.getZoom());
1861        mZoomControl.setSmoothZoomSupported(mSmoothZoomSupported);
1862        mZoomControl.setOnZoomChangeListener(new ZoomChangeListener()   );
1863        mCameraDevice.setZoomChangeListener(mZoomListener);
1864    }
1865
1866    private final class ZoomListener
1867            implements android.hardware.Camera.OnZoomChangeListener {
1868        @Override
1869        public void onZoomChange(int value, boolean stopped, android.hardware.Camera camera) {
1870            Log.v(TAG, "Zoom changed: value=" + value + ". stopped="+ stopped);
1871            mZoomValue = value;
1872
1873            // Update the UI when we get zoom value.
1874            mZoomControl.setZoomIndex(value);
1875
1876            // Keep mParameters up to date. We do not getParameter again in
1877            // takePicture. If we do not do this, wrong zoom value will be set.
1878            mParameters.setZoom(value);
1879
1880            if (stopped && mZoomState != ZOOM_STOPPED) {
1881                if (mTargetZoomValue != -1 && value != mTargetZoomValue) {
1882                    mCameraDevice.startSmoothZoom(mTargetZoomValue);
1883                    mZoomState = ZOOM_START;
1884                } else {
1885                    mZoomState = ZOOM_STOPPED;
1886                }
1887            }
1888        }
1889    }
1890
1891    private void onZoomValueChanged(int index) {
1892        // Not useful to change zoom value when the activity is paused.
1893        if (mPausing) return;
1894
1895        if (mSmoothZoomSupported) {
1896            if (mTargetZoomValue != index && mZoomState != ZOOM_STOPPED) {
1897                mTargetZoomValue = index;
1898                if (mZoomState == ZOOM_START) {
1899                    mZoomState = ZOOM_STOPPING;
1900                    mCameraDevice.stopSmoothZoom();
1901                }
1902            } else if (mZoomState == ZOOM_STOPPED && mZoomValue != index) {
1903                mTargetZoomValue = index;
1904                mCameraDevice.startSmoothZoom(index);
1905                mZoomState = ZOOM_START;
1906            }
1907        } else {
1908            mZoomValue = index;
1909            setCameraParameters();
1910        }
1911    }
1912
1913    // Preview area is touched. Take a picture.
1914    @Override
1915    public boolean onTouch(View v, MotionEvent e) {
1916        if (mPausing || mSnapshotInProgress || !mMediaRecorderRecording) {
1917            return false;
1918        }
1919
1920        Log.v(TAG, "Video snapshot start");
1921        mCameraDevice.takePicture(null, null, null, mJpegPictureCallback);
1922        mSnapshotInProgress = true;
1923        return true;
1924    }
1925
1926    private final class JpegPictureCallback implements PictureCallback {
1927        @Override
1928        public void onPictureTaken(byte [] jpegData, android.hardware.Camera camera) {
1929            Log.v(TAG, "onPictureTaken");
1930            mSnapshotInProgress = false;
1931            storeImage(jpegData, null);
1932        }
1933    }
1934
1935    private void storeImage(final byte[] data, Location loc) {
1936        long dateTaken = System.currentTimeMillis();
1937        String title = Util.createJpegName(dateTaken);
1938        int orientation = Exif.getOrientation(data);
1939        Uri uri = Storage.addImage(mContentResolver, title, dateTaken,
1940                loc, orientation, data);
1941        if (uri != null) {
1942            // Create a thumbnail whose width is equal or bigger than that of the preview.
1943            int ratio = (int) Math.ceil((double) mParameters.getPictureSize().width
1944                    / mPreviewFrameLayout.getWidth());
1945            int inSampleSize = Integer.highestOneBit(ratio);
1946            mThumbnail = Thumbnail.createThumbnail(data, orientation, inSampleSize, uri);
1947            if (mThumbnail != null) {
1948                mThumbnailView.setBitmap(mThumbnail.getBitmap());
1949            }
1950            Util.broadcastNewPicture(this, uri);
1951        }
1952    }
1953}
1954