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