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