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