VideoCamera.java revision 72d0f95789afb93ea795c9e5deefcbe53d41fef7
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 android.content.ActivityNotFoundException;
20import android.content.BroadcastReceiver;
21import android.content.ContentResolver;
22import android.content.ContentValues;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.content.SharedPreferences;
27import android.content.res.Resources;
28import android.graphics.Bitmap;
29import android.graphics.drawable.Drawable;
30import android.hardware.Camera.Parameters;
31import android.hardware.Camera.Size;
32import android.media.CamcorderProfile;
33import android.media.MediaRecorder;
34import android.media.ThumbnailUtils;
35import android.net.Uri;
36import android.os.Build;
37import android.os.Bundle;
38import android.os.Environment;
39import android.os.Handler;
40import android.os.Message;
41import android.os.StatFs;
42import android.os.SystemClock;
43import android.preference.PreferenceManager;
44import android.provider.MediaStore;
45import android.provider.Settings;
46import android.provider.MediaStore.Video;
47import android.util.Log;
48import android.view.KeyEvent;
49import android.view.LayoutInflater;
50import android.view.Menu;
51import android.view.MenuItem;
52import android.view.SurfaceHolder;
53import android.view.SurfaceView;
54import android.view.View;
55import android.view.ViewGroup;
56import android.view.Window;
57import android.view.WindowManager;
58import android.view.MenuItem.OnMenuItemClickListener;
59import android.view.animation.AlphaAnimation;
60import android.view.animation.Animation;
61import android.widget.ImageView;
62import android.widget.TextView;
63import android.widget.Toast;
64
65import com.android.camera.gallery.IImage;
66import com.android.camera.gallery.IImageList;
67import com.android.camera.ui.CamcorderHeadUpDisplay;
68import com.android.camera.ui.GLRootView;
69import com.android.camera.ui.GLView;
70import com.android.camera.ui.HeadUpDisplay;
71
72import java.io.File;
73import java.io.FileDescriptor;
74import java.io.IOException;
75import java.text.SimpleDateFormat;
76import java.util.ArrayList;
77import java.util.Date;
78import java.util.List;
79
80/**
81 * The Camcorder activity.
82 */
83public class VideoCamera extends NoSearchActivity
84        implements View.OnClickListener,
85        ShutterButton.OnShutterButtonListener, SurfaceHolder.Callback,
86        MediaRecorder.OnErrorListener, MediaRecorder.OnInfoListener,
87        Switcher.OnSwitchListener, PreviewFrameLayout.OnSizeChangedListener {
88
89    private static final String TAG = "videocamera";
90
91    private static final int INIT_RECORDER = 3;
92    private static final int CLEAR_SCREEN_DELAY = 4;
93    private static final int UPDATE_RECORD_TIME = 5;
94    private static final int ENABLE_SHUTTER_BUTTON = 6;
95
96    private static final int SCREEN_DELAY = 2 * 60 * 1000;
97
98    // The brightness settings used when it is set to automatic in the system.
99    // The reason why it is set to 0.7 is just because 1.0 is too bright.
100    private static final float DEFAULT_CAMERA_BRIGHTNESS = 0.7f;
101
102    private static final long NO_STORAGE_ERROR = -1L;
103    private static final long CANNOT_STAT_ERROR = -2L;
104    private static final long LOW_STORAGE_THRESHOLD = 512L * 1024L;
105
106    private static final int STORAGE_STATUS_OK = 0;
107    private static final int STORAGE_STATUS_LOW = 1;
108    private static final int STORAGE_STATUS_NONE = 2;
109
110    private static final boolean SWITCH_CAMERA = true;
111    private static final boolean SWITCH_VIDEO = false;
112
113    private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
114
115    private static final String GALLERY_PACKAGE_NAME = "com.cooliris.app";
116    private static final String GALLERY_CLASS_NAME = "com.cooliris.media.Gallery";
117    private static final String REVIEW_EXTRA_IN_GALLERY = "review";
118
119    /**
120     * An unpublished intent flag requesting to start recording straight away
121     * and return as soon as recording is stopped.
122     * TODO: consider publishing by moving into MediaStore.
123     */
124    private final static String EXTRA_QUICK_CAPTURE =
125            "android.intent.extra.quickCapture";
126
127    private SharedPreferences mPreferences;
128
129    private PreviewFrameLayout mPreviewFrameLayout;
130    private SurfaceView mVideoPreview;
131    private SurfaceHolder mSurfaceHolder = null;
132    private ImageView mVideoFrame;
133    private GLRootView mGLRootView;
134    private CamcorderHeadUpDisplay mHeadUpDisplay;
135
136    private boolean mIsVideoCaptureIntent;
137    private boolean mQuickCapture;
138    // mLastPictureButton and mThumbController
139    // are non-null only if mIsVideoCaptureIntent is true.
140    private ImageView mLastPictureButton;
141    private ThumbnailController mThumbController;
142    private boolean mStartPreviewFail = false;
143
144    private int mStorageStatus = STORAGE_STATUS_OK;
145
146    private MediaRecorder mMediaRecorder;
147    private boolean mMediaRecorderRecording = false;
148    private long mRecordingStartTime;
149    // The video file that the hardware camera is about to record into
150    // (or is recording into.)
151    private String mCameraVideoFilename;
152    private FileDescriptor mCameraVideoFileDescriptor;
153
154    // The video file that has already been recorded, and that is being
155    // examined by the user.
156    private String mCurrentVideoFilename;
157    private Uri mCurrentVideoUri;
158    private ContentValues mCurrentVideoValues;
159
160    private CamcorderProfile mProfile;
161
162    // The video duration limit. 0 menas no limit.
163    private int mMaxVideoDurationInMs;
164
165    boolean mPausing = false;
166    boolean mPreviewing = false; // True if preview is started.
167
168    private ContentResolver mContentResolver;
169
170    private ShutterButton mShutterButton;
171    private TextView mRecordingTimeView;
172    private Switcher mSwitcher;
173    private boolean mRecordingTimeCountsDown = false;
174
175    private final ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>();
176
177    private final Handler mHandler = new MainHandler();
178    private Parameters mParameters;
179
180    // This Handler is used to post message back onto the main thread of the
181    // application
182    private class MainHandler extends Handler {
183        @Override
184        public void handleMessage(Message msg) {
185            switch (msg.what) {
186
187                case ENABLE_SHUTTER_BUTTON:
188                    mShutterButton.setEnabled(true);
189                    break;
190
191                case CLEAR_SCREEN_DELAY: {
192                    getWindow().clearFlags(
193                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
194                    break;
195                }
196
197                case UPDATE_RECORD_TIME: {
198                    updateRecordingTime();
199                    break;
200                }
201
202                case INIT_RECORDER: {
203                    initializeRecorder();
204                    break;
205                }
206
207                default:
208                    Log.v(TAG, "Unhandled message: " + msg.what);
209                    break;
210            }
211        }
212    }
213
214    private BroadcastReceiver mReceiver = null;
215
216    private class MyBroadcastReceiver extends BroadcastReceiver {
217        @Override
218        public void onReceive(Context context, Intent intent) {
219            String action = intent.getAction();
220            if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
221                updateAndShowStorageHint(false);
222                stopVideoRecording();
223            } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
224                updateAndShowStorageHint(true);
225                initializeRecorder();
226            } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
227                // SD card unavailable
228                // handled in ACTION_MEDIA_EJECT
229            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
230                Toast.makeText(VideoCamera.this,
231                        getResources().getString(R.string.wait), 5000);
232            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
233                updateAndShowStorageHint(true);
234            }
235        }
236    }
237
238    private String createName(long dateTaken) {
239        Date date = new Date(dateTaken);
240        SimpleDateFormat dateFormat = new SimpleDateFormat(
241                getString(R.string.video_file_name_format));
242
243        return dateFormat.format(date);
244    }
245
246    private void showCameraBusyAndFinish() {
247        Resources ress = getResources();
248        Util.showFatalErrorAndFinish(VideoCamera.this,
249                ress.getString(R.string.camera_error_title),
250                ress.getString(R.string.cannot_connect_camera));
251    }
252
253    @Override
254    public void onCreate(Bundle icicle) {
255        super.onCreate(icicle);
256
257        Window win = getWindow();
258
259        // Overright the brightness settings if it is automatic
260        int mode = Settings.System.getInt(
261                getContentResolver(),
262                Settings.System.SCREEN_BRIGHTNESS_MODE,
263                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
264        if (mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) {
265            WindowManager.LayoutParams winParams = win.getAttributes();
266            winParams.screenBrightness = DEFAULT_CAMERA_BRIGHTNESS;
267            win.setAttributes(winParams);
268        }
269
270        mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
271        CameraSettings.upgradePreferences(mPreferences);
272
273        readVideoPreferences();
274
275        /*
276         * To reduce startup time, we start the preview in another thread.
277         * We make sure the preview is started at the end of onCreate.
278         */
279        Thread startPreviewThread = new Thread(new Runnable() {
280            public void run() {
281                try {
282                    mStartPreviewFail = false;
283                    startPreview();
284                } catch (CameraHardwareException e) {
285                    // In eng build, we throw the exception so that test tool
286                    // can detect it and report it
287                    if ("eng".equals(Build.TYPE)) {
288                        throw new RuntimeException(e);
289                    }
290                    mStartPreviewFail = true;
291                }
292            }
293        });
294        startPreviewThread.start();
295
296        mContentResolver = getContentResolver();
297
298        requestWindowFeature(Window.FEATURE_PROGRESS);
299        setContentView(R.layout.video_camera);
300
301        mPreviewFrameLayout = (PreviewFrameLayout)
302                findViewById(R.id.frame_layout);
303        mPreviewFrameLayout.setOnSizeChangedListener(this);
304        resizeForPreviewAspectRatio();
305
306        mVideoPreview = (SurfaceView) findViewById(R.id.camera_preview);
307        mVideoFrame = (ImageView) findViewById(R.id.video_frame);
308        mGLRootView = (GLRootView) findViewById(R.id.settings_ui);
309
310        // don't set mSurfaceHolder here. We have it set ONLY within
311        // surfaceCreated / surfaceDestroyed, other parts of the code
312        // assume that when it is set, the surface is also set.
313        SurfaceHolder holder = mVideoPreview.getHolder();
314        holder.addCallback(this);
315        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
316
317        mIsVideoCaptureIntent = isVideoCaptureIntent();
318        mQuickCapture = getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
319        mRecordingTimeView = (TextView) findViewById(R.id.recording_time);
320
321        ViewGroup rootView = (ViewGroup) findViewById(R.id.video_camera);
322        LayoutInflater inflater = this.getLayoutInflater();
323        if (!mIsVideoCaptureIntent) {
324            View controlBar = inflater.inflate(
325                    R.layout.camera_control, rootView);
326            mLastPictureButton =
327                    (ImageView) controlBar.findViewById(R.id.review_thumbnail);
328            mThumbController = new ThumbnailController(
329                    getResources(), mLastPictureButton, mContentResolver);
330            mLastPictureButton.setOnClickListener(this);
331            mThumbController.loadData(ImageManager.getLastVideoThumbPath());
332            mSwitcher = ((Switcher) findViewById(R.id.camera_switch));
333            mSwitcher.setOnSwitchListener(this);
334            mSwitcher.addTouchView(findViewById(R.id.camera_switch_set));
335        } else {
336            View controlBar = inflater.inflate(
337                    R.layout.attach_camera_control, rootView);
338            controlBar.findViewById(R.id.btn_cancel).setOnClickListener(this);
339            ImageView retake =
340                    (ImageView) controlBar.findViewById(R.id.btn_retake);
341            retake.setOnClickListener(this);
342            retake.setImageResource(R.drawable.btn_ic_review_retake_video);
343            controlBar.findViewById(R.id.btn_play).setOnClickListener(this);
344            controlBar.findViewById(R.id.btn_done).setOnClickListener(this);
345        }
346
347        mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
348        mShutterButton.setImageResource(R.drawable.btn_ic_video_record);
349        mShutterButton.setOnShutterButtonListener(this);
350        mShutterButton.requestFocus();
351
352        // Make sure preview is started.
353        try {
354            startPreviewThread.join();
355            if (mStartPreviewFail) {
356                showCameraBusyAndFinish();
357                return;
358            }
359        } catch (InterruptedException ex) {
360            // ignore
361        }
362        mHandler.post(new Runnable() {
363            public void run() {
364                initializeHeadUpDisplay();
365            }
366        });
367    }
368
369    private void initializeHeadUpDisplay() {
370        mHeadUpDisplay = new CamcorderHeadUpDisplay(this);
371        CameraSettings settings = new CameraSettings(this, mParameters);
372
373        PreferenceGroup group =
374                settings.getPreferenceGroup(R.xml.video_preferences);
375        if (mIsVideoCaptureIntent) {
376            group = filterPreferenceScreenByIntent(group);
377        }
378        mHeadUpDisplay.initialize(this, group);
379        mGLRootView.setContentPane(mHeadUpDisplay);
380        mHeadUpDisplay.setListener(new MyHeadUpDisplayListener());
381    }
382
383    @Override
384    protected void onStart() {
385        super.onStart();
386        if (!mIsVideoCaptureIntent) {
387            mSwitcher.setSwitch(SWITCH_VIDEO);
388        }
389    }
390
391    private void startPlayVideoActivity() {
392        Intent intent = new Intent(Intent.ACTION_VIEW, mCurrentVideoUri);
393        try {
394            startActivity(intent);
395        } catch (android.content.ActivityNotFoundException ex) {
396            Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
397        }
398    }
399
400    public void onClick(View v) {
401        switch (v.getId()) {
402            case R.id.btn_retake:
403                discardCurrentVideoAndInitRecorder();
404                break;
405            case R.id.btn_play:
406                startPlayVideoActivity();
407                break;
408            case R.id.btn_done:
409                doReturnToCaller(true);
410                break;
411            case R.id.btn_cancel:
412                stopVideoRecordingAndReturn(false);
413                break;
414            case R.id.review_thumbnail:
415                if (!mMediaRecorderRecording) viewLastVideo();
416                break;
417        }
418    }
419
420    public void onShutterButtonFocus(ShutterButton button, boolean pressed) {
421        // Do nothing (everything happens in onShutterButtonClick).
422    }
423
424    private void onStopVideoRecording(boolean valid) {
425        if (mIsVideoCaptureIntent) {
426            if (mQuickCapture) {
427                stopVideoRecordingAndReturn(valid);
428            } else {
429                stopVideoRecordingAndShowAlert();
430            }
431        } else {
432            stopVideoRecordingAndGetThumbnail();
433            initializeRecorder();
434        }
435    }
436
437    public void onShutterButtonClick(ShutterButton button) {
438        switch (button.getId()) {
439            case R.id.shutter_button:
440                if (mHeadUpDisplay.collapse()) return;
441
442                if (mMediaRecorderRecording) {
443                    onStopVideoRecording(true);
444                } else if (mMediaRecorder != null) {
445                    // If the click comes before recorder initialization, it is
446                    // ignored. If users click the button during initialization,
447                    // the event is put in the queue and record will be started
448                    // eventually.
449                    startVideoRecording();
450                }
451                mShutterButton.setEnabled(false);
452                mHandler.sendEmptyMessageDelayed(
453                        ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
454                break;
455        }
456    }
457
458    private void discardCurrentVideoAndInitRecorder() {
459        deleteCurrentVideo();
460        hideAlertAndInitializeRecorder();
461    }
462
463    private OnScreenHint mStorageHint;
464
465    private void updateAndShowStorageHint(boolean mayHaveSd) {
466        mStorageStatus = getStorageStatus(mayHaveSd);
467        showStorageHint();
468    }
469
470    private void showStorageHint() {
471        String errorMessage = null;
472        switch (mStorageStatus) {
473            case STORAGE_STATUS_NONE:
474                errorMessage = getString(R.string.no_storage);
475                break;
476            case STORAGE_STATUS_LOW:
477                errorMessage = getString(R.string.spaceIsLow_content);
478        }
479        if (errorMessage != null) {
480            if (mStorageHint == null) {
481                mStorageHint = OnScreenHint.makeText(this, errorMessage);
482            } else {
483                mStorageHint.setText(errorMessage);
484            }
485            mStorageHint.show();
486        } else if (mStorageHint != null) {
487            mStorageHint.cancel();
488            mStorageHint = null;
489        }
490    }
491
492    private int getStorageStatus(boolean mayHaveSd) {
493        long remaining = mayHaveSd ? getAvailableStorage() : NO_STORAGE_ERROR;
494        if (remaining == NO_STORAGE_ERROR) {
495            return STORAGE_STATUS_NONE;
496        }
497        return remaining < LOW_STORAGE_THRESHOLD
498                ? STORAGE_STATUS_LOW
499                : STORAGE_STATUS_OK;
500    }
501
502    private void readVideoPreferences() {
503        String quality = mPreferences.getString(
504                CameraSettings.KEY_VIDEO_QUALITY,
505                CameraSettings.DEFAULT_VIDEO_QUALITY_VALUE);
506
507        boolean videoQualityHigh = CameraSettings.getVideoQuality(quality);
508
509        // Set video quality.
510        Intent intent = getIntent();
511        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
512            int extraVideoQuality =
513                    intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
514            videoQualityHigh = (extraVideoQuality > 0);
515        }
516
517        // Set video duration limit. The limit is read from the preference,
518        // unless it is specified in the intent.
519        if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
520            int seconds =
521                    intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
522            mMaxVideoDurationInMs = 1000 * seconds;
523        } else {
524            mMaxVideoDurationInMs =
525                    CameraSettings.getVidoeDurationInMillis(quality);
526        }
527        mProfile = CamcorderProfile.get(videoQualityHigh
528                ? CamcorderProfile.Quality.HIGH
529                : CamcorderProfile.Quality.LOW);
530    }
531
532    private void resizeForPreviewAspectRatio() {
533        mPreviewFrameLayout.setAspectRatio(
534                (double) mProfile.mVideoFrameWidth / mProfile.mVideoFrameHeight);
535    }
536
537    @Override
538    public void onResume() {
539        super.onResume();
540        mPausing = false;
541        mGLRootView.onResume();
542
543        mVideoPreview.setVisibility(View.VISIBLE);
544        readVideoPreferences();
545        resizeForPreviewAspectRatio();
546        if (!mPreviewing && !mStartPreviewFail) {
547            try {
548                startPreview();
549            } catch (CameraHardwareException e) {
550                showCameraBusyAndFinish();
551                return;
552            }
553        }
554        keepScreenOnAwhile();
555
556        // install an intent filter to receive SD card related events.
557        IntentFilter intentFilter =
558                new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
559        intentFilter.addAction(Intent.ACTION_MEDIA_EJECT);
560        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
561        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
562        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
563        intentFilter.addDataScheme("file");
564        mReceiver = new MyBroadcastReceiver();
565        registerReceiver(mReceiver, intentFilter);
566        mStorageStatus = getStorageStatus(true);
567
568        mHandler.postDelayed(new Runnable() {
569            public void run() {
570                showStorageHint();
571            }
572        }, 200);
573
574        if (mSurfaceHolder != null) {
575            mHandler.sendEmptyMessage(INIT_RECORDER);
576        }
577    }
578
579    private void setPreviewDisplay(SurfaceHolder holder) {
580        try {
581            mCameraDevice.setPreviewDisplay(holder);
582        } catch (Throwable ex) {
583            closeCamera();
584            throw new RuntimeException("setPreviewDisplay failed", ex);
585        }
586    }
587
588    private void startPreview() throws CameraHardwareException {
589        Log.v(TAG, "startPreview");
590        if (mPreviewing) {
591            // After recording a video, preview is not stopped. So just return.
592            return;
593        }
594
595        if (mCameraDevice == null) {
596            // If the activity is paused and resumed, camera device has been
597            // released and we need to open the camera.
598            mCameraDevice = CameraHolder.instance().open();
599        }
600
601        mCameraDevice.lock();
602        setCameraParameters();
603        setPreviewDisplay(mSurfaceHolder);
604
605        try {
606            mCameraDevice.startPreview();
607            mPreviewing = true;
608        } catch (Throwable ex) {
609            closeCamera();
610            throw new RuntimeException("startPreview failed", ex);
611        }
612
613        // If setPreviewDisplay has been set with a valid surface, unlock now.
614        // If surface is null, unlock later. Otherwise, setPreviewDisplay in
615        // surfaceChanged will fail.
616        if (mSurfaceHolder != null) {
617            mCameraDevice.unlock();
618        }
619    }
620
621    private void closeCamera() {
622        Log.v(TAG, "closeCamera");
623        if (mCameraDevice == null) {
624            Log.d(TAG, "already stopped.");
625            return;
626        }
627        // If we don't lock the camera, release() will fail.
628        mCameraDevice.lock();
629        CameraHolder.instance().release();
630        mCameraDevice = null;
631        mPreviewing = false;
632    }
633
634    @Override
635    protected void onPause() {
636        super.onPause();
637        mPausing = true;
638
639        if (mGLRootView != null) {
640            mGLRootView.onPause();
641            if (mHeadUpDisplay != null) mHeadUpDisplay.collapse();
642        }
643
644        // Hide the preview now. Otherwise, the preview may be rotated during
645        // onPause and it is annoying to users.
646        mVideoPreview.setVisibility(View.INVISIBLE);
647
648        // This is similar to what mShutterButton.performClick() does,
649        // but not quite the same.
650        if (mMediaRecorderRecording) {
651            if (mIsVideoCaptureIntent) {
652                stopVideoRecording();
653                showAlert();
654            } else {
655                stopVideoRecordingAndGetThumbnail();
656            }
657        } else {
658            stopVideoRecording();
659        }
660        closeCamera();
661
662        if (mReceiver != null) {
663            unregisterReceiver(mReceiver);
664            mReceiver = null;
665        }
666        resetScreenOn();
667
668        if (!mIsVideoCaptureIntent) {
669            mThumbController.storeData(ImageManager.getLastVideoThumbPath());
670        }
671
672        if (mStorageHint != null) {
673            mStorageHint.cancel();
674            mStorageHint = null;
675        }
676
677        mHandler.removeMessages(INIT_RECORDER);
678    }
679
680    @Override
681    public void onUserInteraction() {
682        super.onUserInteraction();
683        if (!mMediaRecorderRecording) keepScreenOnAwhile();
684    }
685
686    @Override
687    public void onBackPressed() {
688        if (mPausing) return;
689        if (mMediaRecorderRecording) {
690            onStopVideoRecording(false);
691            return;
692        }
693        super.onBackPressed();
694    }
695
696    @Override
697    public boolean onKeyDown(int keyCode, KeyEvent event) {
698        // Do not handle any key if the activity is paused.
699        if (mPausing) {
700            return true;
701        }
702
703        switch (keyCode) {
704            case KeyEvent.KEYCODE_CAMERA:
705                if (event.getRepeatCount() == 0) {
706                    mShutterButton.performClick();
707                    return true;
708                }
709                break;
710            case KeyEvent.KEYCODE_DPAD_CENTER:
711                if (event.getRepeatCount() == 0) {
712                    mShutterButton.performClick();
713                    return true;
714                }
715                break;
716            case KeyEvent.KEYCODE_MENU:
717                if (mMediaRecorderRecording) {
718                    onStopVideoRecording(true);
719                    return true;
720                }
721                break;
722        }
723
724        return super.onKeyDown(keyCode, event);
725    }
726
727    @Override
728    public boolean onKeyUp(int keyCode, KeyEvent event) {
729        switch (keyCode) {
730            case KeyEvent.KEYCODE_CAMERA:
731                mShutterButton.setPressed(false);
732                return true;
733        }
734        return super.onKeyUp(keyCode, event);
735    }
736
737    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
738        // Make sure we have a surface in the holder before proceeding.
739        if (holder.getSurface() == null) {
740            Log.d(TAG, "holder.getSurface() == null");
741            return;
742        }
743
744        if (mPausing) {
745            // We're pausing, the screen is off and we already stopped
746            // video recording. We don't want to start the camera again
747            // in this case in order to conserve power.
748            // The fact that surfaceChanged is called _after_ an onPause appears
749            // to be legitimate since in that case the lockscreen always returns
750            // to portrait orientation possibly triggering the notification.
751            return;
752        }
753
754        // The mCameraDevice will be null if it is fail to connect to the
755        // camera hardware. In this case we will show a dialog and then
756        // finish the activity, so it's OK to ignore it.
757        if (mCameraDevice == null) return;
758
759        if (mMediaRecorderRecording) {
760            stopVideoRecording();
761        }
762
763        // Set preview display if the surface is being created. Preview was
764        // already started.
765        if (holder.isCreating()) {
766            setPreviewDisplay(holder);
767            mCameraDevice.unlock();
768            mHandler.sendEmptyMessage(INIT_RECORDER);
769        }
770    }
771
772    public void surfaceCreated(SurfaceHolder holder) {
773        mSurfaceHolder = holder;
774    }
775
776    public void surfaceDestroyed(SurfaceHolder holder) {
777        mSurfaceHolder = null;
778    }
779
780    private void gotoGallery() {
781        MenuHelper.gotoCameraVideoGallery(this);
782    }
783
784    @Override
785    public boolean onCreateOptionsMenu(Menu menu) {
786        super.onCreateOptionsMenu(menu);
787
788        if (mIsVideoCaptureIntent) {
789            // No options menu for attach mode.
790            return false;
791        } else {
792            addBaseMenuItems(menu);
793        }
794        return true;
795    }
796
797    private boolean isVideoCaptureIntent() {
798        String action = getIntent().getAction();
799        return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
800    }
801
802    private void doReturnToCaller(boolean valid) {
803        Intent resultIntent = new Intent();
804        int resultCode;
805        if (valid) {
806            resultCode = RESULT_OK;
807            resultIntent.setData(mCurrentVideoUri);
808        } else {
809            resultCode = RESULT_CANCELED;
810        }
811        setResult(resultCode, resultIntent);
812        finish();
813    }
814
815    /**
816     * Returns
817     *
818     * @return number of bytes available, or an ERROR code.
819     */
820    private static long getAvailableStorage() {
821        try {
822            if (!ImageManager.hasStorage()) {
823                return NO_STORAGE_ERROR;
824            } else {
825                String storageDirectory =
826                        Environment.getExternalStorageDirectory().toString();
827                StatFs stat = new StatFs(storageDirectory);
828                return (long) stat.getAvailableBlocks()
829                        * (long) stat.getBlockSize();
830            }
831        } catch (RuntimeException ex) {
832            // if we can't stat the filesystem then we don't know how many
833            // free bytes exist. It might be zero but just leave it
834            // blank since we really don't know.
835            return CANNOT_STAT_ERROR;
836        }
837    }
838
839    private void cleanupEmptyFile() {
840        if (mCameraVideoFilename != null) {
841            File f = new File(mCameraVideoFilename);
842            if (f.length() == 0 && f.delete()) {
843                Log.v(TAG, "Empty video file deleted: " + mCameraVideoFilename);
844                mCameraVideoFilename = null;
845            }
846        }
847    }
848
849    private android.hardware.Camera mCameraDevice;
850
851    // Prepares media recorder.
852    private void initializeRecorder() {
853        Log.v(TAG, "initializeRecorder");
854        if (mMediaRecorder != null) return;
855
856        // We will call initializeRecorder() again when the alert is hidden.
857        // If the mCameraDevice is null, then this activity is going to finish
858        if (isAlertVisible() || mCameraDevice == null) return;
859
860        Intent intent = getIntent();
861        Bundle myExtras = intent.getExtras();
862
863        long requestedSizeLimit = 0;
864        if (mIsVideoCaptureIntent && myExtras != null) {
865            Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
866            if (saveUri != null) {
867                try {
868                    mCameraVideoFileDescriptor =
869                            mContentResolver.openFileDescriptor(saveUri, "rw")
870                            .getFileDescriptor();
871                    mCurrentVideoUri = saveUri;
872                } catch (java.io.FileNotFoundException ex) {
873                    // invalid uri
874                    Log.e(TAG, ex.toString());
875                }
876            }
877            requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
878        }
879        mMediaRecorder = new MediaRecorder();
880
881        mMediaRecorder.setCamera(mCameraDevice);
882        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
883        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
884        mMediaRecorder.setOutputFormat(mProfile.mFileFormat);
885        mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
886
887        // Set output file.
888        if (mStorageStatus != STORAGE_STATUS_OK) {
889            mMediaRecorder.setOutputFile("/dev/null");
890        } else {
891            // Try Uri in the intent first. If it doesn't exist, use our own
892            // instead.
893            if (mCameraVideoFileDescriptor != null) {
894                mMediaRecorder.setOutputFile(mCameraVideoFileDescriptor);
895            } else {
896                createVideoPath();
897                mMediaRecorder.setOutputFile(mCameraVideoFilename);
898            }
899        }
900
901        // Use the same frame rate for both, since internally
902        // if the frame rate is too large, it can cause camera to become
903        // unstable. We need to fix the MediaRecorder to disable the support
904        // of setting frame rate for now.
905        mMediaRecorder.setVideoFrameRate(mProfile.mVideoFrameRate);
906        mMediaRecorder.setVideoSize(
907                mProfile.mVideoFrameWidth, mProfile.mVideoFrameHeight);
908        mMediaRecorder.setVideoEncodingBitRate(mProfile.mVideoBitRate);
909        mMediaRecorder.setAudioEncodingBitRate(mProfile.mAudioBitRate);
910        mMediaRecorder.setAudioChannels(mProfile.mAudioChannels);
911        mMediaRecorder.setAudioSamplingRate(mProfile.mAudioSampleRate);
912        mMediaRecorder.setVideoEncoder(mProfile.mVideoCodec);
913        mMediaRecorder.setAudioEncoder(mProfile.mAudioCodec);
914        mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
915
916        // Set maximum file size.
917        // remaining >= LOW_STORAGE_THRESHOLD at this point, reserve a quarter
918        // of that to make it more likely that recording can complete
919        // successfully.
920        long maxFileSize = getAvailableStorage() - LOW_STORAGE_THRESHOLD / 4;
921        if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
922            maxFileSize = requestedSizeLimit;
923        }
924
925        try {
926            mMediaRecorder.setMaxFileSize(maxFileSize);
927        } catch (RuntimeException exception) {
928            // We are going to ignore failure of setMaxFileSize here, as
929            // a) The composer selected may simply not support it, or
930            // b) The underlying media framework may not handle 64-bit range
931            // on the size restriction.
932        }
933
934        try {
935            mMediaRecorder.prepare();
936        } catch (IOException e) {
937            Log.e(TAG, "prepare failed for " + mCameraVideoFilename);
938            releaseMediaRecorder();
939            throw new RuntimeException(e);
940        }
941        mMediaRecorderRecording = false;
942
943        // Update the last video thumbnail.
944        if (!mIsVideoCaptureIntent) {
945            if (!mThumbController.isUriValid()) {
946                updateLastVideo();
947            }
948            mThumbController.updateDisplayIfNeeded();
949        }
950    }
951
952    private void releaseMediaRecorder() {
953        Log.v(TAG, "Releasing media recorder.");
954        if (mMediaRecorder != null) {
955            cleanupEmptyFile();
956            mMediaRecorder.reset();
957            mMediaRecorder.release();
958            mMediaRecorder = null;
959        }
960    }
961
962    private void createVideoPath() {
963        long dateTaken = System.currentTimeMillis();
964        String title = createName(dateTaken);
965        String filename = title + ".3gp"; // Used when emailing.
966        String cameraDirPath = ImageManager.CAMERA_IMAGE_BUCKET_NAME;
967        String filePath = cameraDirPath + "/" + filename;
968        File cameraDir = new File(cameraDirPath);
969        cameraDir.mkdirs();
970        ContentValues values = new ContentValues(7);
971        values.put(Video.Media.TITLE, title);
972        values.put(Video.Media.DISPLAY_NAME, filename);
973        values.put(Video.Media.DATE_TAKEN, dateTaken);
974        values.put(Video.Media.MIME_TYPE, "video/3gpp");
975        values.put(Video.Media.DATA, filePath);
976        mCameraVideoFilename = filePath;
977        Log.v(TAG, "Current camera video filename: " + mCameraVideoFilename);
978        mCurrentVideoValues = values;
979    }
980
981    private void registerVideo() {
982        if (mCameraVideoFileDescriptor == null) {
983            Uri videoTable = Uri.parse("content://media/external/video/media");
984            mCurrentVideoValues.put(Video.Media.SIZE,
985                    new File(mCurrentVideoFilename).length());
986            try {
987                mCurrentVideoUri = mContentResolver.insert(videoTable,
988                        mCurrentVideoValues);
989            } catch (Exception e) {
990                // We failed to insert into the database. This can happen if
991                // the SD card is unmounted.
992                mCurrentVideoUri = null;
993                mCurrentVideoFilename = null;
994            } finally {
995                Log.v(TAG, "Current video URI: " + mCurrentVideoUri);
996            }
997        }
998        mCurrentVideoValues = null;
999    }
1000
1001    private void deleteCurrentVideo() {
1002        if (mCurrentVideoFilename != null) {
1003            deleteVideoFile(mCurrentVideoFilename);
1004            mCurrentVideoFilename = null;
1005        }
1006        if (mCurrentVideoUri != null) {
1007            mContentResolver.delete(mCurrentVideoUri, null, null);
1008            mCurrentVideoUri = null;
1009        }
1010        updateAndShowStorageHint(true);
1011    }
1012
1013    private void deleteVideoFile(String fileName) {
1014        Log.v(TAG, "Deleting video " + fileName);
1015        File f = new File(fileName);
1016        if (!f.delete()) {
1017            Log.v(TAG, "Could not delete " + fileName);
1018        }
1019    }
1020
1021    private void addBaseMenuItems(Menu menu) {
1022        MenuHelper.addSwitchModeMenuItem(menu, false, new Runnable() {
1023            public void run() {
1024                switchToCameraMode();
1025            }
1026        });
1027        MenuItem gallery = menu.add(Menu.NONE, Menu.NONE,
1028                MenuHelper.POSITION_GOTO_GALLERY,
1029                R.string.camera_gallery_photos_text)
1030                .setOnMenuItemClickListener(
1031                    new OnMenuItemClickListener() {
1032                        public boolean onMenuItemClick(MenuItem item) {
1033                            gotoGallery();
1034                            return true;
1035                        }
1036                    });
1037        gallery.setIcon(android.R.drawable.ic_menu_gallery);
1038        mGalleryItems.add(gallery);
1039    }
1040
1041    private PreferenceGroup filterPreferenceScreenByIntent(
1042            PreferenceGroup screen) {
1043        Intent intent = getIntent();
1044        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
1045            CameraSettings.removePreferenceFromScreen(screen,
1046                    CameraSettings.KEY_VIDEO_QUALITY);
1047        }
1048
1049        if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
1050            CameraSettings.removePreferenceFromScreen(screen,
1051                    CameraSettings.KEY_VIDEO_QUALITY);
1052        }
1053        return screen;
1054    }
1055
1056    // from MediaRecorder.OnErrorListener
1057    public void onError(MediaRecorder mr, int what, int extra) {
1058        if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1059            // We may have run out of space on the sdcard.
1060            stopVideoRecording();
1061            updateAndShowStorageHint(true);
1062        }
1063    }
1064
1065    // from MediaRecorder.OnInfoListener
1066    public void onInfo(MediaRecorder mr, int what, int extra) {
1067        if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
1068            if (mMediaRecorderRecording) onStopVideoRecording(true);
1069        } else if (what
1070                == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
1071            if (mMediaRecorderRecording) onStopVideoRecording(true);
1072
1073            // Show the toast.
1074            Toast.makeText(VideoCamera.this, R.string.video_reach_size_limit,
1075                           Toast.LENGTH_LONG).show();
1076        }
1077    }
1078
1079    /*
1080     * Make sure we're not recording music playing in the background, ask the
1081     * MediaPlaybackService to pause playback.
1082     */
1083    private void pauseAudioPlayback() {
1084        // Shamelessly copied from MediaPlaybackService.java, which
1085        // should be public, but isn't.
1086        Intent i = new Intent("com.android.music.musicservicecommand");
1087        i.putExtra("command", "pause");
1088
1089        sendBroadcast(i);
1090    }
1091
1092    private void startVideoRecording() {
1093        Log.v(TAG, "startVideoRecording");
1094        if (!mMediaRecorderRecording) {
1095            mHeadUpDisplay.setEnabled(false);
1096
1097            if (mStorageStatus != STORAGE_STATUS_OK) {
1098                Log.v(TAG, "Storage issue, ignore the start request");
1099                return;
1100            }
1101
1102            // Check mMediaRecorder to see whether it is initialized or not.
1103            if (mMediaRecorder == null) {
1104                Log.e(TAG, "MediaRecorder is not initialized.");
1105                return;
1106            }
1107
1108            pauseAudioPlayback();
1109
1110            try {
1111                mMediaRecorder.setOnErrorListener(this);
1112                mMediaRecorder.setOnInfoListener(this);
1113                mMediaRecorder.start(); // Recording is now started
1114            } catch (RuntimeException e) {
1115                Log.e(TAG, "Could not start media recorder. ", e);
1116                return;
1117            }
1118            mMediaRecorderRecording = true;
1119            mRecordingStartTime = SystemClock.uptimeMillis();
1120            updateRecordingIndicator(false);
1121            mRecordingTimeView.setText("");
1122            mRecordingTimeView.setVisibility(View.VISIBLE);
1123            updateRecordingTime();
1124            keepScreenOn();
1125        }
1126    }
1127
1128    private void updateRecordingIndicator(boolean showRecording) {
1129        int drawableId =
1130                showRecording ? R.drawable.btn_ic_video_record
1131                        : R.drawable.btn_ic_video_record_stop;
1132        Drawable drawable = getResources().getDrawable(drawableId);
1133        mShutterButton.setImageDrawable(drawable);
1134    }
1135
1136    private void stopVideoRecordingAndGetThumbnail() {
1137        stopVideoRecording();
1138        acquireVideoThumb();
1139    }
1140
1141    private void stopVideoRecordingAndReturn(boolean valid) {
1142        stopVideoRecording();
1143        doReturnToCaller(valid);
1144    }
1145
1146    private void stopVideoRecordingAndShowAlert() {
1147        stopVideoRecording();
1148        showAlert();
1149    }
1150
1151    private void showAlert() {
1152        fadeOut(findViewById(R.id.shutter_button));
1153        if (mCurrentVideoFilename != null) {
1154            mVideoFrame.setImageBitmap(
1155                    ThumbnailUtils.createVideoThumbnail(mCurrentVideoFilename));
1156            mVideoFrame.setVisibility(View.VISIBLE);
1157        }
1158        int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play};
1159        for (int id : pickIds) {
1160            View button = findViewById(id);
1161            fadeIn(((View) button.getParent()));
1162        }
1163    }
1164
1165    private void hideAlert() {
1166        mVideoFrame.setVisibility(View.INVISIBLE);
1167        fadeIn(findViewById(R.id.shutter_button));
1168        int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play};
1169        for (int id : pickIds) {
1170            View button = findViewById(id);
1171            fadeOut(((View) button.getParent()));
1172        }
1173    }
1174
1175    private static void fadeIn(View view) {
1176        view.setVisibility(View.VISIBLE);
1177        Animation animation = new AlphaAnimation(0F, 1F);
1178        animation.setDuration(500);
1179        view.startAnimation(animation);
1180    }
1181
1182    private static void fadeOut(View view) {
1183        view.setVisibility(View.INVISIBLE);
1184        Animation animation = new AlphaAnimation(1F, 0F);
1185        animation.setDuration(500);
1186        view.startAnimation(animation);
1187    }
1188
1189    private boolean isAlertVisible() {
1190        return this.mVideoFrame.getVisibility() == View.VISIBLE;
1191    }
1192
1193    private void viewLastVideo() {
1194        Intent intent = null;
1195        if (mThumbController.isUriValid()) {
1196            intent = new Intent(Intent.ACTION_VIEW, mThumbController.getUri());
1197            intent.setClassName(GALLERY_PACKAGE_NAME, GALLERY_CLASS_NAME);
1198            intent.putExtra(REVIEW_EXTRA_IN_GALLERY, true);
1199            try {
1200                startActivity(intent);
1201            } catch (ActivityNotFoundException ex) {
1202                try {
1203                    intent = new Intent(Intent.ACTION_VIEW, mThumbController.getUri());
1204                } catch (ActivityNotFoundException e) {
1205                    Log.e(TAG, "review video fail", e);
1206                }
1207            }
1208        } else {
1209            Log.e(TAG, "Can't view last video.");
1210        }
1211    }
1212
1213    private void stopVideoRecording() {
1214        Log.v(TAG, "stopVideoRecording");
1215        boolean needToRegisterRecording = false;
1216        mHeadUpDisplay.setEnabled(true);
1217        if (mMediaRecorderRecording || mMediaRecorder != null) {
1218            if (mMediaRecorderRecording && mMediaRecorder != null) {
1219                try {
1220                    mMediaRecorder.setOnErrorListener(null);
1221                    mMediaRecorder.setOnInfoListener(null);
1222                    mMediaRecorder.stop();
1223                } catch (RuntimeException e) {
1224                    Log.e(TAG, "stop fail: " + e.getMessage());
1225                }
1226                mHeadUpDisplay.setEnabled(true);
1227                mCurrentVideoFilename = mCameraVideoFilename;
1228                Log.v(TAG, "Setting current video filename: "
1229                        + mCurrentVideoFilename);
1230                needToRegisterRecording = true;
1231                mMediaRecorderRecording = false;
1232            }
1233            releaseMediaRecorder();
1234            updateRecordingIndicator(true);
1235            mRecordingTimeView.setVisibility(View.GONE);
1236            keepScreenOnAwhile();
1237        }
1238        if (needToRegisterRecording && mStorageStatus == STORAGE_STATUS_OK) {
1239            registerVideo();
1240        }
1241
1242        mCameraVideoFilename = null;
1243        mCameraVideoFileDescriptor = null;
1244    }
1245
1246    private void resetScreenOn() {
1247        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1248        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1249    }
1250
1251    private void keepScreenOnAwhile() {
1252        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1253        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1254        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1255    }
1256
1257    private void keepScreenOn() {
1258        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1259        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1260    }
1261
1262    private void hideAlertAndInitializeRecorder() {
1263        hideAlert();
1264        mHandler.sendEmptyMessage(INIT_RECORDER);
1265    }
1266
1267    private void acquireVideoThumb() {
1268        Bitmap videoFrame = ThumbnailUtils.createVideoThumbnail(
1269                mCurrentVideoFilename);
1270        mThumbController.setData(mCurrentVideoUri, videoFrame);
1271    }
1272
1273    private static ImageManager.DataLocation dataLocation() {
1274        return ImageManager.DataLocation.EXTERNAL;
1275    }
1276
1277    private void updateLastVideo() {
1278        IImageList list = ImageManager.makeImageList(
1279                        mContentResolver,
1280                        dataLocation(),
1281                        ImageManager.INCLUDE_VIDEOS,
1282                        ImageManager.SORT_ASCENDING,
1283                        ImageManager.CAMERA_IMAGE_BUCKET_ID);
1284        int count = list.getCount();
1285        if (count > 0) {
1286            IImage image = list.getImageAt(count - 1);
1287            Uri uri = image.fullSizeImageUri();
1288            mThumbController.setData(uri, image.miniThumbBitmap());
1289        } else {
1290            mThumbController.setData(null, null);
1291        }
1292        list.close();
1293    }
1294
1295    private void updateRecordingTime() {
1296        if (!mMediaRecorderRecording) {
1297            return;
1298        }
1299        long now = SystemClock.uptimeMillis();
1300        long delta = now - mRecordingStartTime;
1301
1302        // Starting a minute before reaching the max duration
1303        // limit, we'll countdown the remaining time instead.
1304        boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1305                && delta >= mMaxVideoDurationInMs - 60000);
1306
1307        long next_update_delay = 1000 - (delta % 1000);
1308        long seconds;
1309        if (countdownRemainingTime) {
1310            delta = Math.max(0, mMaxVideoDurationInMs - delta);
1311            seconds = (delta + 999) / 1000;
1312        } else {
1313            seconds = delta / 1000; // round to nearest
1314        }
1315
1316        long minutes = seconds / 60;
1317        long hours = minutes / 60;
1318        long remainderMinutes = minutes - (hours * 60);
1319        long remainderSeconds = seconds - (minutes * 60);
1320
1321        String secondsString = Long.toString(remainderSeconds);
1322        if (secondsString.length() < 2) {
1323            secondsString = "0" + secondsString;
1324        }
1325        String minutesString = Long.toString(remainderMinutes);
1326        if (minutesString.length() < 2) {
1327            minutesString = "0" + minutesString;
1328        }
1329        String text = minutesString + ":" + secondsString;
1330        if (hours > 0) {
1331            String hoursString = Long.toString(hours);
1332            if (hoursString.length() < 2) {
1333                hoursString = "0" + hoursString;
1334            }
1335            text = hoursString + ":" + text;
1336        }
1337        mRecordingTimeView.setText(text);
1338
1339        if (mRecordingTimeCountsDown != countdownRemainingTime) {
1340            // Avoid setting the color on every update, do it only
1341            // when it needs changing.
1342            mRecordingTimeCountsDown = countdownRemainingTime;
1343
1344            int color = getResources().getColor(countdownRemainingTime
1345                    ? R.color.recording_time_remaining_text
1346                    : R.color.recording_time_elapsed_text);
1347
1348            mRecordingTimeView.setTextColor(color);
1349        }
1350
1351        mHandler.sendEmptyMessageDelayed(
1352                UPDATE_RECORD_TIME, next_update_delay);
1353    }
1354
1355    private static boolean isSupported(String value, List<String> supported) {
1356        return supported == null ? false : supported.indexOf(value) >= 0;
1357    }
1358
1359    private void setCameraParameters() {
1360        mParameters = mCameraDevice.getParameters();
1361
1362        mParameters.setPreviewSize(mProfile.mVideoFrameWidth, mProfile.mVideoFrameHeight);
1363        mParameters.setPreviewFrameRate(mProfile.mVideoFrameRate);
1364
1365        // Set flash mode.
1366        String flashMode = mPreferences.getString(
1367                CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
1368                getString(R.string.pref_camera_video_flashmode_default));
1369        List<String> supportedFlash = mParameters.getSupportedFlashModes();
1370        if (isSupported(flashMode, supportedFlash)) {
1371            mParameters.setFlashMode(flashMode);
1372        } else {
1373            flashMode = mParameters.getFlashMode();
1374            if (flashMode == null) {
1375                flashMode = getString(
1376                        R.string.pref_camera_flashmode_no_flash);
1377            }
1378        }
1379
1380        // Set white balance parameter.
1381        String whiteBalance = mPreferences.getString(
1382                CameraSettings.KEY_WHITE_BALANCE,
1383                getString(R.string.pref_camera_whitebalance_default));
1384        if (isSupported(whiteBalance,
1385                mParameters.getSupportedWhiteBalance())) {
1386            mParameters.setWhiteBalance(whiteBalance);
1387        } else {
1388            whiteBalance = mParameters.getWhiteBalance();
1389            if (whiteBalance == null) {
1390                whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1391            }
1392        }
1393
1394        // Set color effect parameter.
1395        String colorEffect = mPreferences.getString(
1396                CameraSettings.KEY_COLOR_EFFECT,
1397                getString(R.string.pref_camera_coloreffect_default));
1398        if (isSupported(colorEffect, mParameters.getSupportedColorEffects())) {
1399            mParameters.setColorEffect(colorEffect);
1400        }
1401
1402        mCameraDevice.setParameters(mParameters);
1403    }
1404
1405    private boolean switchToCameraMode() {
1406        if (mMediaRecorderRecording) return false;
1407        MenuHelper.gotoCameraMode(this);
1408        ((ViewGroup) mGLRootView.getParent()).removeView(mGLRootView);
1409        mGLRootView = null;
1410        finish();
1411        return true;
1412    }
1413
1414    public boolean onSwitchChanged(Switcher source, boolean onOff) {
1415        if (onOff == SWITCH_CAMERA) {
1416            return switchToCameraMode();
1417        } else {
1418            return true;
1419        }
1420    }
1421
1422    private void resetCameraParameters() {
1423        // We need to restart the preview if preview size is changed.
1424        Size size = mParameters.getPreviewSize();
1425        if (size.width != mProfile.mVideoFrameWidth
1426                || size.height != mProfile.mVideoFrameHeight) {
1427            // It is assumed media recorder is released before
1428            // onSharedPreferenceChanged, so we can close the camera here.
1429            closeCamera();
1430            try {
1431                resizeForPreviewAspectRatio();
1432                startPreview(); // Parameters will be set in startPreview().
1433            } catch (CameraHardwareException e) {
1434                showCameraBusyAndFinish();
1435            }
1436        } else {
1437            try {
1438                // We need to lock the camera before writing parameters.
1439                mCameraDevice.lock();
1440            } catch (RuntimeException e) {
1441                // When preferences are added for the first time, this method
1442                // will be called. But OnScreenSetting is not displayed yet and
1443                // media recorder still owns the camera. Lock will fail and we
1444                // just ignore it.
1445                return;
1446            }
1447            setCameraParameters();
1448            mCameraDevice.unlock();
1449        }
1450    }
1451
1452    public void onSizeChanged() {
1453        // TODO: update the content on GLRootView
1454    }
1455
1456    private class MyHeadUpDisplayListener implements HeadUpDisplay.Listener {
1457        public void onSharedPreferencesChanged() {
1458            mHandler.post(new Runnable() {
1459                public void run() {
1460                    VideoCamera.this.onSharedPreferencesChanged();
1461                }
1462            });
1463        }
1464
1465        public void onRestorePreferencesClicked() {
1466            mHandler.post(new Runnable() {
1467                public void run() {
1468                    VideoCamera.this.onRestorePreferencesClicked();
1469                }
1470            });
1471        }
1472
1473        public void onPopupWindowVisibilityChanged(final int visibility) {
1474            mHandler.post(new Runnable() {
1475                public void run() {
1476                    VideoCamera.this.onPopupWindowVisibilityChanged(visibility);
1477                }
1478            });
1479        }
1480    }
1481
1482    private void onPopupWindowVisibilityChanged(int visibility) {
1483        if (visibility == GLView.VISIBLE) {
1484            releaseMediaRecorder();
1485        } else {
1486            if (!mPausing && mSurfaceHolder != null) initializeRecorder();
1487        }
1488    }
1489
1490    private void onRestorePreferencesClicked() {
1491        Runnable runnable = new Runnable() {
1492            public void run() {
1493                mHeadUpDisplay.restorePreferences(mParameters);
1494            }
1495        };
1496        MenuHelper.confirmAction(this,
1497                getString(R.string.confirm_restore_title),
1498                getString(R.string.confirm_restore_message),
1499                runnable);
1500    }
1501
1502    private void onSharedPreferencesChanged() {
1503        // ignore the events after "onPause()" or preview has not started yet
1504        if (mPausing) return;
1505        synchronized (mPreferences) {
1506            readVideoPreferences();
1507            // If mCameraDevice is not ready then we can set the parameter in
1508            // startPreview().
1509            if (mCameraDevice == null) return;
1510            resetCameraParameters();
1511        }
1512    }
1513}
1514