VideoCamera.java revision 68798bfa946e812beb9190fa7c98a316392fbce6
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.IndicatorControl;
21import com.android.camera.ui.IndicatorControlWheelContainer;
22import com.android.camera.ui.RotateImageView;
23import com.android.camera.ui.SharePopup;
24import com.android.camera.ui.ZoomControl;
25import com.android.camera.ui.ZoomPicker;
26
27import android.content.ActivityNotFoundException;
28import android.content.BroadcastReceiver;
29import android.content.ContentResolver;
30import android.content.ContentValues;
31import android.content.Context;
32import android.content.Intent;
33import android.content.IntentFilter;
34import android.content.res.Configuration;
35import android.graphics.Bitmap;
36import android.hardware.Camera.CameraInfo;
37import android.hardware.Camera.Parameters;
38import android.hardware.Camera.Size;
39import android.media.CamcorderProfile;
40import android.media.MediaRecorder;
41import android.media.ThumbnailUtils;
42import android.net.Uri;
43import android.os.Bundle;
44import android.os.Handler;
45import android.os.Message;
46import android.os.ParcelFileDescriptor;
47import android.os.SystemClock;
48import android.provider.MediaStore;
49import android.provider.MediaStore.Video;
50import android.provider.Settings;
51import android.util.Log;
52import android.view.GestureDetector;
53import android.view.Gravity;
54import android.view.KeyEvent;
55import android.view.Menu;
56import android.view.MenuItem;
57import android.view.MenuItem.OnMenuItemClickListener;
58import android.view.MotionEvent;
59import android.view.OrientationEventListener;
60import android.view.SurfaceHolder;
61import android.view.SurfaceView;
62import android.view.View;
63import android.view.Window;
64import android.view.WindowManager;
65import android.view.animation.AlphaAnimation;
66import android.view.animation.Animation;
67import android.widget.Button;
68import android.widget.ImageView;
69import android.widget.TextView;
70import android.widget.Toast;
71
72import java.io.File;
73import java.io.IOException;
74import java.text.SimpleDateFormat;
75import java.util.ArrayList;
76import java.util.Date;
77import java.util.HashMap;
78import java.util.Iterator;
79import java.util.List;
80
81/**
82 * The Camcorder activity.
83 */
84public class VideoCamera extends ActivityBase
85        implements CameraPreference.OnPreferenceChangedListener,
86        ShutterButton.OnShutterButtonListener, SurfaceHolder.Callback,
87        MediaRecorder.OnErrorListener, MediaRecorder.OnInfoListener,
88        ModePicker.OnModeChangeListener {
89
90    private static final String TAG = "videocamera";
91
92    private static final String LAST_THUMB_FILENAME = "video_last_thumb";
93
94    private static final int CHECK_DISPLAY_ROTATION = 3;
95    private static final int CLEAR_SCREEN_DELAY = 4;
96    private static final int UPDATE_RECORD_TIME = 5;
97    private static final int ENABLE_SHUTTER_BUTTON = 6;
98
99    private static final int SCREEN_DELAY = 2 * 60 * 1000;
100
101    // The brightness settings used when it is set to automatic in the system.
102    // The reason why it is set to 0.7 is just because 1.0 is too bright.
103    private static final float DEFAULT_CAMERA_BRIGHTNESS = 0.7f;
104
105    private static final long LOW_STORAGE_THRESHOLD = 512L * 1024L;
106
107    private static final boolean SWITCH_CAMERA = true;
108    private static final boolean SWITCH_VIDEO = false;
109
110    private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
111
112    private static final int[] TIME_LAPSE_VIDEO_QUALITY = {
113            CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
114            CamcorderProfile.QUALITY_TIME_LAPSE_720P,
115            CamcorderProfile.QUALITY_TIME_LAPSE_480P,
116            CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
117            CamcorderProfile.QUALITY_TIME_LAPSE_QCIF};
118
119    private static final int[] VIDEO_QUALITY = {
120            CamcorderProfile.QUALITY_1080P,
121            CamcorderProfile.QUALITY_720P,
122            CamcorderProfile.QUALITY_480P,
123            CamcorderProfile.QUALITY_CIF,
124            CamcorderProfile.QUALITY_QCIF};
125
126    /**
127     * An unpublished intent flag requesting to start recording straight away
128     * and return as soon as recording is stopped.
129     * TODO: consider publishing by moving into MediaStore.
130     */
131    private final static String EXTRA_QUICK_CAPTURE =
132            "android.intent.extra.quickCapture";
133
134    private android.hardware.Camera mCameraDevice;
135    private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
136
137    private ComboPreferences mPreferences;
138    private PreferenceGroup mPreferenceGroup;
139
140    private PreviewFrameLayout mPreviewFrameLayout;
141    private SurfaceHolder mSurfaceHolder = null;
142    private IndicatorControl mIndicatorControl;
143    private View mReviewControl;
144
145    private Toast mNoShareToast;
146    // An review image having same size as preview. It is displayed when
147    // recording is stopped in capture intent.
148    private ImageView mReviewImage;
149    // A popup window that contains a bigger thumbnail and a list of apps to share.
150    private SharePopup mSharePopup;
151    // The bitmap of the last captured video thumbnail and the URI of the
152    // original video.
153    private Thumbnail mThumbnail;
154    // An imageview showing showing the last captured picture thumbnail.
155    private RotateImageView mThumbnailView;
156    private ModePicker mModePicker;
157    private ShutterButton mShutterButton;
158    private TextView mRecordingTimeView;
159
160    private boolean mIsVideoCaptureIntent;
161    private boolean mQuickCapture;
162
163    private boolean mOpenCameraFail = false;
164    private boolean mCameraDisabled = false;
165
166    private long mStorageSpace;
167
168    private MediaRecorder mMediaRecorder;
169    private boolean mMediaRecorderRecording = false;
170    private long mRecordingStartTime;
171    private boolean mRecordingTimeCountsDown = false;
172    private long mOnResumeTime;
173    // The video file that the hardware camera is about to record into
174    // (or is recording into.)
175    private String mVideoFilename;
176    private ParcelFileDescriptor mVideoFileDescriptor;
177
178    // The video file that has already been recorded, and that is being
179    // examined by the user.
180    private String mCurrentVideoFilename;
181    private Uri mCurrentVideoUri;
182    private ContentValues mCurrentVideoValues;
183
184    private CamcorderProfile mProfile;
185    // The array of video quality profiles supported by each camera(s). Here the
186    // cameraId is the index of the array to get the profile map which contain
187    // the set of quality string and its real quality of a camera.
188    private HashMap<String, Integer>[] mProfileQuality;
189    private HashMap<String, Integer>[] mTimeLapseProfileQuality;
190
191    // The video duration limit. 0 menas no limit.
192    private int mMaxVideoDurationInMs;
193
194    // Time Lapse parameters.
195    private boolean mCaptureTimeLapse = false;
196    // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
197    private int mTimeBetweenTimeLapseFrameCaptureMs = 0;
198    private View mTimeLapseLabel;
199    private View mPreviewBorder;
200
201    private int mDesiredPreviewWidth;
202    private int mDesiredPreviewHeight;
203
204    boolean mPausing = false;
205    boolean mPreviewing = false; // True if preview is started.
206    // The display rotation in degrees. This is only valid when mPreviewing is
207    // true.
208    private int mDisplayRotation;
209
210    private ContentResolver mContentResolver;
211
212    private final ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>();
213
214    private final Handler mHandler = new MainHandler();
215    private Parameters mParameters;
216
217    // multiple cameras support
218    private int mNumberOfCameras;
219    private int mCameraId;
220    private int mFrontCameraId;
221    private int mBackCameraId;
222
223    private GestureDetector mPopupGestureDetector;
224
225    private MyOrientationEventListener mOrientationListener;
226    // The degrees of the device rotated clockwise from its natural orientation.
227    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
228    // The orientation compensation for icons and thumbnails. Ex: if the value
229    // is 90, the UI components should be rotated 90 degrees counter-clockwise.
230    private int mOrientationCompensation = 0;
231    private int mOrientationHint; // the orientation hint for video playback
232
233    private static final int ZOOM_STOPPED = 0;
234    private static final int ZOOM_START = 1;
235    private static final int ZOOM_STOPPING = 2;
236
237    private int mZoomState = ZOOM_STOPPED;
238    private boolean mSmoothZoomSupported = false;
239    private int mZoomValue;  // The current zoom value.
240    private int mZoomMax;
241    private int mTargetZoomValue;
242    private ZoomControl mZoomControl;
243    private final ZoomListener mZoomListener = new ZoomListener();
244
245    // This Handler is used to post message back onto the main thread of the
246    // application
247    private class MainHandler extends Handler {
248        @Override
249        public void handleMessage(Message msg) {
250            switch (msg.what) {
251
252                case ENABLE_SHUTTER_BUTTON:
253                    mShutterButton.setEnabled(true);
254                    break;
255
256                case CLEAR_SCREEN_DELAY: {
257                    getWindow().clearFlags(
258                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
259                    break;
260                }
261
262                case UPDATE_RECORD_TIME: {
263                    updateRecordingTime();
264                    break;
265                }
266
267                case CHECK_DISPLAY_ROTATION: {
268                    // Restart the preview if display rotation has changed.
269                    // Sometimes this happens when the device is held upside
270                    // down and camera app is opened. Rotation animation will
271                    // take some time and the rotation value we have got may be
272                    // wrong. Framework does not have a callback for this now.
273                    if ((Util.getDisplayRotation(VideoCamera.this) != mDisplayRotation)
274                            && !mMediaRecorderRecording) {
275                        startPreview();
276                    }
277                    if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
278                        mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
279                    }
280                    break;
281                }
282
283                default:
284                    Log.v(TAG, "Unhandled message: " + msg.what);
285                    break;
286            }
287        }
288    }
289
290    private BroadcastReceiver mReceiver = null;
291
292    private class MyBroadcastReceiver extends BroadcastReceiver {
293        @Override
294        public void onReceive(Context context, Intent intent) {
295            String action = intent.getAction();
296            if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
297                updateAndShowStorageHint();
298                stopVideoRecording();
299            } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
300                updateAndShowStorageHint();
301                updateThumbnailButton();
302            } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
303                // SD card unavailable
304                // handled in ACTION_MEDIA_EJECT
305            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
306                Toast.makeText(VideoCamera.this,
307                        getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
308            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
309                updateAndShowStorageHint();
310            }
311        }
312    }
313
314    private String createName(long dateTaken) {
315        Date date = new Date(dateTaken);
316        SimpleDateFormat dateFormat = new SimpleDateFormat(
317                getString(R.string.video_file_name_format));
318
319        return dateFormat.format(date);
320    }
321
322    @Override
323    public void onCreate(Bundle icicle) {
324        super.onCreate(icicle);
325
326        Window win = getWindow();
327
328        // Overright the brightness settings if it is automatic
329        int mode = Settings.System.getInt(
330                getContentResolver(),
331                Settings.System.SCREEN_BRIGHTNESS_MODE,
332                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
333        if (mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) {
334            WindowManager.LayoutParams winParams = win.getAttributes();
335            winParams.screenBrightness = DEFAULT_CAMERA_BRIGHTNESS;
336            win.setAttributes(winParams);
337        }
338
339        mPreferences = new ComboPreferences(this);
340        CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
341        mCameraId = CameraSettings.readPreferredCameraId(mPreferences);
342
343        //Testing purpose. Launch a specific camera through the intent extras.
344        int intentCameraId = Util.getCameraFacingIntentExtras(this);
345        if (intentCameraId != -1) {
346            mCameraId = intentCameraId;
347        }
348
349        mPreferences.setLocalId(this, mCameraId);
350        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
351
352        mNumberOfCameras = CameraHolder.instance().getNumberOfCameras();
353
354        /*
355         * To reduce startup time, we start the preview in another thread.
356         * We make sure the preview is started at the end of onCreate.
357         */
358        Thread startPreviewThread = new Thread(new Runnable() {
359            public void run() {
360                try {
361                    mCameraDevice = Util.openCamera(VideoCamera.this, mCameraId);
362                    readVideoPreferences();
363                    startPreview();
364                } catch(CameraHardwareException e) {
365                    mOpenCameraFail = true;
366                } catch(CameraDisabledException e) {
367                    mCameraDisabled = true;
368                }
369            }
370        });
371        startPreviewThread.start();
372
373        mContentResolver = getContentResolver();
374
375        requestWindowFeature(Window.FEATURE_PROGRESS);
376        mIsVideoCaptureIntent = isVideoCaptureIntent();
377        if (mIsVideoCaptureIntent) {
378            setContentView(R.layout.video_camera_attach);
379
380            mReviewControl = findViewById(R.id.review_control);
381            mReviewControl.setVisibility(View.VISIBLE);
382            View retake = findViewById(R.id.btn_retake);
383            if (retake instanceof ImageView) {
384                ((ImageView) retake).setImageResource(R.drawable.btn_ic_review_retake_video);
385            } else {
386                ((Button) retake).setCompoundDrawablesWithIntrinsicBounds(
387                        R.drawable.ic_switch_video_holo_dark, 0, 0, 0);
388            }
389        } else {
390            setContentView(R.layout.video_camera);
391
392            initThumbnailButton();
393            mModePicker = (ModePicker) findViewById(R.id.mode_picker);
394            mModePicker.setVisibility(View.VISIBLE);
395            mModePicker.setOnModeChangeListener(this);
396        }
397
398        mPreviewFrameLayout = (PreviewFrameLayout)
399                findViewById(R.id.frame_layout);
400
401        mReviewImage = (ImageView) findViewById(R.id.review_image);
402        mModePicker = (ModePicker) findViewById(R.id.mode_picker);
403
404        // don't set mSurfaceHolder here. We have it set ONLY within
405        // surfaceCreated / surfaceDestroyed, other parts of the code
406        // assume that when it is set, the surface is also set.
407        SurfaceView preview = (SurfaceView) findViewById(R.id.camera_preview);
408        SurfaceHolder holder = preview.getHolder();
409        holder.addCallback(this);
410        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
411
412        mQuickCapture = getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
413
414        mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
415        mShutterButton.setBackgroundResource(R.drawable.btn_shutter_video);
416        mShutterButton.setOnShutterButtonListener(this);
417        mShutterButton.requestFocus();
418
419        mRecordingTimeView = (TextView) findViewById(R.id.recording_time);
420        mOrientationListener = new MyOrientationEventListener(VideoCamera.this);
421        mTimeLapseLabel = findViewById(R.id.time_lapse_label);
422        mPreviewBorder = findViewById(R.id.preview_border);
423
424        // Make sure preview is started.
425        try {
426            startPreviewThread.join();
427            if (mOpenCameraFail) {
428                Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
429                return;
430            } else if (mCameraDisabled) {
431                Util.showErrorAndFinish(this, R.string.camera_disabled);
432                return;
433            }
434        } catch (InterruptedException ex) {
435            // ignore
436        }
437
438        showTimeLapseUI(mCaptureTimeLapse);
439        resizeForPreviewAspectRatio();
440
441        mBackCameraId = CameraHolder.instance().getBackCameraId();
442        mFrontCameraId = CameraHolder.instance().getFrontCameraId();
443
444        // Initialize after startPreview becuase this need mParameters.
445        initializeZoomControl();
446        initializeIndicatorControl();
447    }
448
449    private void loadCameraPreferences() {
450        CameraSettings settings = new CameraSettings(this, mParameters,
451                mCameraId, CameraHolder.instance().getCameraInfo());
452        mPreferenceGroup = settings.getPreferenceGroup(R.xml.video_preferences);
453    }
454
455    private boolean collapseCameraControls() {
456        if (mIndicatorControl != null && mIndicatorControl.dismissSettingPopup()) {
457            return true;
458        }
459        return false;
460    }
461
462    private void enableCameraControls(boolean enable) {
463        if (mIndicatorControl != null) mIndicatorControl.setEnabled(enable);
464        if (mModePicker != null) mModePicker.setEnabled(enable);
465    }
466
467    private void initializeIndicatorControl() {
468        mIndicatorControl = (IndicatorControl) findViewById(R.id.indicator_control);
469        if (mIndicatorControl == null) return;
470        loadCameraPreferences();
471
472        final String[] SETTING_KEYS = {
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        mIndicatorControl.initialize(this, mPreferenceGroup,
480                CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
481                SETTING_KEYS, OTHER_SETTING_KEYS);
482        mIndicatorControl.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 (mIndicatorControl != null) mIndicatorControl.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 (mIndicatorControl != null) mIndicatorControl.dismissSettingPopup();
880
881        finishRecorderAndCloseCamera();
882
883        if (mSharePopup != null) mSharePopup.dismiss();
884
885        if (mReceiver != null) {
886            unregisterReceiver(mReceiver);
887            mReceiver = null;
888        }
889        resetScreenOn();
890
891        if (!mIsVideoCaptureIntent && mThumbnail != null) {
892            mThumbnail.saveTo(new File(getFilesDir(), LAST_THUMB_FILENAME));
893        }
894
895        if (mStorageHint != null) {
896            mStorageHint.cancel();
897            mStorageHint = null;
898        }
899
900        mOrientationListener.disable();
901
902        mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
903    }
904
905    @Override
906    public void onUserInteraction() {
907        super.onUserInteraction();
908        if (!mMediaRecorderRecording) keepScreenOnAwhile();
909    }
910
911    @Override
912    public void onBackPressed() {
913        if (mPausing) return;
914        if (mMediaRecorderRecording) {
915            onStopVideoRecording(false);
916        } else if (!collapseCameraControls()) {
917            super.onBackPressed();
918        }
919    }
920
921    @Override
922    public boolean onKeyDown(int keyCode, KeyEvent event) {
923        // Do not handle any key if the activity is paused.
924        if (mPausing) {
925            return true;
926        }
927
928        switch (keyCode) {
929            case KeyEvent.KEYCODE_CAMERA:
930                if (event.getRepeatCount() == 0) {
931                    mShutterButton.performClick();
932                    return true;
933                }
934                break;
935            case KeyEvent.KEYCODE_DPAD_CENTER:
936                if (event.getRepeatCount() == 0) {
937                    mShutterButton.performClick();
938                    return true;
939                }
940                break;
941            case KeyEvent.KEYCODE_MENU:
942                if (mMediaRecorderRecording) return true;
943                break;
944        }
945
946        return super.onKeyDown(keyCode, event);
947    }
948
949    @Override
950    public boolean onKeyUp(int keyCode, KeyEvent event) {
951        switch (keyCode) {
952            case KeyEvent.KEYCODE_CAMERA:
953                mShutterButton.setPressed(false);
954                return true;
955        }
956        return super.onKeyUp(keyCode, event);
957    }
958
959    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
960        // Make sure we have a surface in the holder before proceeding.
961        if (holder.getSurface() == null) {
962            Log.d(TAG, "holder.getSurface() == null");
963            return;
964        }
965
966        Log.v(TAG, "surfaceChanged. w=" + w + ". h=" + h);
967
968        mSurfaceHolder = holder;
969
970        if (mPausing) {
971            // We're pausing, the screen is off and we already stopped
972            // video recording. We don't want to start the camera again
973            // in this case in order to conserve power.
974            // The fact that surfaceChanged is called _after_ an onPause appears
975            // to be legitimate since in that case the lockscreen always returns
976            // to portrait orientation possibly triggering the notification.
977            return;
978        }
979
980        // The mCameraDevice will be null if it is fail to connect to the
981        // camera hardware. In this case we will show a dialog and then
982        // finish the activity, so it's OK to ignore it.
983        if (mCameraDevice == null) return;
984
985        // Set preview display if the surface is being created. Preview was
986        // already started. Also restart the preview if display rotation has
987        // changed. Sometimes this happens when the device is held in portrait
988        // and camera app is opened. Rotation animation takes some time and
989        // display rotation in onCreate may not be what we want.
990        if (mPreviewing && (Util.getDisplayRotation(this) == mDisplayRotation)
991                && holder.isCreating()) {
992            setPreviewDisplay(holder);
993        } else {
994            stopVideoRecording();
995            startPreview();
996        }
997    }
998
999    public void surfaceCreated(SurfaceHolder holder) {
1000    }
1001
1002    public void surfaceDestroyed(SurfaceHolder holder) {
1003        mSurfaceHolder = null;
1004    }
1005
1006    private void gotoGallery() {
1007        MenuHelper.gotoCameraVideoGallery(this);
1008    }
1009
1010    @Override
1011    public boolean onCreateOptionsMenu(Menu menu) {
1012        super.onCreateOptionsMenu(menu);
1013
1014        if (mIsVideoCaptureIntent) {
1015            // No options menu for attach mode.
1016            return false;
1017        } else {
1018            addBaseMenuItems(menu);
1019        }
1020        return true;
1021    }
1022
1023    private boolean isVideoCaptureIntent() {
1024        String action = getIntent().getAction();
1025        return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
1026    }
1027
1028    private void doReturnToCaller(boolean valid) {
1029        Intent resultIntent = new Intent();
1030        int resultCode;
1031        if (valid) {
1032            resultCode = RESULT_OK;
1033            resultIntent.setData(mCurrentVideoUri);
1034        } else {
1035            resultCode = RESULT_CANCELED;
1036        }
1037        setResultEx(resultCode, resultIntent);
1038        finish();
1039    }
1040
1041    private void cleanupEmptyFile() {
1042        if (mVideoFilename != null) {
1043            File f = new File(mVideoFilename);
1044            if (f.length() == 0 && f.delete()) {
1045                Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
1046                mVideoFilename = null;
1047            }
1048        }
1049    }
1050
1051    // Prepares media recorder.
1052    private void initializeRecorder() {
1053        Log.v(TAG, "initializeRecorder");
1054        // If the mCameraDevice is null, then this activity is going to finish
1055        if (mCameraDevice == null) return;
1056
1057        if (mSurfaceHolder == null) {
1058            Log.v(TAG, "Surface holder is null. Wait for surface changed.");
1059            return;
1060        }
1061
1062        Intent intent = getIntent();
1063        Bundle myExtras = intent.getExtras();
1064
1065        long requestedSizeLimit = 0;
1066        if (mIsVideoCaptureIntent && myExtras != null) {
1067            Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1068            if (saveUri != null) {
1069                try {
1070                    mVideoFileDescriptor =
1071                            mContentResolver.openFileDescriptor(saveUri, "rw");
1072                    mCurrentVideoUri = saveUri;
1073                } catch (java.io.FileNotFoundException ex) {
1074                    // invalid uri
1075                    Log.e(TAG, ex.toString());
1076                }
1077            }
1078            requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1079        }
1080        mMediaRecorder = new MediaRecorder();
1081
1082        // Unlock the camera object before passing it to media recorder.
1083        mCameraDevice.unlock();
1084        mMediaRecorder.setCamera(mCameraDevice);
1085        if (!mCaptureTimeLapse) {
1086            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
1087        }
1088        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
1089        mMediaRecorder.setProfile(mProfile);
1090        mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
1091        if (mCaptureTimeLapse) {
1092            mMediaRecorder.setCaptureRate((1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs));
1093        }
1094
1095        // Set output file.
1096        // Try Uri in the intent first. If it doesn't exist, use our own
1097        // instead.
1098        if (mVideoFileDescriptor != null) {
1099            mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1100        } else {
1101            generateVideoFilename(mProfile.fileFormat);
1102            mMediaRecorder.setOutputFile(mVideoFilename);
1103        }
1104
1105        mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
1106
1107        // Set maximum file size.
1108        // remaining >= LOW_STORAGE_THRESHOLD at this point, reserve a quarter
1109        // of that to make it more likely that recording can complete
1110        // successfully.
1111        long maxFileSize = mStorageSpace - LOW_STORAGE_THRESHOLD / 4;
1112        if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1113            maxFileSize = requestedSizeLimit;
1114        }
1115
1116        try {
1117            mMediaRecorder.setMaxFileSize(maxFileSize);
1118        } catch (RuntimeException exception) {
1119            // We are going to ignore failure of setMaxFileSize here, as
1120            // a) The composer selected may simply not support it, or
1121            // b) The underlying media framework may not handle 64-bit range
1122            // on the size restriction.
1123        }
1124
1125        // See android.hardware.Camera.Parameters.setRotation for
1126        // documentation.
1127        int rotation = 0;
1128        if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1129            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1130            if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
1131                rotation = (info.orientation - mOrientation + 360) % 360;
1132            } else {  // back-facing camera
1133                rotation = (info.orientation + mOrientation) % 360;
1134            }
1135        }
1136        mMediaRecorder.setOrientationHint(rotation);
1137        mOrientationHint = rotation;
1138
1139        try {
1140            mMediaRecorder.prepare();
1141        } catch (IOException e) {
1142            Log.e(TAG, "prepare failed for " + mVideoFilename, e);
1143            releaseMediaRecorder();
1144            throw new RuntimeException(e);
1145        }
1146
1147        mMediaRecorder.setOnErrorListener(this);
1148        mMediaRecorder.setOnInfoListener(this);
1149    }
1150
1151    private void releaseMediaRecorder() {
1152        Log.v(TAG, "Releasing media recorder.");
1153        if (mMediaRecorder != null) {
1154            cleanupEmptyFile();
1155            mMediaRecorder.reset();
1156            mMediaRecorder.release();
1157            mMediaRecorder = null;
1158        }
1159        mVideoFilename = null;
1160        if (mVideoFileDescriptor != null) {
1161            try {
1162                mVideoFileDescriptor.close();
1163            } catch (IOException e) {
1164                Log.e(TAG, "Fail to close fd", e);
1165            }
1166            mVideoFileDescriptor = null;
1167        }
1168    }
1169
1170    private void generateVideoFilename(int outputFileFormat) {
1171        long dateTaken = System.currentTimeMillis();
1172        String title = createName(dateTaken);
1173        String filename = title + ".3gp"; // Used when emailing.
1174        String mime = "video/3gpp";
1175        if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1176            filename = title + ".mp4";
1177            mime = "video/mp4";
1178        }
1179        mVideoFilename = Storage.DIRECTORY + '/' + filename;
1180        mCurrentVideoValues = new ContentValues(7);
1181        mCurrentVideoValues.put(Video.Media.TITLE, title);
1182        mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
1183        mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
1184        mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
1185        mCurrentVideoValues.put(Video.Media.DATA, mVideoFilename);
1186        Log.v(TAG, "New video filename: " + mVideoFilename);
1187    }
1188
1189    private void addVideoToMediaStore() {
1190        if (mVideoFileDescriptor == null) {
1191            Uri videoTable = Uri.parse("content://media/external/video/media");
1192            mCurrentVideoValues.put(Video.Media.SIZE,
1193                    new File(mCurrentVideoFilename).length());
1194            long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
1195            if (duration > 0) {
1196                if (mCaptureTimeLapse) {
1197                    duration = getTimeLapseVideoLength(duration);
1198                }
1199                mCurrentVideoValues.put(Video.Media.DURATION, duration);
1200            } else {
1201                Log.w(TAG, "Video duration <= 0 : " + duration);
1202            }
1203            try {
1204                mCurrentVideoUri = mContentResolver.insert(videoTable,
1205                        mCurrentVideoValues);
1206                sendBroadcast(new Intent(android.hardware.Camera.ACTION_NEW_VIDEO,
1207                        mCurrentVideoUri));
1208            } catch (Exception e) {
1209                // We failed to insert into the database. This can happen if
1210                // the SD card is unmounted.
1211                mCurrentVideoUri = null;
1212                mCurrentVideoFilename = null;
1213            } finally {
1214                Log.v(TAG, "Current video URI: " + mCurrentVideoUri);
1215            }
1216        }
1217        mCurrentVideoValues = null;
1218    }
1219
1220    private void deleteCurrentVideo() {
1221        // Remove the video and the uri if the uri is not passed in by intent.
1222        if (mCurrentVideoFilename != null) {
1223            deleteVideoFile(mCurrentVideoFilename);
1224            mCurrentVideoFilename = null;
1225            if (mCurrentVideoUri != null) {
1226                mContentResolver.delete(mCurrentVideoUri, null, null);
1227                mCurrentVideoUri = null;
1228            }
1229        }
1230        updateAndShowStorageHint();
1231    }
1232
1233    private void deleteVideoFile(String fileName) {
1234        Log.v(TAG, "Deleting video " + fileName);
1235        File f = new File(fileName);
1236        if (!f.delete()) {
1237            Log.v(TAG, "Could not delete " + fileName);
1238        }
1239    }
1240
1241    private void addBaseMenuItems(Menu menu) {
1242        MenuHelper.addSwitchModeMenuItem(menu, ModePicker.MODE_CAMERA, new Runnable() {
1243            public void run() {
1244                switchToOtherMode(ModePicker.MODE_CAMERA);
1245            }
1246        });
1247        MenuHelper.addSwitchModeMenuItem(menu, ModePicker.MODE_PANORAMA, new Runnable() {
1248            public void run() {
1249                switchToOtherMode(ModePicker.MODE_PANORAMA);
1250            }
1251        });
1252        MenuItem gallery = menu.add(R.string.camera_gallery_photos_text)
1253                .setOnMenuItemClickListener(
1254                    new OnMenuItemClickListener() {
1255                        public boolean onMenuItemClick(MenuItem item) {
1256                            gotoGallery();
1257                            return true;
1258                        }
1259                    });
1260        gallery.setIcon(android.R.drawable.ic_menu_gallery);
1261        mGalleryItems.add(gallery);
1262
1263        if (mNumberOfCameras > 1) {
1264            menu.add(R.string.switch_camera_id)
1265                    .setOnMenuItemClickListener(new OnMenuItemClickListener() {
1266                public boolean onMenuItemClick(MenuItem item) {
1267                    CameraSettings.writePreferredCameraId(mPreferences,
1268                            ((mCameraId == mFrontCameraId)
1269                            ? mBackCameraId : mFrontCameraId));
1270                    onSharedPreferenceChanged();
1271                    return true;
1272                }
1273            }).setIcon(android.R.drawable.ic_menu_camera);
1274        }
1275    }
1276
1277    private PreferenceGroup filterPreferenceScreenByIntent(
1278            PreferenceGroup screen) {
1279        Intent intent = getIntent();
1280        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
1281            CameraSettings.removePreferenceFromScreen(screen,
1282                    CameraSettings.KEY_VIDEO_QUALITY);
1283        }
1284
1285        if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
1286            CameraSettings.removePreferenceFromScreen(screen,
1287                    CameraSettings.KEY_VIDEO_QUALITY);
1288        }
1289        return screen;
1290    }
1291
1292    // from MediaRecorder.OnErrorListener
1293    public void onError(MediaRecorder mr, int what, int extra) {
1294        Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
1295        if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1296            // We may have run out of space on the sdcard.
1297            stopVideoRecording();
1298            updateAndShowStorageHint();
1299        }
1300    }
1301
1302    // from MediaRecorder.OnInfoListener
1303    public void onInfo(MediaRecorder mr, int what, int extra) {
1304        if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
1305            if (mMediaRecorderRecording) onStopVideoRecording(true);
1306        } else if (what
1307                == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
1308            if (mMediaRecorderRecording) onStopVideoRecording(true);
1309
1310            // Show the toast.
1311            Toast.makeText(VideoCamera.this, R.string.video_reach_size_limit,
1312                           Toast.LENGTH_LONG).show();
1313        }
1314    }
1315
1316    /*
1317     * Make sure we're not recording music playing in the background, ask the
1318     * MediaPlaybackService to pause playback.
1319     */
1320    private void pauseAudioPlayback() {
1321        // Shamelessly copied from MediaPlaybackService.java, which
1322        // should be public, but isn't.
1323        Intent i = new Intent("com.android.music.musicservicecommand");
1324        i.putExtra("command", "pause");
1325
1326        sendBroadcast(i);
1327    }
1328
1329    // For testing.
1330    public boolean isRecording() {
1331        return mMediaRecorderRecording;
1332    }
1333
1334    private void startVideoRecording() {
1335        Log.v(TAG, "startVideoRecording");
1336
1337        updateAndShowStorageHint();
1338        if (mStorageSpace < LOW_STORAGE_THRESHOLD) {
1339            Log.v(TAG, "Storage issue, ignore the start request");
1340            return;
1341        }
1342
1343        initializeRecorder();
1344        if (mMediaRecorder == null) {
1345            Log.e(TAG, "Fail to initialize media recorder");
1346            return;
1347        }
1348
1349        pauseAudioPlayback();
1350
1351        try {
1352            mMediaRecorder.start(); // Recording is now started
1353        } catch (RuntimeException e) {
1354            Log.e(TAG, "Could not start media recorder. ", e);
1355            releaseMediaRecorder();
1356            // If start fails, frameworks will not lock the camera for us.
1357            mCameraDevice.lock();
1358            return;
1359        }
1360        enableCameraControls(false);
1361
1362        mMediaRecorderRecording = true;
1363        mRecordingStartTime = SystemClock.uptimeMillis();
1364        showRecordingUI(true);
1365
1366        updateRecordingTime();
1367        keepScreenOn();
1368    }
1369
1370    private void showRecordingUI(boolean recording) {
1371        if (recording) {
1372            mShutterButton.setBackgroundResource(R.drawable.btn_shutter_video_recording);
1373            mRecordingTimeView.setText("");
1374            mRecordingTimeView.setVisibility(View.VISIBLE);
1375            if (mReviewControl != null) mReviewControl.setVisibility(View.GONE);
1376            if (mCaptureTimeLapse) {
1377                if (Util.isTabletUI()) {
1378                    ((IndicatorControlWheelContainer) mIndicatorControl).startTimeLapseAnimation(
1379                            mTimeBetweenTimeLapseFrameCaptureMs,
1380                            mRecordingStartTime);
1381                }
1382            }
1383        } else {
1384            mShutterButton.setBackgroundResource(R.drawable.btn_shutter_video);
1385            mRecordingTimeView.setVisibility(View.GONE);
1386            if (mReviewControl != null) mReviewControl.setVisibility(View.VISIBLE);
1387            if (mCaptureTimeLapse) {
1388                if (Util.isTabletUI()) {
1389                    ((IndicatorControlWheelContainer) mIndicatorControl).stopTimeLapseAnimation();
1390                }
1391            }
1392        }
1393    }
1394
1395    private void getThumbnail() {
1396        if (mCurrentVideoUri != null) {
1397            Bitmap videoFrame = ThumbnailUtils.createVideoThumbnail(
1398                    mCurrentVideoFilename, Video.Thumbnails.MINI_KIND);
1399            if (videoFrame != null) {
1400                mThumbnail = new Thumbnail(mCurrentVideoUri, videoFrame, 0);
1401                mThumbnailView.setBitmap(mThumbnail.getBitmap());
1402            }
1403        }
1404    }
1405
1406    private void showAlert() {
1407        if (!Util.isTabletUI()) {
1408            fadeOut(findViewById(R.id.shutter_button));
1409        }
1410        if (mCurrentVideoFilename != null) {
1411            Bitmap bitmap = ThumbnailUtils.createVideoThumbnail(
1412                    mCurrentVideoFilename, Video.Thumbnails.MINI_KIND);
1413            if (bitmap != null) {
1414                // MetadataRetriever already rotates the thumbnail. We should rotate
1415                // it back (and mirror if it is front-facing camera).
1416                CameraInfo[] info = CameraHolder.instance().getCameraInfo();
1417                if (info[mCameraId].facing == CameraInfo.CAMERA_FACING_BACK) {
1418                    bitmap = Util.rotateAndMirror(bitmap, -mOrientationHint, false);
1419                } else {
1420                    bitmap = Util.rotateAndMirror(bitmap, -mOrientationHint, true);
1421                }
1422                mReviewImage.setImageBitmap(bitmap);
1423                mReviewImage.setVisibility(View.VISIBLE);
1424            }
1425        }
1426        int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play};
1427        for (int id : pickIds) {
1428            View button = findViewById(id);
1429            fadeIn(((View) button.getParent()));
1430        }
1431
1432        // Remove the text of the cancel button
1433        View view = findViewById(R.id.btn_cancel);
1434        if (view instanceof Button) ((Button) view).setText("");
1435        showTimeLapseUI(false);
1436    }
1437
1438    private void hideAlert() {
1439        mReviewImage.setVisibility(View.INVISIBLE);
1440        fadeIn(findViewById(R.id.shutter_button));
1441        mShutterButton.setEnabled(true);
1442        enableCameraControls(true);
1443
1444        // Restore the text of the cancel button
1445        View view = findViewById(R.id.btn_cancel);
1446        if (view instanceof Button) {
1447            ((Button) view).setText(R.string.review_cancel);
1448        }
1449
1450        int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play};
1451        for (int id : pickIds) {
1452            View button = findViewById(id);
1453            ((View) button.getParent()).setVisibility(View.GONE);
1454        }
1455        if (mCaptureTimeLapse) {
1456            showTimeLapseUI(true);
1457        }
1458    }
1459
1460    private static void fadeIn(View view) {
1461        view.setVisibility(View.VISIBLE);
1462        Animation animation = new AlphaAnimation(0F, 1F);
1463        animation.setDuration(500);
1464        view.startAnimation(animation);
1465    }
1466
1467    private static void fadeOut(View view) {
1468        view.setVisibility(View.INVISIBLE);
1469        Animation animation = new AlphaAnimation(1F, 0F);
1470        animation.setDuration(500);
1471        view.startAnimation(animation);
1472    }
1473
1474    private boolean isAlertVisible() {
1475        return this.mReviewImage.getVisibility() == View.VISIBLE;
1476    }
1477
1478    private void stopVideoRecording() {
1479        Log.v(TAG, "stopVideoRecording");
1480        if (mMediaRecorderRecording) {
1481            boolean shouldAddToMediaStore = false;
1482            mMediaRecorder.setOnErrorListener(null);
1483            mMediaRecorder.setOnInfoListener(null);
1484            try {
1485                mMediaRecorder.stop();
1486                mCurrentVideoFilename = mVideoFilename;
1487                Log.v(TAG, "Setting current video filename: "
1488                        + mCurrentVideoFilename);
1489                shouldAddToMediaStore = true;
1490            } catch (RuntimeException e) {
1491                Log.e(TAG, "stop fail",  e);
1492                if (mVideoFilename != null) deleteVideoFile(mVideoFilename);
1493            }
1494            mMediaRecorderRecording = false;
1495            showRecordingUI(false);
1496            if (!mIsVideoCaptureIntent) {
1497                enableCameraControls(true);
1498            }
1499            keepScreenOnAwhile();
1500            if (shouldAddToMediaStore && mStorageSpace >= LOW_STORAGE_THRESHOLD) {
1501                addVideoToMediaStore();
1502            }
1503        }
1504        releaseMediaRecorder();  // always release media recorder
1505    }
1506
1507    private void resetScreenOn() {
1508        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1509        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1510    }
1511
1512    private void keepScreenOnAwhile() {
1513        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1514        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1515        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1516    }
1517
1518    private void keepScreenOn() {
1519        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1520        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1521    }
1522
1523    private void initThumbnailButton() {
1524        mThumbnailView = (RotateImageView) findViewById(R.id.thumbnail);
1525        // Load the thumbnail from the disk.
1526        mThumbnail = Thumbnail.loadFrom(new File(getFilesDir(), LAST_THUMB_FILENAME));
1527    }
1528
1529    private void updateThumbnailButton() {
1530        if (mThumbnail == null || !Util.isUriValid(mThumbnail.getUri(), mContentResolver)) {
1531            mThumbnail = Thumbnail.getLastVideoThumbnail(mContentResolver);
1532        }
1533        if (mThumbnail != null) {
1534            mThumbnailView.setBitmap(mThumbnail.getBitmap());
1535        } else {
1536            mThumbnailView.setBitmap(null);
1537        }
1538    }
1539
1540    private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
1541        long seconds = milliSeconds / 1000; // round down to compute seconds
1542        long minutes = seconds / 60;
1543        long hours = minutes / 60;
1544        long remainderMinutes = minutes - (hours * 60);
1545        long remainderSeconds = seconds - (minutes * 60);
1546
1547        StringBuilder timeStringBuilder = new StringBuilder();
1548
1549        // Hours
1550        if (hours > 0) {
1551            if (hours < 10) {
1552                timeStringBuilder.append('0');
1553            }
1554            timeStringBuilder.append(hours);
1555
1556            timeStringBuilder.append(':');
1557        }
1558
1559        // Minutes
1560        if (remainderMinutes < 10) {
1561            timeStringBuilder.append('0');
1562        }
1563        timeStringBuilder.append(remainderMinutes);
1564        timeStringBuilder.append(':');
1565
1566        // Seconds
1567        if (remainderSeconds < 10) {
1568            timeStringBuilder.append('0');
1569        }
1570        timeStringBuilder.append(remainderSeconds);
1571
1572        // Centi seconds
1573        if (displayCentiSeconds) {
1574            timeStringBuilder.append('.');
1575            long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
1576            if (remainderCentiSeconds < 10) {
1577                timeStringBuilder.append('0');
1578            }
1579            timeStringBuilder.append(remainderCentiSeconds);
1580        }
1581
1582        return timeStringBuilder.toString();
1583    }
1584
1585    private long getTimeLapseVideoLength(long deltaMs) {
1586        // For better approximation calculate fractional number of frames captured.
1587        // This will update the video time at a higher resolution.
1588        double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
1589        return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
1590    }
1591
1592    private void updateRecordingTime() {
1593        if (!mMediaRecorderRecording) {
1594            return;
1595        }
1596        long now = SystemClock.uptimeMillis();
1597        long delta = now - mRecordingStartTime;
1598
1599        // Starting a minute before reaching the max duration
1600        // limit, we'll countdown the remaining time instead.
1601        boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1602                && delta >= mMaxVideoDurationInMs - 60000);
1603
1604        long deltaAdjusted = delta;
1605        if (countdownRemainingTime) {
1606            deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
1607        }
1608        String text;
1609
1610        long targetNextUpdateDelay;
1611        if (!mCaptureTimeLapse) {
1612            text = millisecondToTimeString(deltaAdjusted, false);
1613            targetNextUpdateDelay = 1000;
1614        } else {
1615            // The length of time lapse video is different from the length
1616            // of the actual wall clock time elapsed. Display the video length
1617            // only in format hh:mm:ss.dd, where dd are the centi seconds.
1618            text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
1619            targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
1620        }
1621
1622        mRecordingTimeView.setText(text);
1623
1624        if (mRecordingTimeCountsDown != countdownRemainingTime) {
1625            // Avoid setting the color on every update, do it only
1626            // when it needs changing.
1627            mRecordingTimeCountsDown = countdownRemainingTime;
1628
1629            int color = getResources().getColor(countdownRemainingTime
1630                    ? R.color.recording_time_remaining_text
1631                    : R.color.recording_time_elapsed_text);
1632
1633            mRecordingTimeView.setTextColor(color);
1634        }
1635
1636        long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
1637        mHandler.sendEmptyMessageDelayed(
1638                UPDATE_RECORD_TIME, actualNextUpdateDelay);
1639    }
1640
1641    private static boolean isSupported(String value, List<String> supported) {
1642        return supported == null ? false : supported.indexOf(value) >= 0;
1643    }
1644
1645    private void setCameraParameters() {
1646        mParameters = mCameraDevice.getParameters();
1647
1648        mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
1649        mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
1650
1651        // Set flash mode.
1652        String flashMode = mPreferences.getString(
1653                CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
1654                getString(R.string.pref_camera_video_flashmode_default));
1655        List<String> supportedFlash = mParameters.getSupportedFlashModes();
1656        if (isSupported(flashMode, supportedFlash)) {
1657            mParameters.setFlashMode(flashMode);
1658        } else {
1659            flashMode = mParameters.getFlashMode();
1660            if (flashMode == null) {
1661                flashMode = getString(
1662                        R.string.pref_camera_flashmode_no_flash);
1663            }
1664        }
1665
1666        // Set white balance parameter.
1667        String whiteBalance = mPreferences.getString(
1668                CameraSettings.KEY_WHITE_BALANCE,
1669                getString(R.string.pref_camera_whitebalance_default));
1670        if (isSupported(whiteBalance,
1671                mParameters.getSupportedWhiteBalance())) {
1672            mParameters.setWhiteBalance(whiteBalance);
1673        } else {
1674            whiteBalance = mParameters.getWhiteBalance();
1675            if (whiteBalance == null) {
1676                whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1677            }
1678        }
1679
1680        // Set zoom.
1681        if (mParameters.isZoomSupported()) {
1682            mParameters.setZoom(mZoomValue);
1683        }
1684
1685        // Set continuous autofocus.
1686        List<String> supportedFocus = mParameters.getSupportedFocusModes();
1687        if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
1688            mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
1689        }
1690
1691        mParameters.setRecordingHint(true);
1692
1693        mCameraDevice.setParameters(mParameters);
1694        // Keep preview size up to date.
1695        mParameters = mCameraDevice.getParameters();
1696    }
1697
1698    private boolean switchToOtherMode(int mode) {
1699        if (isFinishing() || mMediaRecorderRecording) return false;
1700        MenuHelper.gotoMode(mode, VideoCamera.this);
1701        finish();
1702        return true;
1703    }
1704
1705    public boolean onModeChanged(int mode) {
1706        if (mode != ModePicker.MODE_VIDEO) {
1707            return switchToOtherMode(mode);
1708        } else {
1709            return true;
1710        }
1711    }
1712
1713    @Override
1714    public void onConfigurationChanged(Configuration config) {
1715        super.onConfigurationChanged(config);
1716    }
1717
1718    public void onOverriddenPreferencesClicked() {
1719    }
1720
1721    public void onRestorePreferencesClicked() {
1722        Runnable runnable = new Runnable() {
1723            public void run() {
1724                restorePreferences();
1725            }
1726        };
1727        MenuHelper.confirmAction(this,
1728                getString(R.string.confirm_restore_title),
1729                getString(R.string.confirm_restore_message),
1730                runnable);
1731    }
1732
1733    private void restorePreferences() {
1734        // Reset the zoom. Zoom value is not stored in preference.
1735        if (mParameters.isZoomSupported()) {
1736            mZoomValue = 0;
1737            setCameraParameters();
1738            if (mZoomControl != null) mZoomControl.setZoomIndex(0);
1739        }
1740
1741        if (mIndicatorControl != null) {
1742            mIndicatorControl.dismissSettingPopup();
1743            CameraSettings.restorePreferences(VideoCamera.this, mPreferences,
1744                    mParameters);
1745            mIndicatorControl.reloadPreferences();
1746            onSharedPreferenceChanged();
1747        }
1748    }
1749
1750    public void onSharedPreferenceChanged() {
1751        // ignore the events after "onPause()" or preview has not started yet
1752        if (mPausing) return;
1753        synchronized (mPreferences) {
1754            // If mCameraDevice is not ready then we can set the parameter in
1755            // startPreview().
1756            if (mCameraDevice == null) return;
1757
1758            // Check if camera id is changed.
1759            int cameraId = CameraSettings.readPreferredCameraId(mPreferences);
1760            if (mCameraId != cameraId) {
1761                // Restart the activity to have a crossfade animation.
1762                // TODO: Use SurfaceTexture to implement a better and faster
1763                // animation.
1764                if (mIsVideoCaptureIntent) {
1765                    // If the intent is video capture, stay in video capture mode.
1766                    MenuHelper.gotoVideoMode(this, getIntent());
1767                } else {
1768                    MenuHelper.gotoVideoMode(this);
1769                }
1770                finish();
1771            } else {
1772                readVideoPreferences();
1773                // We need to restart the preview if preview size is changed.
1774                Size size = mParameters.getPreviewSize();
1775                if (size.width != mDesiredPreviewWidth
1776                        || size.height != mDesiredPreviewHeight) {
1777                    mCameraDevice.stopPreview();
1778                    resizeForPreviewAspectRatio();
1779                    startPreview(); // Parameters will be set in startPreview().
1780                } else {
1781                    setCameraParameters();
1782                }
1783            }
1784            showTimeLapseUI(mCaptureTimeLapse);
1785        }
1786    }
1787
1788    private void showTimeLapseUI(boolean enable) {
1789        if (mTimeLapseLabel != null) {
1790            mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.GONE);
1791        }
1792        if (mPreviewBorder != null) {
1793            mPreviewBorder.setBackgroundResource(enable
1794                    ? R.drawable.border_preview_time_lapse
1795                    : R.drawable.border_preview);
1796        }
1797
1798    }
1799
1800    private void showSharePopup() {
1801        Uri uri = mThumbnail.getUri();
1802        if (mSharePopup == null || !uri.equals(mSharePopup.getUri())) {
1803            mSharePopup = new SharePopup(this, uri, mThumbnail.getBitmap(),
1804                    "video/*", mOrientationCompensation, mThumbnailView);
1805        }
1806        mSharePopup.showAtLocation(mThumbnailView, Gravity.NO_GRAVITY, 0, 0);
1807    }
1808
1809    @Override
1810    public boolean dispatchTouchEvent(MotionEvent m) {
1811        // Check if the popup window should be dismissed first.
1812        if (mPopupGestureDetector != null && mPopupGestureDetector.onTouchEvent(m)) {
1813            return true;
1814        }
1815
1816        return super.dispatchTouchEvent(m);
1817    }
1818
1819    private class PopupGestureListener extends
1820            GestureDetector.SimpleOnGestureListener {
1821        @Override
1822        public boolean onDown(MotionEvent e) {
1823            // Check if the popup window is visible.
1824            View popup = mIndicatorControl.getActiveSettingPopup();
1825            if (popup == null) return false;
1826
1827            // Let popup window or indicator wheel handle the event by
1828            // themselves. Dismiss the popup window if users touch on other
1829            // areas.
1830            if (!Util.pointInView(e.getX(), e.getY(), popup)
1831                    && !Util.pointInView(e.getX(), e.getY(), mIndicatorControl)) {
1832                mIndicatorControl.dismissSettingPopup();
1833                // Let event fall through.
1834            }
1835            return false;
1836        }
1837    }
1838
1839    private void initializeZoomControl() {
1840        mZoomControl = (ZoomControl) findViewById(R.id.zoom_control);
1841        if (!mParameters.isZoomSupported()) {
1842            mZoomControl.setZoomSupported(false);
1843            return;
1844        }
1845        mZoomControl.initialize(this);
1846
1847        mZoomMax = mParameters.getMaxZoom();
1848        mSmoothZoomSupported = mParameters.isSmoothZoomSupported();
1849        if (mZoomControl != null) {
1850            if (Util.isTabletUI()) ((ZoomPicker) mZoomControl).initialize(this);
1851            mZoomControl.setZoomMax(mZoomMax);
1852            mZoomControl.setZoomIndex(mParameters.getZoom());
1853            mZoomControl.setSmoothZoomSupported(mSmoothZoomSupported);
1854            mZoomControl.setOnZoomChangeListener(
1855                    new ZoomPicker.OnZoomChangedListener() {
1856                // only for immediate zoom
1857                @Override
1858                public void onZoomValueChanged(int index) {
1859                    VideoCamera.this.onZoomValueChanged(index);
1860                }
1861
1862                // only for smooth zoom
1863                @Override
1864                public void onZoomStateChanged(int state) {
1865                    if (mPausing) return;
1866
1867                    Log.v(TAG, "zoom picker state=" + state);
1868                    if (state == ZoomPicker.ZOOM_IN) {
1869                        VideoCamera.this.onZoomValueChanged(mZoomMax);
1870                    } else if (state == ZoomPicker.ZOOM_OUT){
1871                        VideoCamera.this.onZoomValueChanged(0);
1872                    } else {
1873                        mTargetZoomValue = -1;
1874                        if (mZoomState == ZOOM_START) {
1875                            mZoomState = ZOOM_STOPPING;
1876                            mCameraDevice.stopSmoothZoom();
1877                        }
1878                    }
1879                }
1880            });
1881        }
1882
1883        mCameraDevice.setZoomChangeListener(mZoomListener);
1884    }
1885
1886    private final class ZoomListener
1887            implements android.hardware.Camera.OnZoomChangeListener {
1888        @Override
1889        public void onZoomChange(int value, boolean stopped, android.hardware.Camera camera) {
1890            Log.v(TAG, "Zoom changed: value=" + value + ". stopped="+ stopped);
1891            mZoomValue = value;
1892
1893            // Update the UI when we get zoom value.
1894            if (mZoomControl != null) mZoomControl.setZoomIndex(value);
1895
1896            // Keep mParameters up to date. We do not getParameter again in
1897            // takePicture. If we do not do this, wrong zoom value will be set.
1898            mParameters.setZoom(value);
1899
1900            if (stopped && mZoomState != ZOOM_STOPPED) {
1901                if (mTargetZoomValue != -1 && value != mTargetZoomValue) {
1902                    mCameraDevice.startSmoothZoom(mTargetZoomValue);
1903                    mZoomState = ZOOM_START;
1904                } else {
1905                    mZoomState = ZOOM_STOPPED;
1906                }
1907            }
1908        }
1909    }
1910
1911    private void onZoomValueChanged(int index) {
1912        // Not useful to change zoom value when the activity is paused.
1913        if (mPausing) return;
1914
1915        if (mSmoothZoomSupported) {
1916            if (mTargetZoomValue != index && mZoomState != ZOOM_STOPPED) {
1917                mTargetZoomValue = index;
1918                if (mZoomState == ZOOM_START) {
1919                    mZoomState = ZOOM_STOPPING;
1920                    mCameraDevice.stopSmoothZoom();
1921                }
1922            } else if (mZoomState == ZOOM_STOPPED && mZoomValue != index) {
1923                mTargetZoomValue = index;
1924                mCameraDevice.startSmoothZoom(index);
1925                mZoomState = ZOOM_START;
1926            }
1927        } else {
1928            mZoomValue = index;
1929            setCameraParameters();
1930        }
1931    }
1932}
1933