VideoCamera.java revision fdea7c43e7b48e39cc0c8fb588bada0d6cb5c71a
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.camera;
18
19import com.android.camera.ui.CameraPicker;
20import com.android.camera.ui.IndicatorControlContainer;
21import com.android.camera.ui.IndicatorControlWheelContainer;
22import com.android.camera.ui.Rotatable;
23import com.android.camera.ui.RotateImageView;
24import com.android.camera.ui.RotateLayout;
25import com.android.camera.ui.SharePopup;
26import com.android.camera.ui.ZoomControl;
27
28import android.content.ActivityNotFoundException;
29import android.content.BroadcastReceiver;
30import android.content.ContentResolver;
31import android.content.ContentValues;
32import android.content.Context;
33import android.content.Intent;
34import android.content.IntentFilter;
35import android.content.res.Configuration;
36import android.graphics.Bitmap;
37import android.hardware.Camera.CameraInfo;
38import android.hardware.Camera.Parameters;
39import android.hardware.Camera.PictureCallback;
40import android.hardware.Camera.Size;
41import android.location.Location;
42import android.media.CamcorderProfile;
43import android.media.CameraProfile;
44import android.media.MediaRecorder;
45import android.net.Uri;
46import android.os.Bundle;
47import android.os.Handler;
48import android.os.Message;
49import android.os.ParcelFileDescriptor;
50import android.os.SystemClock;
51import android.provider.MediaStore;
52import android.provider.MediaStore.Video;
53import android.util.Log;
54import android.view.GestureDetector;
55import android.view.Gravity;
56import android.view.KeyEvent;
57import android.view.Menu;
58import android.view.MenuItem;
59import android.view.MenuItem.OnMenuItemClickListener;
60import android.view.MotionEvent;
61import android.view.OrientationEventListener;
62import android.view.SurfaceHolder;
63import android.view.SurfaceView;
64import android.view.View;
65import android.view.Window;
66import android.view.WindowManager;
67import android.widget.ImageView;
68import android.widget.LinearLayout;
69import android.widget.TextView;
70import android.widget.Toast;
71
72import java.io.File;
73import java.io.IOException;
74import java.text.SimpleDateFormat;
75import java.util.Date;
76import java.util.Iterator;
77import java.util.List;
78
79import android.filterpacks.videosink.MediaRecorderStopException;
80
81/**
82 * The Camcorder activity.
83 */
84public class VideoCamera extends ActivityBase
85        implements CameraPreference.OnPreferenceChangedListener,
86        ShutterButton.OnShutterButtonListener, SurfaceHolder.Callback,
87        MediaRecorder.OnErrorListener, MediaRecorder.OnInfoListener,
88        ModePicker.OnModeChangeListener, View.OnTouchListener,
89        EffectsRecorder.EffectsListener {
90
91    private static final String TAG = "videocamera";
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 boolean SWITCH_CAMERA = true;
105    private static final boolean SWITCH_VIDEO = false;
106
107    private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
108
109    private static final int[] TIME_LAPSE_VIDEO_QUALITY = {
110            CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
111            CamcorderProfile.QUALITY_TIME_LAPSE_720P,
112            CamcorderProfile.QUALITY_TIME_LAPSE_480P,
113            CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
114            1007, /* TODO: replace it with QUALITY_TIME_LAPSE_QVGA if public. */
115            CamcorderProfile.QUALITY_TIME_LAPSE_QCIF};
116
117    private static final int[] VIDEO_QUALITY = {
118            CamcorderProfile.QUALITY_1080P,
119            CamcorderProfile.QUALITY_720P,
120            CamcorderProfile.QUALITY_480P,
121            CamcorderProfile.QUALITY_CIF,
122            7, /* TODO: replace it with CamcorderProfile.QUALITY_QVGA */
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 static final String EXTRA_QUICK_CAPTURE =
131            "android.intent.extra.quickCapture";
132
133    private boolean mSnapshotInProgress = false;
134
135    private static final String EFFECT_BG_FROM_GALLERY = "gallery";
136
137    private android.hardware.Camera mCameraDevice;
138    private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
139
140    private ComboPreferences mPreferences;
141    private PreferenceGroup mPreferenceGroup;
142
143    private View mPreviewPanel;  // The container of PreviewFrameLayout.
144    private PreviewFrameLayout mPreviewFrameLayout;
145    private SurfaceHolder mSurfaceHolder = null;
146    private IndicatorControlContainer mIndicatorControlContainer;
147    private int mSurfaceWidth;
148    private int mSurfaceHeight;
149    private View mReviewControl;
150
151    private Toast mNoShareToast;
152    // An review image having same size as preview. It is displayed when
153    // recording is stopped in capture intent.
154    private ImageView mReviewImage;
155    // A popup window that contains a bigger thumbnail and a list of apps to share.
156    private SharePopup mSharePopup;
157    // The bitmap of the last captured video thumbnail and the URI of the
158    // original video.
159    private Thumbnail mThumbnail;
160    // An imageview showing showing the last captured picture thumbnail.
161    private RotateImageView mThumbnailView;
162    private Rotatable mReviewCancelButton;
163    private Rotatable mReviewDoneButton;
164    private Rotatable mReviewPlayButton;
165    private ModePicker mModePicker;
166    private ShutterButton mShutterButton;
167    private TextView mRecordingTimeView;
168    private RotateLayout mBgLearningMessageRotater;
169    private View mBgLearningMessageFrame;
170    private LinearLayout mLabelsLinearLayout;
171
172    private boolean mIsVideoCaptureIntent;
173    private boolean mQuickCapture;
174
175    private boolean mOpenCameraFail = false;
176    private boolean mCameraDisabled = false;
177
178    private long mStorageSpace;
179
180    private MediaRecorder mMediaRecorder;
181    private EffectsRecorder mEffectsRecorder;
182
183    private int mEffectType = EffectsRecorder.EFFECT_NONE;
184    private Object mEffectParameter = null;
185    private String mEffectUriFromGallery = null;
186
187    private boolean mMediaRecorderRecording = false;
188    private long mRecordingStartTime;
189    private boolean mRecordingTimeCountsDown = false;
190    private RotateLayout mRecordingTimeRect;
191    private long mOnResumeTime;
192    // The video file that the hardware camera is about to record into
193    // (or is recording into.)
194    private String mVideoFilename;
195    private ParcelFileDescriptor mVideoFileDescriptor;
196
197    // The video file that has already been recorded, and that is being
198    // examined by the user.
199    private String mCurrentVideoFilename;
200    private Uri mCurrentVideoUri;
201    private ContentValues mCurrentVideoValues;
202
203    private CamcorderProfile mProfile;
204
205    // The video duration limit. 0 menas no limit.
206    private int mMaxVideoDurationInMs;
207
208    // Time Lapse parameters.
209    private boolean mCaptureTimeLapse = false;
210    // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
211    private int mTimeBetweenTimeLapseFrameCaptureMs = 0;
212    private View mTimeLapseLabel;
213
214    private int mDesiredPreviewWidth;
215    private int mDesiredPreviewHeight;
216
217    boolean mPausing = false;
218    boolean mPreviewing = false; // True if preview is started.
219    // The display rotation in degrees. This is only valid when mPreviewing is
220    // true.
221    private int mDisplayRotation;
222
223    private ContentResolver mContentResolver;
224
225    private LocationManager mLocationManager;
226
227    private final Handler mHandler = new MainHandler();
228    private Parameters mParameters;
229
230    // multiple cameras support
231    private int mNumberOfCameras;
232    private int mCameraId;
233    private int mFrontCameraId;
234    private int mBackCameraId;
235
236    private GestureDetector mPopupGestureDetector;
237
238    private MyOrientationEventListener mOrientationListener;
239    // The degrees of the device rotated clockwise from its natural orientation.
240    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
241    // The orientation compensation for icons and thumbnails. Ex: if the value
242    // is 90, the UI components should be rotated 90 degrees counter-clockwise.
243    private int mOrientationCompensation = 0;
244    // The orientation compenstaion when we start recording.
245    private int mOrientationCompensationAtRecordStart;
246
247    private static final int ZOOM_STOPPED = 0;
248    private static final int ZOOM_START = 1;
249    private static final int ZOOM_STOPPING = 2;
250
251    private int mZoomState = ZOOM_STOPPED;
252    private boolean mSmoothZoomSupported = false;
253    private int mZoomValue;  // The current zoom value.
254    private int mZoomMax;
255    private int mTargetZoomValue;
256    private ZoomControl mZoomControl;
257    private final ZoomListener mZoomListener = new ZoomListener();
258
259    // This Handler is used to post message back onto the main thread of the
260    // application
261    private class MainHandler extends Handler {
262        @Override
263        public void handleMessage(Message msg) {
264            switch (msg.what) {
265
266                case ENABLE_SHUTTER_BUTTON:
267                    mShutterButton.setEnabled(true);
268                    break;
269
270                case CLEAR_SCREEN_DELAY: {
271                    getWindow().clearFlags(
272                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
273                    break;
274                }
275
276                case UPDATE_RECORD_TIME: {
277                    updateRecordingTime();
278                    break;
279                }
280
281                case CHECK_DISPLAY_ROTATION: {
282                    // Restart the preview if display rotation has changed.
283                    // Sometimes this happens when the device is held upside
284                    // down and camera app is opened. Rotation animation will
285                    // take some time and the rotation value we have got may be
286                    // wrong. Framework does not have a callback for this now.
287                    if ((Util.getDisplayRotation(VideoCamera.this) != mDisplayRotation)
288                            && !mMediaRecorderRecording) {
289                        startPreview();
290                    }
291                    if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
292                        mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
293                    }
294                    break;
295                }
296
297                default:
298                    Log.v(TAG, "Unhandled message: " + msg.what);
299                    break;
300            }
301        }
302    }
303
304    private BroadcastReceiver mReceiver = null;
305
306    private class MyBroadcastReceiver extends BroadcastReceiver {
307        @Override
308        public void onReceive(Context context, Intent intent) {
309            String action = intent.getAction();
310            if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
311                updateAndShowStorageHint();
312                stopVideoRecording();
313            } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
314                updateAndShowStorageHint();
315                updateThumbnailButton();
316            } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
317                // SD card unavailable
318                // handled in ACTION_MEDIA_EJECT
319            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
320                Toast.makeText(VideoCamera.this,
321                        getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
322            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
323                updateAndShowStorageHint();
324            }
325        }
326    }
327
328    private String createName(long dateTaken) {
329        Date date = new Date(dateTaken);
330        SimpleDateFormat dateFormat = new SimpleDateFormat(
331                getString(R.string.video_file_name_format));
332
333        return dateFormat.format(date);
334    }
335
336    @Override
337    public void onCreate(Bundle icicle) {
338        super.onCreate(icicle);
339
340        Util.initializeScreenBrightness(getWindow(), getContentResolver());
341
342        mPreferences = new ComboPreferences(this);
343        CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
344        mCameraId = CameraSettings.readPreferredCameraId(mPreferences);
345
346        //Testing purpose. Launch a specific camera through the intent extras.
347        int intentCameraId = Util.getCameraFacingIntentExtras(this);
348        if (intentCameraId != -1) {
349            mCameraId = intentCameraId;
350        }
351
352        mPreferences.setLocalId(this, mCameraId);
353        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
354
355        mNumberOfCameras = CameraHolder.instance().getNumberOfCameras();
356
357        /*
358         * To reduce startup time, we start the preview in another thread.
359         * We make sure the preview is started at the end of onCreate.
360         */
361        Thread startPreviewThread = new Thread(new Runnable() {
362            public void run() {
363                try {
364                    mCameraDevice = Util.openCamera(VideoCamera.this, mCameraId);
365                    readVideoPreferences();
366                    startPreview();
367                } catch (CameraHardwareException e) {
368                    mOpenCameraFail = true;
369                } catch (CameraDisabledException e) {
370                    mCameraDisabled = true;
371                }
372            }
373        });
374        startPreviewThread.start();
375
376        Util.enterLightsOutMode(getWindow());
377
378        mContentResolver = getContentResolver();
379
380        requestWindowFeature(Window.FEATURE_PROGRESS);
381        mIsVideoCaptureIntent = isVideoCaptureIntent();
382        setContentView(R.layout.video_camera);
383        if (mIsVideoCaptureIntent) {
384            mReviewDoneButton = (Rotatable) findViewById(R.id.btn_done);
385            mReviewPlayButton = (Rotatable) findViewById(R.id.btn_play);
386            mReviewCancelButton = (Rotatable) findViewById(R.id.btn_cancel);
387            findViewById(R.id.btn_cancel).setVisibility(View.VISIBLE);
388        } else {
389            initThumbnailButton();
390            mModePicker = (ModePicker) findViewById(R.id.mode_picker);
391            mModePicker.setVisibility(View.VISIBLE);
392            mModePicker.setOnModeChangeListener(this);
393        }
394
395        mPreviewPanel = findViewById(R.id.frame_layout);
396        mPreviewFrameLayout = (PreviewFrameLayout) findViewById(R.id.frame);
397        mReviewImage = (ImageView) findViewById(R.id.review_image);
398
399        // don't set mSurfaceHolder here. We have it set ONLY within
400        // surfaceCreated / surfaceDestroyed, other parts of the code
401        // assume that when it is set, the surface is also set.
402        SurfaceView preview = (SurfaceView) findViewById(R.id.camera_preview);
403        SurfaceHolder holder = preview.getHolder();
404        holder.addCallback(this);
405        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
406
407        mQuickCapture = getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
408
409        mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
410        mShutterButton.setBackgroundResource(R.drawable.btn_shutter_video);
411        mShutterButton.setOnShutterButtonListener(this);
412        mShutterButton.requestFocus();
413
414        mRecordingTimeView = (TextView) findViewById(R.id.recording_time);
415        mRecordingTimeRect = (RotateLayout) findViewById(R.id.recording_time_rect);
416        mOrientationListener = new MyOrientationEventListener(this);
417        mTimeLapseLabel = findViewById(R.id.time_lapse_label);
418        // The R.id.labels can only be found in phone layout. For tablet, the id is
419        // R.id.labels_w1024. That is, mLabelsLinearLayout should be null in tablet layout.
420        mLabelsLinearLayout = (LinearLayout) findViewById(R.id.labels);
421
422        mBgLearningMessageRotater = (RotateLayout) findViewById(R.id.bg_replace_message);
423        mBgLearningMessageFrame = findViewById(R.id.bg_replace_message_frame);
424
425        mLocationManager = new LocationManager(this, null);
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        initializeVideoSnapshot();
443        resizeForPreviewAspectRatio();
444
445        mBackCameraId = CameraHolder.instance().getBackCameraId();
446        mFrontCameraId = CameraHolder.instance().getFrontCameraId();
447
448        initializeIndicatorControl();
449    }
450
451    private void loadCameraPreferences() {
452        CameraSettings settings = new CameraSettings(this, mParameters,
453                mCameraId, CameraHolder.instance().getCameraInfo());
454        // Remove the video quality preference setting when the quality is given in the intent.
455        mPreferenceGroup = filterPreferenceScreenByIntent(
456                settings.getPreferenceGroup(R.xml.video_preferences));
457    }
458
459    private boolean collapseCameraControls() {
460        if ((mIndicatorControlContainer != null)
461                && mIndicatorControlContainer.dismissSettingPopup()) {
462            return true;
463        }
464        return false;
465    }
466
467    private void enableCameraControls(boolean enable) {
468        if (mIndicatorControlContainer != null) {
469            mIndicatorControlContainer.setEnabled(enable);
470        }
471        if (mModePicker != null) mModePicker.setEnabled(enable);
472    }
473
474    private void initializeIndicatorControl() {
475        mIndicatorControlContainer =
476                (IndicatorControlContainer) findViewById(R.id.indicator_control);
477        if (mIndicatorControlContainer == null) return;
478        loadCameraPreferences();
479
480        final String[] SETTING_KEYS = {
481                    CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
482                    CameraSettings.KEY_WHITE_BALANCE,
483                    CameraSettings.KEY_VIDEO_EFFECT,
484                    CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
485                    CameraSettings.KEY_VIDEO_QUALITY};
486        final String[] OTHER_SETTING_KEYS = {
487                    CameraSettings.KEY_RECORD_LOCATION};
488
489        CameraPicker.setImageResourceId(R.drawable.ic_switch_video_facing_holo_light);
490        mIndicatorControlContainer.initialize(this, mPreferenceGroup,
491                mParameters.isZoomSupported(), SETTING_KEYS, OTHER_SETTING_KEYS);
492        mIndicatorControlContainer.setListener(this);
493        mPopupGestureDetector = new GestureDetector(this,
494                new PopupGestureListener());
495
496        if (effectsActive()) {
497            mIndicatorControlContainer.overrideSettings(
498                    CameraSettings.KEY_VIDEO_QUALITY,
499                    Integer.toString(CamcorderProfile.QUALITY_480P));
500        }
501    }
502
503    public static int roundOrientation(int orientation) {
504        return ((orientation + 45) / 90 * 90) % 360;
505    }
506
507    private class MyOrientationEventListener
508            extends OrientationEventListener {
509        public MyOrientationEventListener(Context context) {
510            super(context);
511        }
512
513        @Override
514        public void onOrientationChanged(int orientation) {
515            // We keep the last known orientation. So if the user first orient
516            // the camera then point the camera to floor or sky, we still have
517            // the correct orientation.
518            if (orientation == ORIENTATION_UNKNOWN) return;
519            mOrientation = roundOrientation(orientation);
520            // When the screen is unlocked, display rotation may change. Always
521            // calculate the up-to-date orientationCompensation.
522            int orientationCompensation = mOrientation
523                    + Util.getDisplayRotation(VideoCamera.this);
524
525            if (mOrientationCompensation != orientationCompensation) {
526                mOrientationCompensation = orientationCompensation;
527                if (effectsActive()) {
528                    CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
529                    int rotation = (info.orientation + mOrientation) % 360;
530                    mEffectsRecorder.setOrientationHint(rotation);
531                }
532                // Do not rotate the icons during recording because the video
533                // orientation is fixed after recording.
534                if (!mMediaRecorderRecording) {
535                    setOrientationIndicator(mOrientationCompensation);
536                }
537            }
538        }
539    }
540
541    private void setOrientationIndicator(int orientation) {
542        Rotatable[] indicators = {mThumbnailView, mModePicker, mSharePopup,
543                mBgLearningMessageRotater, mIndicatorControlContainer,
544                mReviewDoneButton, mReviewPlayButton, mReviewCancelButton};
545        for (Rotatable indicator : indicators) {
546            if (indicator != null) indicator.setOrientation(orientation);
547        }
548
549        // We change the orientation of the linearlayout only for phone UI because when in portrait
550        // the width is not enough.
551        if (mLabelsLinearLayout != null) {
552            if (((orientation / 90) & 1) == 1) {
553                mLabelsLinearLayout.setOrientation(mLabelsLinearLayout.VERTICAL);
554            } else {
555                mLabelsLinearLayout.setOrientation(mLabelsLinearLayout.HORIZONTAL);
556            }
557        }
558        mRecordingTimeRect.setOrientation(mOrientationCompensation);
559    }
560
561    private void startPlayVideoActivity() {
562        Intent intent = new Intent(Intent.ACTION_VIEW, mCurrentVideoUri);
563        try {
564            startActivity(intent);
565        } catch (ActivityNotFoundException ex) {
566            Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
567        }
568    }
569
570    @OnClickAttr
571    public void onThumbnailClicked(View v) {
572        if (!mMediaRecorderRecording && mThumbnail != null) {
573            showSharePopup();
574        }
575    }
576
577    @OnClickAttr
578    public void onReviewRetakeClicked(View v) {
579        deleteCurrentVideo();
580        hideAlert();
581    }
582
583    @OnClickAttr
584    public void onReviewPlayClicked(View v) {
585        startPlayVideoActivity();
586    }
587
588    @OnClickAttr
589    public void onReviewDoneClicked(View v) {
590        doReturnToCaller(true);
591    }
592
593    @OnClickAttr
594    public void onReviewCancelClicked(View v) {
595        stopVideoRecording();
596        doReturnToCaller(false);
597    }
598
599    private void onStopVideoRecording(boolean valid) {
600        stopVideoRecording();
601        if (mIsVideoCaptureIntent) {
602            if (mQuickCapture) {
603                doReturnToCaller(valid);
604            } else {
605                showAlert();
606            }
607        } else {
608            getThumbnail();
609        }
610    }
611
612    public void onProtectiveCurtainClick(View v) {
613        // Consume clicks
614    }
615
616    @Override
617    public void onShutterButtonClick() {
618        if (collapseCameraControls()) return;
619        boolean stop = mMediaRecorderRecording;
620
621        if (stop) {
622            onStopVideoRecording(true);
623        } else {
624            startVideoRecording();
625        }
626        mShutterButton.setEnabled(false);
627
628        // Keep the shutter button disabled when in video capture intent
629        // mode and recording is stopped. It'll be re-enabled when
630        // re-take button is clicked.
631        if (!(mIsVideoCaptureIntent && stop)) {
632            mHandler.sendEmptyMessageDelayed(
633                    ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
634        }
635    }
636
637    @Override
638    public void onShutterButtonFocus(boolean pressed) {
639        // Do nothing (everything happens in onShutterButtonClick).
640    }
641
642    private OnScreenHint mStorageHint;
643
644    private void updateAndShowStorageHint() {
645        mStorageSpace = Storage.getAvailableSpace();
646        showStorageHint();
647    }
648
649    private void showStorageHint() {
650        String errorMessage = null;
651        if (mStorageSpace == Storage.UNAVAILABLE) {
652            errorMessage = getString(R.string.no_storage);
653        } else if (mStorageSpace == Storage.PREPARING) {
654            errorMessage = getString(R.string.preparing_sd);
655        } else if (mStorageSpace == Storage.UNKNOWN_SIZE) {
656            errorMessage = getString(R.string.access_sd_fail);
657        } else if (mStorageSpace < Storage.LOW_STORAGE_THRESHOLD) {
658            errorMessage = getString(R.string.spaceIsLow_content);
659        }
660
661        if (errorMessage != null) {
662            if (mStorageHint == null) {
663                mStorageHint = OnScreenHint.makeText(this, errorMessage);
664            } else {
665                mStorageHint.setText(errorMessage);
666            }
667            mStorageHint.show();
668        } else if (mStorageHint != null) {
669            mStorageHint.cancel();
670            mStorageHint = null;
671        }
672    }
673
674    private void readVideoPreferences() {
675        // The preference stores values from ListPreference and is thus string type for all values.
676        // We need to convert it to int manually.
677        String defaultQuality = CameraSettings.getDefaultVideoQuality(mCameraId,
678                getResources().getString(R.string.pref_video_quality_default));
679        String videoQuality =
680                mPreferences.getString(CameraSettings.KEY_VIDEO_QUALITY,
681                        defaultQuality);
682        int quality = Integer.valueOf(videoQuality);
683
684        // Set video quality.
685        Intent intent = getIntent();
686        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
687            int extraVideoQuality =
688                    intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
689            if (extraVideoQuality > 0) {
690                quality = CamcorderProfile.QUALITY_HIGH;
691            } else {  // 0 is mms.
692                quality = CamcorderProfile.QUALITY_LOW;
693            }
694        }
695
696        // Set video duration limit. The limit is read from the preference,
697        // unless it is specified in the intent.
698        if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
699            int seconds =
700                    intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
701            mMaxVideoDurationInMs = 1000 * seconds;
702        } else {
703            mMaxVideoDurationInMs = CameraSettings.DEFAULT_VIDEO_DURATION;
704        }
705
706        // Set effect
707        mEffectType = CameraSettings.readEffectType(mPreferences);
708        if (mEffectType != EffectsRecorder.EFFECT_NONE) {
709            mEffectParameter = CameraSettings.readEffectParameter(mPreferences);
710            // When picking from gallery, mEffectParameter should have been
711            // initialized in onActivityResult. If not, fall back to no effect
712            if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER
713                    && ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)
714                    && mEffectUriFromGallery == null) {
715                Log.w(TAG, "No URI from gallery, resetting to no effect");
716                mEffectType = EffectsRecorder.EFFECT_NONE;
717                mEffectParameter = null;
718                writeDefaultEffectToPrefs();
719                if (mIndicatorControlContainer != null) {
720                    mIndicatorControlContainer.overrideSettings(
721                        CameraSettings.KEY_VIDEO_QUALITY,
722                        null);
723                }
724            } else {
725                // Set quality to 480p for effects, unless intent is overriding it
726                if (!intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
727                    quality = CamcorderProfile.QUALITY_480P;
728                }
729                // On initial startup, can get here before indicator control is
730                // enabled. In that case, UI quality override handled in
731                // initializeIndicatorControl.
732                if (mIndicatorControlContainer != null) {
733                    mIndicatorControlContainer.overrideSettings(
734                            CameraSettings.KEY_VIDEO_QUALITY,
735                            Integer.toString(CamcorderProfile.QUALITY_480P));
736                }
737            }
738        } else {
739            mEffectParameter = null;
740            if (mIndicatorControlContainer != null) {
741                mIndicatorControlContainer.overrideSettings(
742                        CameraSettings.KEY_VIDEO_QUALITY,
743                        null);
744            }
745        }
746        // Read time lapse recording interval.
747        String frameIntervalStr = mPreferences.getString(
748                CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
749                getString(R.string.pref_video_time_lapse_frame_interval_default));
750        mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
751
752        mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
753        // TODO: This should be checked instead directly +1000.
754        if (mCaptureTimeLapse) quality += 1000;
755        mProfile = CamcorderProfile.get(mCameraId, quality);
756        getDesiredPreviewSize();
757    }
758
759    private void writeDefaultEffectToPrefs()  {
760        ComboPreferences.Editor editor = mPreferences.edit();
761        editor.putString(CameraSettings.KEY_VIDEO_EFFECT,
762                getString(R.string.pref_video_effect_default));
763        editor.apply();
764    }
765
766    private void getDesiredPreviewSize() {
767        mParameters = mCameraDevice.getParameters();
768        if (mParameters.getSupportedVideoSizes() == null || effectsActive()) {
769            mDesiredPreviewWidth = mProfile.videoFrameWidth;
770            mDesiredPreviewHeight = mProfile.videoFrameHeight;
771        } else {  // Driver supports separates outputs for preview and video.
772            List<Size> sizes = mParameters.getSupportedPreviewSizes();
773            Size preferred = mParameters.getPreferredPreviewSizeForVideo();
774            int product = preferred.width * preferred.height;
775            Iterator it = sizes.iterator();
776            // Remove the preview sizes that are not preferred.
777            while (it.hasNext()) {
778                Size size = (Size) it.next();
779                if (size.width * size.height > product) {
780                    it.remove();
781                }
782            }
783            Size optimalSize = Util.getOptimalPreviewSize(this, sizes,
784                (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
785            mDesiredPreviewWidth = optimalSize.width;
786            mDesiredPreviewHeight = optimalSize.height;
787        }
788        Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
789                ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
790    }
791
792    private void resizeForPreviewAspectRatio() {
793        mPreviewFrameLayout.setAspectRatio(
794                (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
795    }
796
797    @Override
798    protected void onResume() {
799        super.onResume();
800        mPausing = false;
801        if (mOpenCameraFail || mCameraDisabled) return;
802        mZoomValue = 0;
803
804        showVideoSnapshotUI(false);
805
806        // Start orientation listener as soon as possible because it takes
807        // some time to get first orientation.
808        mOrientationListener.enable();
809        if (!mPreviewing) {
810            try {
811                mCameraDevice = Util.openCamera(this, mCameraId);
812                readVideoPreferences();
813                resizeForPreviewAspectRatio();
814                startPreview();
815            } catch (CameraHardwareException e) {
816                Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
817                return;
818            } catch (CameraDisabledException e) {
819                Util.showErrorAndFinish(this, R.string.camera_disabled);
820                return;
821            }
822        }
823
824        // Initializing it here after the preview is started.
825        initializeZoom();
826
827        keepScreenOnAwhile();
828
829        // install an intent filter to receive SD card related events.
830        IntentFilter intentFilter =
831                new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
832        intentFilter.addAction(Intent.ACTION_MEDIA_EJECT);
833        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
834        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
835        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
836        intentFilter.addDataScheme("file");
837        mReceiver = new MyBroadcastReceiver();
838        registerReceiver(mReceiver, intentFilter);
839        mStorageSpace = Storage.getAvailableSpace();
840
841        mHandler.postDelayed(new Runnable() {
842            public void run() {
843                showStorageHint();
844            }
845        }, 200);
846
847        // Initialize location sevice.
848        boolean recordLocation = RecordLocationPreference.get(
849                mPreferences, getContentResolver());
850        mLocationManager.recordLocation(recordLocation);
851
852        if (!mIsVideoCaptureIntent) {
853            updateThumbnailButton();  // Update the last video thumbnail.
854            mModePicker.setCurrentMode(ModePicker.MODE_VIDEO);
855        }
856
857        if (mPreviewing) {
858            mOnResumeTime = SystemClock.uptimeMillis();
859            mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
860        }
861    }
862
863    private void setPreviewDisplay(SurfaceHolder holder) {
864        try {
865            if (effectsActive()) {
866                mEffectsRecorder.setPreviewDisplay(
867                        mSurfaceHolder,
868                        mSurfaceWidth,
869                        mSurfaceHeight);
870            } else {
871                mCameraDevice.setPreviewDisplay(holder);
872            }
873        } catch (Throwable ex) {
874            closeCamera();
875            throw new RuntimeException("setPreviewDisplay failed", ex);
876        }
877    }
878
879    private void startPreview() {
880        Log.v(TAG, "startPreview");
881
882        mCameraDevice.setErrorCallback(mErrorCallback);
883        if (mPreviewing == true) {
884            mCameraDevice.stopPreview();
885            if (effectsActive() && mEffectsRecorder != null) {
886                mEffectsRecorder.release();
887            }
888            mPreviewing = false;
889        }
890
891        mDisplayRotation = Util.getDisplayRotation(this);
892        int orientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
893        mCameraDevice.setDisplayOrientation(orientation);
894        setCameraParameters();
895
896        if (!effectsActive()) {
897            setPreviewDisplay(mSurfaceHolder);
898            try {
899                mCameraDevice.startPreview();
900            } catch (Throwable ex) {
901                closeCamera();
902                throw new RuntimeException("startPreview failed", ex);
903            }
904        } else {
905            initializeEffectsPreview();
906            Log.v(TAG, "effectsStartPreview");
907            mEffectsRecorder.startPreview();
908        }
909
910        mZoomState = ZOOM_STOPPED;
911        mPreviewing = true;
912    }
913
914    private void closeCamera() {
915        Log.v(TAG, "closeCamera");
916        if (mCameraDevice == null) {
917            Log.d(TAG, "already stopped.");
918            return;
919        }
920        if (mEffectsRecorder != null) {
921            mEffectsRecorder.release();
922        }
923        mEffectType = EffectsRecorder.EFFECT_NONE;
924        CameraHolder.instance().release();
925        mCameraDevice.setZoomChangeListener(null);
926        mCameraDevice.setErrorCallback(null);
927        mCameraDevice = null;
928        mPreviewing = false;
929        mSnapshotInProgress = false;
930    }
931
932    private void finishRecorderAndCloseCamera() {
933        // This is similar to what mShutterButton.performClick() does,
934        // but not quite the same.
935        if (mMediaRecorderRecording) {
936            if (mIsVideoCaptureIntent) {
937                stopVideoRecording();
938                showAlert();
939            } else {
940                stopVideoRecording();
941                getThumbnail();
942            }
943        } else {
944            stopVideoRecording();
945        }
946        closeCamera();
947    }
948
949    @Override
950    protected void onPause() {
951        super.onPause();
952        mPausing = true;
953
954        if (mIndicatorControlContainer != null) {
955            mIndicatorControlContainer.dismissSettingPopup();
956        }
957
958        finishRecorderAndCloseCamera();
959
960        if (mSharePopup != null) mSharePopup.dismiss();
961
962        if (mReceiver != null) {
963            unregisterReceiver(mReceiver);
964            mReceiver = null;
965        }
966        resetScreenOn();
967
968        if (!mIsVideoCaptureIntent && mThumbnail != null && !mThumbnail.fromFile()) {
969            mThumbnail.saveTo(new File(getFilesDir(), Thumbnail.LAST_THUMB_FILENAME));
970        }
971
972        if (mStorageHint != null) {
973            mStorageHint.cancel();
974            mStorageHint = null;
975        }
976
977        mOrientationListener.disable();
978        mLocationManager.recordLocation(false);
979
980        mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
981    }
982
983    @Override
984    public void onUserInteraction() {
985        super.onUserInteraction();
986        if (!mMediaRecorderRecording) keepScreenOnAwhile();
987    }
988
989    @Override
990    public void onBackPressed() {
991        if (mPausing) return;
992        if (mMediaRecorderRecording) {
993            onStopVideoRecording(false);
994        } else if (!collapseCameraControls()) {
995            super.onBackPressed();
996        }
997    }
998
999    @Override
1000    public boolean onKeyDown(int keyCode, KeyEvent event) {
1001        // Do not handle any key if the activity is paused.
1002        if (mPausing) {
1003            return true;
1004        }
1005
1006        switch (keyCode) {
1007            case KeyEvent.KEYCODE_CAMERA:
1008                if (event.getRepeatCount() == 0) {
1009                    mShutterButton.performClick();
1010                    return true;
1011                }
1012                break;
1013            case KeyEvent.KEYCODE_DPAD_CENTER:
1014                if (event.getRepeatCount() == 0) {
1015                    mShutterButton.performClick();
1016                    return true;
1017                }
1018                break;
1019            case KeyEvent.KEYCODE_MENU:
1020                if (mMediaRecorderRecording) return true;
1021                break;
1022        }
1023
1024        return super.onKeyDown(keyCode, event);
1025    }
1026
1027    @Override
1028    public boolean onKeyUp(int keyCode, KeyEvent event) {
1029        switch (keyCode) {
1030            case KeyEvent.KEYCODE_CAMERA:
1031                mShutterButton.setPressed(false);
1032                return true;
1033        }
1034        return super.onKeyUp(keyCode, event);
1035    }
1036
1037    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
1038        // Make sure we have a surface in the holder before proceeding.
1039        if (holder.getSurface() == null) {
1040            Log.d(TAG, "holder.getSurface() == null");
1041            return;
1042        }
1043
1044        Log.v(TAG, "surfaceChanged. w=" + w + ". h=" + h);
1045
1046        mSurfaceHolder = holder;
1047        mSurfaceWidth = w;
1048        mSurfaceHeight = h;
1049
1050        if (mPausing) {
1051            // We're pausing, the screen is off and we already stopped
1052            // video recording. We don't want to start the camera again
1053            // in this case in order to conserve power.
1054            // The fact that surfaceChanged is called _after_ an onPause appears
1055            // to be legitimate since in that case the lockscreen always returns
1056            // to portrait orientation possibly triggering the notification.
1057            return;
1058        }
1059
1060        // The mCameraDevice will be null if it is fail to connect to the
1061        // camera hardware. In this case we will show a dialog and then
1062        // finish the activity, so it's OK to ignore it.
1063        if (mCameraDevice == null) return;
1064
1065        // Set preview display if the surface is being created. Preview was
1066        // already started. Also restart the preview if display rotation has
1067        // changed. Sometimes this happens when the device is held in portrait
1068        // and camera app is opened. Rotation animation takes some time and
1069        // display rotation in onCreate may not be what we want.
1070        if (mPreviewing && (Util.getDisplayRotation(this) == mDisplayRotation)
1071                && holder.isCreating()) {
1072            setPreviewDisplay(holder);
1073        } else {
1074            stopVideoRecording();
1075            startPreview();
1076        }
1077    }
1078
1079    public void surfaceCreated(SurfaceHolder holder) {
1080    }
1081
1082    public void surfaceDestroyed(SurfaceHolder holder) {
1083        mSurfaceHolder = null;
1084    }
1085
1086    private void gotoGallery() {
1087        MenuHelper.gotoCameraVideoGallery(this);
1088    }
1089
1090    @Override
1091    public boolean onCreateOptionsMenu(Menu menu) {
1092        super.onCreateOptionsMenu(menu);
1093
1094        if (mIsVideoCaptureIntent) {
1095            // No options menu for attach mode.
1096            return false;
1097        } else {
1098            addBaseMenuItems(menu);
1099        }
1100        return true;
1101    }
1102
1103    private boolean isVideoCaptureIntent() {
1104        String action = getIntent().getAction();
1105        return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
1106    }
1107
1108    private void doReturnToCaller(boolean valid) {
1109        Intent resultIntent = new Intent();
1110        int resultCode;
1111        if (valid) {
1112            resultCode = RESULT_OK;
1113            resultIntent.setData(mCurrentVideoUri);
1114        } else {
1115            resultCode = RESULT_CANCELED;
1116        }
1117        setResultEx(resultCode, resultIntent);
1118        finish();
1119    }
1120
1121    private void cleanupEmptyFile() {
1122        if (mVideoFilename != null) {
1123            File f = new File(mVideoFilename);
1124            if (f.length() == 0 && f.delete()) {
1125                Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
1126                mVideoFilename = null;
1127            }
1128        }
1129    }
1130
1131    // Prepares media recorder.
1132    private void initializeRecorder() {
1133        Log.v(TAG, "initializeRecorder");
1134        // If the mCameraDevice is null, then this activity is going to finish
1135        if (mCameraDevice == null) return;
1136
1137        if (mSurfaceHolder == null) {
1138            Log.v(TAG, "Surface holder is null. Wait for surface changed.");
1139            return;
1140        }
1141
1142        Intent intent = getIntent();
1143        Bundle myExtras = intent.getExtras();
1144
1145        long requestedSizeLimit = 0;
1146        if (mIsVideoCaptureIntent && myExtras != null) {
1147            Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1148            if (saveUri != null) {
1149                try {
1150                    mVideoFileDescriptor =
1151                            mContentResolver.openFileDescriptor(saveUri, "rw");
1152                    mCurrentVideoUri = saveUri;
1153                } catch (java.io.FileNotFoundException ex) {
1154                    // invalid uri
1155                    Log.e(TAG, ex.toString());
1156                }
1157            }
1158            requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1159        }
1160        mMediaRecorder = new MediaRecorder();
1161
1162        // Unlock the camera object before passing it to media recorder.
1163        mCameraDevice.unlock();
1164        mMediaRecorder.setCamera(mCameraDevice);
1165        if (!mCaptureTimeLapse) {
1166            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
1167        }
1168        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
1169        mMediaRecorder.setProfile(mProfile);
1170        mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
1171        if (mCaptureTimeLapse) {
1172            mMediaRecorder.setCaptureRate((1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs));
1173        }
1174
1175        Location loc = mLocationManager.getCurrentLocation();
1176        if (loc != null) {
1177            mMediaRecorder.setLocation((float) loc.getLatitude(),
1178                    (float) loc.getLongitude());
1179        }
1180
1181
1182        // Set output file.
1183        // Try Uri in the intent first. If it doesn't exist, use our own
1184        // instead.
1185        if (mVideoFileDescriptor != null) {
1186            mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1187        } else {
1188            generateVideoFilename(mProfile.fileFormat);
1189            mMediaRecorder.setOutputFile(mVideoFilename);
1190        }
1191
1192        mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
1193
1194        // Set maximum file size.
1195        long maxFileSize = mStorageSpace - Storage.LOW_STORAGE_THRESHOLD;
1196        if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1197            maxFileSize = requestedSizeLimit;
1198        }
1199
1200        try {
1201            mMediaRecorder.setMaxFileSize(maxFileSize);
1202        } catch (RuntimeException exception) {
1203            // We are going to ignore failure of setMaxFileSize here, as
1204            // a) The composer selected may simply not support it, or
1205            // b) The underlying media framework may not handle 64-bit range
1206            // on the size restriction.
1207        }
1208
1209        // See android.hardware.Camera.Parameters.setRotation for
1210        // documentation.
1211        // Note that mOrientation here is the device orientation, which is the opposite of
1212        // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
1213        // which is the orientation the graphics need to rotate in order to render correctly.
1214        int rotation = 0;
1215        if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1216            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1217            if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
1218                rotation = (info.orientation - mOrientation + 360) % 360;
1219            } else {  // back-facing camera
1220                rotation = (info.orientation + mOrientation) % 360;
1221            }
1222        }
1223        mMediaRecorder.setOrientationHint(rotation);
1224        mOrientationCompensationAtRecordStart = mOrientationCompensation;
1225
1226        try {
1227            mMediaRecorder.prepare();
1228        } catch (IOException e) {
1229            Log.e(TAG, "prepare failed for " + mVideoFilename, e);
1230            releaseMediaRecorder();
1231            throw new RuntimeException(e);
1232        }
1233
1234        mMediaRecorder.setOnErrorListener(this);
1235        mMediaRecorder.setOnInfoListener(this);
1236    }
1237
1238    private void initializeEffectsPreview() {
1239        Log.v(TAG, "initializeEffectsPreview");
1240        // If the mCameraDevice is null, then this activity is going to finish
1241        if (mCameraDevice == null) return;
1242
1243        CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1244
1245        mEffectsRecorder = new EffectsRecorder(this);
1246
1247        // TODO: Confirm none of the foll need to go to initializeEffectsRecording()
1248        // and none of these change even when the preview is not refreshed.
1249        mEffectsRecorder.setCamera(mCameraDevice);
1250        mEffectsRecorder.setCameraFacing(info.facing);
1251        mEffectsRecorder.setProfile(mProfile);
1252        mEffectsRecorder.setEffectsListener(this);
1253        mEffectsRecorder.setOnInfoListener(this);
1254        mEffectsRecorder.setOnErrorListener(this);
1255
1256        // See android.hardware.Camera.Parameters.setRotation for
1257        // documentation.
1258        int rotation = 0;
1259        if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1260            rotation = (info.orientation + mOrientation) % 360;
1261        }
1262        mEffectsRecorder.setOrientationHint(rotation);
1263        mOrientationCompensationAtRecordStart = mOrientationCompensation;
1264
1265        mEffectsRecorder.setPreviewDisplay(
1266                mSurfaceHolder,
1267                mSurfaceWidth,
1268                mSurfaceHeight);
1269
1270        if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER &&
1271                ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) {
1272            mEffectsRecorder.setEffect(mEffectType, mEffectUriFromGallery);
1273        } else {
1274            mEffectsRecorder.setEffect(mEffectType, mEffectParameter);
1275        }
1276    }
1277
1278    private void initializeEffectsRecording() {
1279        Log.v(TAG, "initializeEffectsRecording");
1280
1281        Intent intent = getIntent();
1282        Bundle myExtras = intent.getExtras();
1283
1284        long requestedSizeLimit = 0;
1285        if (mIsVideoCaptureIntent && myExtras != null) {
1286            Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1287            if (saveUri != null) {
1288                try {
1289                    mVideoFileDescriptor =
1290                            mContentResolver.openFileDescriptor(saveUri, "rw");
1291                    mCurrentVideoUri = saveUri;
1292                } catch (java.io.FileNotFoundException ex) {
1293                    // invalid uri
1294                    Log.e(TAG, ex.toString());
1295                }
1296            }
1297            requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1298        }
1299
1300        mEffectsRecorder.setProfile(mProfile);
1301        // important to set the capture rate to zero if not timelapsed, since the
1302        // effectsrecorder object does not get created again for each recording
1303        // session
1304        if (mCaptureTimeLapse) {
1305            mEffectsRecorder.setCaptureRate((1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs));
1306        } else {
1307            mEffectsRecorder.setCaptureRate(0);
1308        }
1309
1310        // Set output file
1311        if (mVideoFileDescriptor != null) {
1312            mEffectsRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1313        } else {
1314            generateVideoFilename(mProfile.fileFormat);
1315            mEffectsRecorder.setOutputFile(mVideoFilename);
1316        }
1317
1318        // Set maximum file size.
1319        long maxFileSize = mStorageSpace - Storage.LOW_STORAGE_THRESHOLD;
1320        if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1321            maxFileSize = requestedSizeLimit;
1322        }
1323        mEffectsRecorder.setMaxFileSize(maxFileSize);
1324    }
1325
1326
1327    private void releaseMediaRecorder() {
1328        Log.v(TAG, "Releasing media recorder.");
1329        if (mMediaRecorder != null) {
1330            cleanupEmptyFile();
1331            mMediaRecorder.reset();
1332            mMediaRecorder.release();
1333            mMediaRecorder = null;
1334        }
1335        mVideoFilename = null;
1336        if (mVideoFileDescriptor != null) {
1337            try {
1338                mVideoFileDescriptor.close();
1339            } catch (IOException e) {
1340                Log.e(TAG, "Fail to close fd", e);
1341            }
1342            mVideoFileDescriptor = null;
1343        }
1344    }
1345
1346    private void releaseEffectsRecorder() {
1347        Log.v(TAG, "Releasing effects recorder.");
1348        if (mEffectsRecorder != null) {
1349            cleanupEmptyFile();
1350            mEffectsRecorder.release();
1351            mEffectsRecorder = null;
1352        }
1353        mVideoFilename = null;
1354    }
1355
1356    private void generateVideoFilename(int outputFileFormat) {
1357        long dateTaken = System.currentTimeMillis();
1358        String title = createName(dateTaken);
1359        String filename = title + ".3gp"; // Used when emailing.
1360        String mime = "video/3gpp";
1361        if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1362            filename = title + ".mp4";
1363            mime = "video/mp4";
1364        }
1365        mVideoFilename = Storage.DIRECTORY + '/' + filename;
1366        mCurrentVideoValues = new ContentValues(7);
1367        mCurrentVideoValues.put(Video.Media.TITLE, title);
1368        mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
1369        mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
1370        mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
1371        mCurrentVideoValues.put(Video.Media.DATA, mVideoFilename);
1372        mCurrentVideoValues.put(Video.Media.RESOLUTION,
1373                Integer.toString(mProfile.videoFrameWidth) + "x" +
1374                Integer.toString(mProfile.videoFrameHeight));
1375        Log.v(TAG, "New video filename: " + mVideoFilename);
1376    }
1377
1378    private void addVideoToMediaStore() {
1379        if (mVideoFileDescriptor == null) {
1380            Uri videoTable = Uri.parse("content://media/external/video/media");
1381            mCurrentVideoValues.put(Video.Media.SIZE,
1382                    new File(mCurrentVideoFilename).length());
1383            long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
1384            if (duration > 0) {
1385                if (mCaptureTimeLapse) {
1386                    duration = getTimeLapseVideoLength(duration);
1387                }
1388                mCurrentVideoValues.put(Video.Media.DURATION, duration);
1389            } else {
1390                Log.w(TAG, "Video duration <= 0 : " + duration);
1391            }
1392            try {
1393                mCurrentVideoUri = mContentResolver.insert(videoTable,
1394                        mCurrentVideoValues);
1395                sendBroadcast(new Intent(android.hardware.Camera.ACTION_NEW_VIDEO,
1396                        mCurrentVideoUri));
1397            } catch (Exception e) {
1398                // We failed to insert into the database. This can happen if
1399                // the SD card is unmounted.
1400                mCurrentVideoUri = null;
1401                mCurrentVideoFilename = null;
1402            } finally {
1403                Log.v(TAG, "Current video URI: " + mCurrentVideoUri);
1404            }
1405        }
1406        mCurrentVideoValues = null;
1407    }
1408
1409    private void deleteCurrentVideo() {
1410        // Remove the video and the uri if the uri is not passed in by intent.
1411        if (mCurrentVideoFilename != null) {
1412            deleteVideoFile(mCurrentVideoFilename);
1413            mCurrentVideoFilename = null;
1414            if (mCurrentVideoUri != null) {
1415                mContentResolver.delete(mCurrentVideoUri, null, null);
1416                mCurrentVideoUri = null;
1417            }
1418        }
1419        updateAndShowStorageHint();
1420    }
1421
1422    private void deleteVideoFile(String fileName) {
1423        Log.v(TAG, "Deleting video " + fileName);
1424        File f = new File(fileName);
1425        if (!f.delete()) {
1426            Log.v(TAG, "Could not delete " + fileName);
1427        }
1428    }
1429
1430    private void addBaseMenuItems(Menu menu) {
1431        MenuHelper.addSwitchModeMenuItem(menu, ModePicker.MODE_CAMERA, new Runnable() {
1432            public void run() {
1433                switchToOtherMode(ModePicker.MODE_CAMERA);
1434            }
1435        });
1436        MenuHelper.addSwitchModeMenuItem(menu, ModePicker.MODE_PANORAMA, new Runnable() {
1437            public void run() {
1438                switchToOtherMode(ModePicker.MODE_PANORAMA);
1439            }
1440        });
1441
1442        if (mNumberOfCameras > 1) {
1443            menu.add(R.string.switch_camera_id)
1444                    .setOnMenuItemClickListener(new OnMenuItemClickListener() {
1445                public boolean onMenuItemClick(MenuItem item) {
1446                    CameraSettings.writePreferredCameraId(mPreferences,
1447                            ((mCameraId == mFrontCameraId)
1448                            ? mBackCameraId : mFrontCameraId));
1449                    onSharedPreferenceChanged();
1450                    return true;
1451                }
1452            }).setIcon(android.R.drawable.ic_menu_camera);
1453        }
1454    }
1455
1456    private PreferenceGroup filterPreferenceScreenByIntent(
1457            PreferenceGroup screen) {
1458        Intent intent = getIntent();
1459        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
1460            CameraSettings.removePreferenceFromScreen(screen,
1461                    CameraSettings.KEY_VIDEO_QUALITY);
1462        }
1463
1464        if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
1465            CameraSettings.removePreferenceFromScreen(screen,
1466                    CameraSettings.KEY_VIDEO_QUALITY);
1467        }
1468        return screen;
1469    }
1470
1471    // from MediaRecorder.OnErrorListener
1472    public void onError(MediaRecorder mr, int what, int extra) {
1473        Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
1474        if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1475            // We may have run out of space on the sdcard.
1476            stopVideoRecording();
1477            updateAndShowStorageHint();
1478        }
1479    }
1480
1481    // from MediaRecorder.OnInfoListener
1482    public void onInfo(MediaRecorder mr, int what, int extra) {
1483        if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
1484            if (mMediaRecorderRecording) onStopVideoRecording(true);
1485        } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
1486            if (mMediaRecorderRecording) onStopVideoRecording(true);
1487
1488            // Show the toast.
1489            Toast.makeText(this, R.string.video_reach_size_limit,
1490                    Toast.LENGTH_LONG).show();
1491        }
1492    }
1493
1494    /*
1495     * Make sure we're not recording music playing in the background, ask the
1496     * MediaPlaybackService to pause playback.
1497     */
1498    private void pauseAudioPlayback() {
1499        // Shamelessly copied from MediaPlaybackService.java, which
1500        // should be public, but isn't.
1501        Intent i = new Intent("com.android.music.musicservicecommand");
1502        i.putExtra("command", "pause");
1503
1504        sendBroadcast(i);
1505    }
1506
1507    // For testing.
1508    public boolean isRecording() {
1509        return mMediaRecorderRecording;
1510    }
1511
1512    private void startVideoRecording() {
1513        Log.v(TAG, "startVideoRecording");
1514
1515        updateAndShowStorageHint();
1516        if (mStorageSpace < Storage.LOW_STORAGE_THRESHOLD) {
1517            Log.v(TAG, "Storage issue, ignore the start request");
1518            return;
1519        }
1520
1521        if (effectsActive()) {
1522            initializeEffectsRecording();
1523            if (mEffectsRecorder == null) {
1524                Log.e(TAG, "Fail to initialize effect recorder");
1525                return;
1526            }
1527        } else {
1528            initializeRecorder();
1529            if (mMediaRecorder == null) {
1530                Log.e(TAG, "Fail to initialize media recorder");
1531                return;
1532            }
1533        }
1534
1535        pauseAudioPlayback();
1536
1537        if (effectsActive()) {
1538            try {
1539                mEffectsRecorder.startRecording();
1540            } catch (RuntimeException e) {
1541                Log.e(TAG, "Could not start effects recorder. ", e);
1542                releaseEffectsRecorder();
1543                return;
1544            }
1545        } else {
1546            try {
1547                mMediaRecorder.start(); // Recording is now started
1548            } catch (RuntimeException e) {
1549                Log.e(TAG, "Could not start media recorder. ", e);
1550                releaseMediaRecorder();
1551                // If start fails, frameworks will not lock the camera for us.
1552                mCameraDevice.lock();
1553                return;
1554            }
1555        }
1556
1557        enableCameraControls(false);
1558
1559        mMediaRecorderRecording = true;
1560        mRecordingStartTime = SystemClock.uptimeMillis();
1561        showRecordingUI(true);
1562
1563        updateRecordingTime();
1564        keepScreenOn();
1565    }
1566
1567    private void showRecordingUI(boolean recording) {
1568        if (recording) {
1569            mIndicatorControlContainer.dismissSecondLevelIndicator();
1570            if (mThumbnailView != null) mThumbnailView.setEnabled(false);
1571            mShutterButton.setBackgroundResource(R.drawable.btn_shutter_video_recording);
1572            mRecordingTimeView.setText("");
1573            mRecordingTimeView.setVisibility(View.VISIBLE);
1574            if (mReviewControl != null) mReviewControl.setVisibility(View.GONE);
1575            if (mCaptureTimeLapse) {
1576                if (Util.isTabletUI()) {
1577                    ((IndicatorControlWheelContainer) mIndicatorControlContainer)
1578                            .startTimeLapseAnimation(
1579                                    mTimeBetweenTimeLapseFrameCaptureMs,
1580                                    mRecordingStartTime);
1581                }
1582            }
1583        } else {
1584            if (mThumbnailView != null) mThumbnailView.setEnabled(true);
1585            mShutterButton.setBackgroundResource(R.drawable.btn_shutter_video);
1586            mRecordingTimeView.setVisibility(View.GONE);
1587            if (mReviewControl != null) mReviewControl.setVisibility(View.VISIBLE);
1588            if (mCaptureTimeLapse) {
1589                if (Util.isTabletUI()) {
1590                    ((IndicatorControlWheelContainer) mIndicatorControlContainer)
1591                            .stopTimeLapseAnimation();
1592                }
1593            }
1594        }
1595    }
1596
1597    private void getThumbnail() {
1598        if (mCurrentVideoUri != null) {
1599            Bitmap videoFrame = Thumbnail.createVideoThumbnail(mCurrentVideoFilename,
1600                    mPreviewFrameLayout.getWidth());
1601            if (videoFrame != null) {
1602                mThumbnail = new Thumbnail(mCurrentVideoUri, videoFrame, 0);
1603                mThumbnailView.setBitmap(mThumbnail.getBitmap());
1604                // Share popup may still have the reference to the old thumbnail. Clear it.
1605                mSharePopup = null;
1606            }
1607        }
1608    }
1609
1610    private void showAlert() {
1611        if (mCurrentVideoFilename != null) {
1612            Bitmap bitmap = Thumbnail.createVideoThumbnail(mCurrentVideoFilename,
1613                    mPreviewFrameLayout.getWidth());
1614            if (bitmap != null) {
1615                // MetadataRetriever already rotates the thumbnail. We should rotate
1616                // it to match the UI orientation (and mirror if it is front-facing camera).
1617                CameraInfo[] info = CameraHolder.instance().getCameraInfo();
1618                boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT);
1619                bitmap = Util.rotateAndMirror(bitmap, -mOrientationCompensationAtRecordStart,
1620                        mirror);
1621                mReviewImage.setImageBitmap(bitmap);
1622                mReviewImage.setVisibility(View.VISIBLE);
1623            }
1624        }
1625
1626        Util.fadeOut(mShutterButton);
1627        Util.fadeOut(mIndicatorControlContainer);
1628        int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play};
1629        for (int id : pickIds) {
1630            Util.fadeIn(findViewById(id));
1631        }
1632
1633        showTimeLapseUI(false);
1634    }
1635
1636    private void hideAlert() {
1637        mReviewImage.setVisibility(View.GONE);
1638        mShutterButton.setEnabled(true);
1639        enableCameraControls(true);
1640
1641        int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play};
1642        for (int id : pickIds) {
1643            Util.fadeOut(findViewById(id));
1644        }
1645        Util.fadeIn(mShutterButton);
1646        Util.fadeIn(mIndicatorControlContainer);
1647
1648        if (mCaptureTimeLapse) {
1649            showTimeLapseUI(true);
1650        }
1651    }
1652
1653    private void stopVideoRecording() {
1654        Log.v(TAG, "stopVideoRecording");
1655        if (mMediaRecorderRecording) {
1656            boolean shouldAddToMediaStoreNow = false;
1657
1658            try {
1659                if (effectsActive()) {
1660                    // This is asynchronous, so we cant add to media store now because thumbnail
1661                    // may not be ready. In such case addVideoToMediaStore is called later
1662                    // through a callback from the MediaEncoderFilter to EffectsRecorder,
1663                    // and then to the VideoCamera.
1664                    mEffectsRecorder.stopRecording();
1665                } else {
1666                    mMediaRecorder.setOnErrorListener(null);
1667                    mMediaRecorder.setOnInfoListener(null);
1668                    mMediaRecorder.stop();
1669                    shouldAddToMediaStoreNow = true;
1670                }
1671                mCurrentVideoFilename = mVideoFilename;
1672                Log.v(TAG, "Setting current video filename: "
1673                        + mCurrentVideoFilename);
1674            } catch (RuntimeException e) {
1675                Log.e(TAG, "stop fail",  e);
1676                if (mVideoFilename != null) deleteVideoFile(mVideoFilename);
1677            }
1678
1679            mMediaRecorderRecording = false;
1680            showRecordingUI(false);
1681            if (!mIsVideoCaptureIntent) {
1682                enableCameraControls(true);
1683            }
1684            // The orientation was fixed during video recording. Now make it
1685            // reflect the device orientation as video recording is stopped.
1686            setOrientationIndicator(mOrientationCompensation);
1687            keepScreenOnAwhile();
1688            if (shouldAddToMediaStoreNow) {
1689                addVideoToMediaStore();
1690            }
1691        }
1692        // always release media recorder
1693        if (!effectsActive()) {
1694            releaseMediaRecorder();
1695        }
1696    }
1697
1698    private void resetScreenOn() {
1699        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1700        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1701    }
1702
1703    private void keepScreenOnAwhile() {
1704        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1705        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1706        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1707    }
1708
1709    private void keepScreenOn() {
1710        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1711        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1712    }
1713
1714    private void initThumbnailButton() {
1715        mThumbnailView = (RotateImageView) findViewById(R.id.thumbnail);
1716        mThumbnailView.enableFilter(false);
1717        mThumbnailView.setVisibility(View.VISIBLE);
1718        // Load the thumbnail from the disk.
1719        mThumbnail = Thumbnail.loadFrom(new File(getFilesDir(), Thumbnail.LAST_THUMB_FILENAME));
1720    }
1721
1722    private void updateThumbnailButton() {
1723        if (mThumbnail == null || !Util.isUriValid(mThumbnail.getUri(), mContentResolver)) {
1724            mThumbnail = Thumbnail.getLastThumbnail(mContentResolver);
1725        }
1726        if (mThumbnail != null) {
1727            mThumbnailView.setBitmap(mThumbnail.getBitmap());
1728        } else {
1729            mThumbnailView.setBitmap(null);
1730        }
1731    }
1732
1733    private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
1734        long seconds = milliSeconds / 1000; // round down to compute seconds
1735        long minutes = seconds / 60;
1736        long hours = minutes / 60;
1737        long remainderMinutes = minutes - (hours * 60);
1738        long remainderSeconds = seconds - (minutes * 60);
1739
1740        StringBuilder timeStringBuilder = new StringBuilder();
1741
1742        // Hours
1743        if (hours > 0) {
1744            if (hours < 10) {
1745                timeStringBuilder.append('0');
1746            }
1747            timeStringBuilder.append(hours);
1748
1749            timeStringBuilder.append(':');
1750        }
1751
1752        // Minutes
1753        if (remainderMinutes < 10) {
1754            timeStringBuilder.append('0');
1755        }
1756        timeStringBuilder.append(remainderMinutes);
1757        timeStringBuilder.append(':');
1758
1759        // Seconds
1760        if (remainderSeconds < 10) {
1761            timeStringBuilder.append('0');
1762        }
1763        timeStringBuilder.append(remainderSeconds);
1764
1765        // Centi seconds
1766        if (displayCentiSeconds) {
1767            timeStringBuilder.append('.');
1768            long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
1769            if (remainderCentiSeconds < 10) {
1770                timeStringBuilder.append('0');
1771            }
1772            timeStringBuilder.append(remainderCentiSeconds);
1773        }
1774
1775        return timeStringBuilder.toString();
1776    }
1777
1778    private long getTimeLapseVideoLength(long deltaMs) {
1779        // For better approximation calculate fractional number of frames captured.
1780        // This will update the video time at a higher resolution.
1781        double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
1782        return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
1783    }
1784
1785    private void updateRecordingTime() {
1786        if (!mMediaRecorderRecording) {
1787            return;
1788        }
1789        long now = SystemClock.uptimeMillis();
1790        long delta = now - mRecordingStartTime;
1791
1792        // Starting a minute before reaching the max duration
1793        // limit, we'll countdown the remaining time instead.
1794        boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1795                && delta >= mMaxVideoDurationInMs - 60000);
1796
1797        long deltaAdjusted = delta;
1798        if (countdownRemainingTime) {
1799            deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
1800        }
1801        String text;
1802
1803        long targetNextUpdateDelay;
1804        if (!mCaptureTimeLapse) {
1805            text = millisecondToTimeString(deltaAdjusted, false);
1806            targetNextUpdateDelay = 1000;
1807        } else {
1808            // The length of time lapse video is different from the length
1809            // of the actual wall clock time elapsed. Display the video length
1810            // only in format hh:mm:ss.dd, where dd are the centi seconds.
1811            text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
1812            targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
1813        }
1814
1815        mRecordingTimeView.setText(text);
1816
1817        if (mRecordingTimeCountsDown != countdownRemainingTime) {
1818            // Avoid setting the color on every update, do it only
1819            // when it needs changing.
1820            mRecordingTimeCountsDown = countdownRemainingTime;
1821
1822            int color = getResources().getColor(countdownRemainingTime
1823                    ? R.color.recording_time_remaining_text
1824                    : R.color.recording_time_elapsed_text);
1825
1826            mRecordingTimeView.setTextColor(color);
1827        }
1828
1829        long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
1830        mHandler.sendEmptyMessageDelayed(
1831                UPDATE_RECORD_TIME, actualNextUpdateDelay);
1832    }
1833
1834    private static boolean isSupported(String value, List<String> supported) {
1835        return supported == null ? false : supported.indexOf(value) >= 0;
1836    }
1837
1838    private void setCameraParameters() {
1839        mParameters = mCameraDevice.getParameters();
1840
1841        mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
1842        mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
1843
1844        // Set flash mode.
1845        String flashMode = mPreferences.getString(
1846                CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
1847                getString(R.string.pref_camera_video_flashmode_default));
1848        List<String> supportedFlash = mParameters.getSupportedFlashModes();
1849        if (isSupported(flashMode, supportedFlash)) {
1850            mParameters.setFlashMode(flashMode);
1851        } else {
1852            flashMode = mParameters.getFlashMode();
1853            if (flashMode == null) {
1854                flashMode = getString(
1855                        R.string.pref_camera_flashmode_no_flash);
1856            }
1857        }
1858
1859        // Set white balance parameter.
1860        String whiteBalance = mPreferences.getString(
1861                CameraSettings.KEY_WHITE_BALANCE,
1862                getString(R.string.pref_camera_whitebalance_default));
1863        if (isSupported(whiteBalance,
1864                mParameters.getSupportedWhiteBalance())) {
1865            mParameters.setWhiteBalance(whiteBalance);
1866        } else {
1867            whiteBalance = mParameters.getWhiteBalance();
1868            if (whiteBalance == null) {
1869                whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1870            }
1871        }
1872
1873        // Set zoom.
1874        if (mParameters.isZoomSupported()) {
1875            mParameters.setZoom(mZoomValue);
1876        }
1877
1878        // Set continuous autofocus.
1879        List<String> supportedFocus = mParameters.getSupportedFocusModes();
1880        if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
1881            mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
1882        }
1883
1884        mParameters.setRecordingHint(true);
1885
1886        // Enable video stabilization. Convenience methods not available in API
1887        // level <= 14
1888        String vstabSupported = mParameters.get("video-stabilization-supported");
1889        if ("true".equals(vstabSupported)) {
1890            mParameters.set("video-stabilization", "true");
1891        }
1892
1893        // Set picture size.
1894        String pictureSize = mPreferences.getString(
1895                CameraSettings.KEY_PICTURE_SIZE, null);
1896        if (pictureSize == null) {
1897            CameraSettings.initialCameraPictureSize(this, mParameters);
1898        } else {
1899            List<Size> supported = mParameters.getSupportedPictureSizes();
1900            CameraSettings.setCameraPictureSize(
1901                    pictureSize, supported, mParameters);
1902        }
1903
1904        // Set JPEG quality.
1905        int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1906                CameraProfile.QUALITY_HIGH);
1907        mParameters.setJpegQuality(jpegQuality);
1908
1909        mCameraDevice.setParameters(mParameters);
1910        // Keep preview size up to date.
1911        mParameters = mCameraDevice.getParameters();
1912    }
1913
1914    private boolean switchToOtherMode(int mode) {
1915        if (isFinishing()) return false;
1916        MenuHelper.gotoMode(mode, this);
1917        finish();
1918        return true;
1919    }
1920
1921    public boolean onModeChanged(int mode) {
1922        if (mode != ModePicker.MODE_VIDEO) {
1923            return switchToOtherMode(mode);
1924        } else {
1925            return true;
1926        }
1927    }
1928
1929    @Override
1930    public void onActivityResult(int requestCode, int resultCode, Intent data) {
1931        switch (requestCode) {
1932            case EffectsRecorder.EFFECT_BACKDROPPER:
1933                if (resultCode == RESULT_OK) {
1934                    // onActivityResult() runs before onResume(), so this parameter will be
1935                    // seen by startPreview from onResume()
1936                    mEffectUriFromGallery = ((Uri) data.getData()).toString();
1937                    Log.v(TAG, "Received URI from gallery: " + mEffectUriFromGallery);
1938                }
1939                break;
1940            default:
1941                Log.e(TAG, "Unknown activity result sent to Camera!");
1942                break;
1943        }
1944    }
1945
1946    @Override
1947    public void onEffectsUpdate(int effectId, int effectMsg) {
1948        if (effectMsg == EffectsRecorder.EFFECT_MSG_EFFECTS_STOPPED) {
1949            // Effects have shut down. Hide learning message if any,
1950            // and restart regular preview.
1951            mBgLearningMessageFrame.setVisibility(View.GONE);
1952            checkQualityAndStartPreview();
1953        } else if (effectMsg == EffectsRecorder.EFFECT_MSG_RECORDING_DONE) {
1954            addVideoToMediaStore();
1955            getThumbnail();
1956        } else if (effectId == EffectsRecorder.EFFECT_BACKDROPPER) {
1957            switch (effectMsg) {
1958                case EffectsRecorder.EFFECT_MSG_STARTED_LEARNING:
1959                    mBgLearningMessageFrame.setVisibility(View.VISIBLE);
1960                    break;
1961                case EffectsRecorder.EFFECT_MSG_DONE_LEARNING:
1962                case EffectsRecorder.EFFECT_MSG_SWITCHING_EFFECT:
1963                    mBgLearningMessageFrame.setVisibility(View.GONE);
1964                    break;
1965            }
1966        }
1967    }
1968
1969    public void onCancelBgTraining(View v) {
1970        // Remove training message
1971        mBgLearningMessageFrame.setVisibility(View.GONE);
1972        // Write default effect out to shared prefs
1973        writeDefaultEffectToPrefs();
1974        // Tell the indicator controller to redraw based on new shared pref values
1975        mIndicatorControlContainer.reloadPreferences();
1976        // Tell VideoCamer to re-init based on new shared pref values.
1977        onSharedPreferenceChanged();
1978    }
1979
1980    @Override
1981    public synchronized void onEffectsError(Exception exception, String fileName) {
1982        // TODO: Eventually we may want to show the user an error dialog, and then restart the
1983        // camera and encoder gracefully. For now, we just delete the file and bail out.
1984        if (fileName != null && new File(fileName).exists()) {
1985            deleteVideoFile(fileName);
1986        }
1987        if (exception instanceof MediaRecorderStopException) {
1988            Log.w(TAG, "Problem recoding video file. Removing incomplete file.");
1989            return;
1990        }
1991        throw new RuntimeException("Error during recording!", exception);
1992    }
1993
1994    @Override
1995    public void onConfigurationChanged(Configuration config) {
1996        super.onConfigurationChanged(config);
1997    }
1998
1999    public void onOverriddenPreferencesClicked() {
2000    }
2001
2002    public void onRestorePreferencesClicked() {
2003        Runnable runnable = new Runnable() {
2004            public void run() {
2005                restorePreferences();
2006            }
2007        };
2008        MenuHelper.confirmAction(this,
2009                getString(R.string.confirm_restore_title),
2010                getString(R.string.confirm_restore_message),
2011                runnable);
2012    }
2013
2014    private void restorePreferences() {
2015        // Reset the zoom. Zoom value is not stored in preference.
2016        if (mParameters.isZoomSupported()) {
2017            mZoomValue = 0;
2018            setCameraParameters();
2019            mZoomControl.setZoomIndex(0);
2020        }
2021
2022        if (mIndicatorControlContainer != null) {
2023            mIndicatorControlContainer.dismissSettingPopup();
2024            CameraSettings.restorePreferences(this, mPreferences,
2025                    mParameters);
2026            mIndicatorControlContainer.reloadPreferences();
2027            onSharedPreferenceChanged();
2028        }
2029    }
2030
2031    private boolean effectsActive() {
2032        return (mEffectType != EffectsRecorder.EFFECT_NONE);
2033    }
2034
2035    public void onSharedPreferenceChanged() {
2036        // ignore the events after "onPause()" or preview has not started yet
2037        if (mPausing) return;
2038        synchronized (mPreferences) {
2039            // If mCameraDevice is not ready then we can set the parameter in
2040            // startPreview().
2041            if (mCameraDevice == null) return;
2042
2043            boolean recordLocation = RecordLocationPreference.get(
2044                    mPreferences, getContentResolver());
2045            mLocationManager.recordLocation(recordLocation);
2046
2047            // Check if the current effects selection has changed
2048            if (updateEffectSelection()) return;
2049
2050            // Check if camera id is changed.
2051            int cameraId = CameraSettings.readPreferredCameraId(mPreferences);
2052            if (mCameraId != cameraId) {
2053                // Restart the activity to have a crossfade animation.
2054                // TODO: Use SurfaceTexture to implement a better and faster
2055                // animation.
2056                if (mIsVideoCaptureIntent) {
2057                    // If the intent is video capture, stay in video capture mode.
2058                    MenuHelper.gotoVideoMode(this, getIntent());
2059                } else {
2060                    MenuHelper.gotoVideoMode(this);
2061                }
2062                finish();
2063            } else {
2064                readVideoPreferences();
2065                showTimeLapseUI(mCaptureTimeLapse);
2066                // We need to restart the preview if preview size is changed.
2067                Size size = mParameters.getPreviewSize();
2068                if (size.width != mDesiredPreviewWidth
2069                        || size.height != mDesiredPreviewHeight) {
2070                    if (!effectsActive()) {
2071                        mCameraDevice.stopPreview();
2072                    } else {
2073                        mEffectsRecorder.release();
2074                    }
2075                    resizeForPreviewAspectRatio();
2076                    startPreview(); // Parameters will be set in startPreview().
2077                } else {
2078                    setCameraParameters();
2079                }
2080            }
2081        }
2082    }
2083
2084    private boolean updateEffectSelection() {
2085        int previousEffectType = mEffectType;
2086        Object previousEffectParameter = mEffectParameter;
2087        mEffectType = CameraSettings.readEffectType(mPreferences);
2088        mEffectParameter = CameraSettings.readEffectParameter(mPreferences);
2089
2090        if (mEffectType == previousEffectType) {
2091            if (mEffectType == EffectsRecorder.EFFECT_NONE) return false;
2092            if (mEffectParameter.equals(previousEffectParameter)) return false;
2093        }
2094        Log.v(TAG, "New effect selection: " + mPreferences.getString(
2095                CameraSettings.KEY_VIDEO_EFFECT, "none"));
2096
2097        if (mEffectType == EffectsRecorder.EFFECT_NONE) {
2098            // Stop effects and return to normal preview
2099            mEffectsRecorder.stopPreview();
2100            return true;
2101        }
2102        if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER &&
2103            ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) {
2104            // Request video from gallery to use for background
2105            Intent i = new Intent(Intent.ACTION_PICK);
2106            i.setDataAndType(Video.Media.EXTERNAL_CONTENT_URI,
2107                             "video/*");
2108            i.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
2109            startActivityForResult(i, EffectsRecorder.EFFECT_BACKDROPPER);
2110            return true;
2111        }
2112        if (previousEffectType == EffectsRecorder.EFFECT_NONE) {
2113            // Stop regular preview and start effects.
2114            mCameraDevice.stopPreview();
2115            checkQualityAndStartPreview();
2116        } else {
2117            // Switch currently running effect
2118            mEffectsRecorder.setEffect(mEffectType, mEffectParameter);
2119        }
2120        return true;
2121    }
2122
2123    // Verifies that the current preview view size is correct before starting
2124    // preview. If not, resets the surface holder and resizes the view.
2125    private void checkQualityAndStartPreview() {
2126        readVideoPreferences();
2127        showTimeLapseUI(mCaptureTimeLapse);
2128        Size size = mParameters.getPreviewSize();
2129        if (size.width != mDesiredPreviewWidth
2130                || size.height != mDesiredPreviewHeight) {
2131            resizeForPreviewAspectRatio();
2132        } else {
2133            // Start up preview again
2134            startPreview();
2135        }
2136    }
2137
2138    private void showTimeLapseUI(boolean enable) {
2139        if (mTimeLapseLabel != null) {
2140            mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.GONE);
2141        }
2142    }
2143
2144    private void showSharePopup() {
2145        Uri uri = mThumbnail.getUri();
2146        if (mSharePopup == null || !uri.equals(mSharePopup.getUri())) {
2147            mSharePopup = new SharePopup(this, uri, mThumbnail.getBitmap(),
2148                    mOrientationCompensation, mPreviewPanel);
2149        }
2150        mSharePopup.showAtLocation(mThumbnailView, Gravity.NO_GRAVITY, 0, 0);
2151    }
2152
2153    @Override
2154    public boolean dispatchTouchEvent(MotionEvent m) {
2155        // Check if the popup window should be dismissed first.
2156        if (mPopupGestureDetector != null && mPopupGestureDetector.onTouchEvent(m)) {
2157            return true;
2158        }
2159
2160        return super.dispatchTouchEvent(m);
2161    }
2162
2163    private class PopupGestureListener extends
2164            GestureDetector.SimpleOnGestureListener {
2165        @Override
2166        public boolean onDown(MotionEvent e) {
2167            // Check if the popup window is visible.
2168            View popup = mIndicatorControlContainer.getActiveSettingPopup();
2169            if (popup == null) return false;
2170
2171            // Let popup window or indicator wheel handle the event by
2172            // themselves. Dismiss the popup window if users touch on other
2173            // areas.
2174            if (!Util.pointInView(e.getX(), e.getY(), popup)
2175                    && !Util.pointInView(e.getX(), e.getY(), mIndicatorControlContainer)) {
2176                mIndicatorControlContainer.dismissSettingPopup();
2177                // Let event fall through.
2178            }
2179            return false;
2180        }
2181    }
2182
2183    private class ZoomChangeListener implements ZoomControl.OnZoomChangedListener {
2184        // only for immediate zoom
2185        @Override
2186        public void onZoomValueChanged(int index) {
2187            VideoCamera.this.onZoomValueChanged(index);
2188        }
2189
2190        // only for smooth zoom
2191        @Override
2192        public void onZoomStateChanged(int state) {
2193            if (mPausing) return;
2194
2195            Log.v(TAG, "zoom picker state=" + state);
2196            if (state == ZoomControl.ZOOM_IN) {
2197                VideoCamera.this.onZoomValueChanged(mZoomMax);
2198            } else if (state == ZoomControl.ZOOM_OUT){
2199                VideoCamera.this.onZoomValueChanged(0);
2200            } else {
2201                mTargetZoomValue = -1;
2202                if (mZoomState == ZOOM_START) {
2203                    mZoomState = ZOOM_STOPPING;
2204                    mCameraDevice.stopSmoothZoom();
2205                }
2206            }
2207        }
2208    }
2209
2210    private void initializeZoom() {
2211        mZoomControl = (ZoomControl) findViewById(R.id.zoom_control);
2212        // Get the parameter to make sure we have the up-to-date zoom value.
2213        mParameters = mCameraDevice.getParameters();
2214        if (!mParameters.isZoomSupported()) return;
2215
2216        mZoomMax = mParameters.getMaxZoom();
2217        // Currently we use immediate zoom for fast zooming to get better UX and
2218        // there is no plan to take advantage of the smooth zoom.
2219        mZoomControl.setZoomMax(mZoomMax);
2220        mZoomControl.setZoomIndex(mParameters.getZoom());
2221        mZoomControl.setSmoothZoomSupported(mSmoothZoomSupported);
2222        mZoomControl.setOnZoomChangeListener(new ZoomChangeListener());
2223        mCameraDevice.setZoomChangeListener(mZoomListener);
2224    }
2225
2226    private final class ZoomListener
2227            implements android.hardware.Camera.OnZoomChangeListener {
2228        @Override
2229        public void onZoomChange(int value, boolean stopped, android.hardware.Camera camera) {
2230            Log.v(TAG, "Zoom changed: value=" + value + ". stopped=" + stopped);
2231            mZoomValue = value;
2232
2233            // Update the UI when we get zoom value.
2234            mZoomControl.setZoomIndex(value);
2235
2236            // Keep mParameters up to date. We do not getParameter again in
2237            // takePicture. If we do not do this, wrong zoom value will be set.
2238            mParameters.setZoom(value);
2239
2240            if (stopped && mZoomState != ZOOM_STOPPED) {
2241                if (mTargetZoomValue != -1 && value != mTargetZoomValue) {
2242                    mCameraDevice.startSmoothZoom(mTargetZoomValue);
2243                    mZoomState = ZOOM_START;
2244                } else {
2245                    mZoomState = ZOOM_STOPPED;
2246                }
2247            }
2248        }
2249    }
2250
2251    private void onZoomValueChanged(int index) {
2252        // Not useful to change zoom value when the activity is paused.
2253        if (mPausing) return;
2254
2255        if (mSmoothZoomSupported) {
2256            if (mTargetZoomValue != index && mZoomState != ZOOM_STOPPED) {
2257                mTargetZoomValue = index;
2258                if (mZoomState == ZOOM_START) {
2259                    mZoomState = ZOOM_STOPPING;
2260                    mCameraDevice.stopSmoothZoom();
2261                }
2262            } else if (mZoomState == ZOOM_STOPPED && mZoomValue != index) {
2263                mTargetZoomValue = index;
2264                mCameraDevice.startSmoothZoom(index);
2265                mZoomState = ZOOM_START;
2266            }
2267        } else {
2268            mZoomValue = index;
2269            setCameraParameters();
2270        }
2271    }
2272
2273    private void initializeVideoSnapshot() {
2274        if (mParameters.isVideoSnapshotSupported() && !mIsVideoCaptureIntent) {
2275            findViewById(R.id.camera_preview).setOnTouchListener(this);
2276        }
2277    }
2278
2279    void showVideoSnapshotUI(boolean enabled) {
2280        if (mParameters.isVideoSnapshotSupported() && !mIsVideoCaptureIntent) {
2281            mPreviewFrameLayout.showBorder(enabled);
2282            mIndicatorControlContainer.enableZoom(!enabled);
2283            mShutterButton.setEnabled(!enabled);
2284        }
2285    }
2286
2287    // Preview area is touched. Take a picture.
2288    @Override
2289    public boolean onTouch(View v, MotionEvent e) {
2290        if (mMediaRecorderRecording && effectsActive()) {
2291            Toast.makeText(this, getResources().getString(
2292                    R.string.disable_video_snapshot_hint), Toast.LENGTH_LONG).show();
2293            return false;
2294        }
2295
2296        if (mPausing || mSnapshotInProgress
2297                || !mMediaRecorderRecording || effectsActive()) {
2298            return false;
2299        }
2300
2301        // Set rotation and gps data.
2302        Util.setRotationParameter(mParameters, mCameraId, mOrientation);
2303        Location loc = mLocationManager.getCurrentLocation();
2304        Util.setGpsParameters(mParameters, loc);
2305        mCameraDevice.setParameters(mParameters);
2306
2307        Log.v(TAG, "Video snapshot start");
2308        mCameraDevice.takePicture(null, null, null, new JpegPictureCallback(loc));
2309        showVideoSnapshotUI(true);
2310        mSnapshotInProgress = true;
2311        return true;
2312    }
2313
2314    private final class JpegPictureCallback implements PictureCallback {
2315        Location mLocation;
2316
2317        public JpegPictureCallback(Location loc) {
2318            mLocation = loc;
2319        }
2320
2321        @Override
2322        public void onPictureTaken(byte [] jpegData, android.hardware.Camera camera) {
2323            Log.v(TAG, "onPictureTaken");
2324            mSnapshotInProgress = false;
2325            showVideoSnapshotUI(false);
2326            storeImage(jpegData, mLocation);
2327        }
2328    }
2329
2330    private void storeImage(final byte[] data, Location loc) {
2331        long dateTaken = System.currentTimeMillis();
2332        String title = Util.createJpegName(dateTaken);
2333        int orientation = Exif.getOrientation(data);
2334        Size s = mParameters.getPictureSize();
2335        Uri uri = Storage.addImage(mContentResolver, title, dateTaken, loc, orientation, data,
2336                s.width, s.height);
2337        if (uri != null) {
2338            // Create a thumbnail whose width is equal or bigger than that of the preview.
2339            int ratio = (int) Math.ceil((double) mParameters.getPictureSize().width
2340                    / mPreviewFrameLayout.getWidth());
2341            int inSampleSize = Integer.highestOneBit(ratio);
2342            mThumbnail = Thumbnail.createThumbnail(data, orientation, inSampleSize, uri);
2343            if (mThumbnail != null) {
2344                mThumbnailView.setBitmap(mThumbnail.getBitmap());
2345            }
2346            // Share popup may still have the reference to the old thumbnail. Clear it.
2347            mSharePopup = null;
2348            Util.broadcastNewPicture(this, uri);
2349        }
2350    }
2351}
2352