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