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