VideoCamera.java revision 9ae7d027bb8f55b85a158cddeb7ed84c5a0a7983
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    /**
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 (mMediaRecorderRecording) {
437                    onStopVideoRecording(true);
438                } else if (mMediaRecorder != null) {
439                    // If the click comes before recorder initialization, it is
440                    // ignored. If users click the button during initialization,
441                    // the event is put in the queue and record will be started
442                    // eventually.
443                    startVideoRecording();
444                }
445                mShutterButton.setEnabled(false);
446                mHandler.sendEmptyMessageDelayed(
447                        ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
448                break;
449        }
450    }
451
452    private void discardCurrentVideoAndInitRecorder() {
453        deleteCurrentVideo();
454        hideAlertAndInitializeRecorder();
455    }
456
457    private OnScreenHint mStorageHint;
458
459    private void updateAndShowStorageHint(boolean mayHaveSd) {
460        mStorageStatus = getStorageStatus(mayHaveSd);
461        showStorageHint();
462    }
463
464    private void showStorageHint() {
465        String errorMessage = null;
466        switch (mStorageStatus) {
467            case STORAGE_STATUS_NONE:
468                errorMessage = getString(R.string.no_storage);
469                break;
470            case STORAGE_STATUS_LOW:
471                errorMessage = getString(R.string.spaceIsLow_content);
472        }
473        if (errorMessage != null) {
474            if (mStorageHint == null) {
475                mStorageHint = OnScreenHint.makeText(this, errorMessage);
476            } else {
477                mStorageHint.setText(errorMessage);
478            }
479            mStorageHint.show();
480        } else if (mStorageHint != null) {
481            mStorageHint.cancel();
482            mStorageHint = null;
483        }
484    }
485
486    private int getStorageStatus(boolean mayHaveSd) {
487        long remaining = mayHaveSd ? getAvailableStorage() : NO_STORAGE_ERROR;
488        if (remaining == NO_STORAGE_ERROR) {
489            return STORAGE_STATUS_NONE;
490        }
491        return remaining < LOW_STORAGE_THRESHOLD
492                ? STORAGE_STATUS_LOW
493                : STORAGE_STATUS_OK;
494    }
495
496    private void readVideoPreferences() {
497        String quality = mPreferences.getString(
498                CameraSettings.KEY_VIDEO_QUALITY,
499                CameraSettings.DEFAULT_VIDEO_QUALITY_VALUE);
500
501        boolean videoQualityHigh = CameraSettings.getVideoQuality(quality);
502
503        // Set video quality.
504        Intent intent = getIntent();
505        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
506            int extraVideoQuality =
507                    intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
508            videoQualityHigh = (extraVideoQuality > 0);
509        }
510
511        // Set video duration limit. The limit is read from the preference,
512        // unless it is specified in the intent.
513        if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
514            int seconds =
515                    intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
516            mMaxVideoDurationInMs = 1000 * seconds;
517        } else {
518            mMaxVideoDurationInMs =
519                    CameraSettings.getVidoeDurationInMillis(quality);
520        }
521        mProfile = CamcorderProfile.get(videoQualityHigh
522                ? CamcorderProfile.Quality.HIGH
523                : CamcorderProfile.Quality.LOW);
524    }
525
526    private void resizeForPreviewAspectRatio() {
527        mPreviewFrameLayout.setAspectRatio(
528                (double) mProfile.mVideoFrameWidth / mProfile.mVideoFrameHeight);
529    }
530
531    @Override
532    public void onResume() {
533        super.onResume();
534        mPausing = false;
535        mGLRootView.onResume();
536
537        mVideoPreview.setVisibility(View.VISIBLE);
538        readVideoPreferences();
539        resizeForPreviewAspectRatio();
540        if (!mPreviewing && !mStartPreviewFail) {
541            try {
542                startPreview();
543            } catch (CameraHardwareException e) {
544                showCameraBusyAndFinish();
545                return;
546            }
547        }
548        keepScreenOnAwhile();
549
550        // install an intent filter to receive SD card related events.
551        IntentFilter intentFilter =
552                new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
553        intentFilter.addAction(Intent.ACTION_MEDIA_EJECT);
554        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
555        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
556        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
557        intentFilter.addDataScheme("file");
558        mReceiver = new MyBroadcastReceiver();
559        registerReceiver(mReceiver, intentFilter);
560        mStorageStatus = getStorageStatus(true);
561
562        mHandler.postDelayed(new Runnable() {
563            public void run() {
564                showStorageHint();
565            }
566        }, 200);
567
568        if (mSurfaceHolder != null) {
569            mHandler.sendEmptyMessage(INIT_RECORDER);
570        }
571    }
572
573    private void setPreviewDisplay(SurfaceHolder holder) {
574        try {
575            mCameraDevice.setPreviewDisplay(holder);
576        } catch (Throwable ex) {
577            closeCamera();
578            throw new RuntimeException("setPreviewDisplay failed", ex);
579        }
580    }
581
582    private void startPreview() throws CameraHardwareException {
583        Log.v(TAG, "startPreview");
584        if (mPreviewing) {
585            // After recording a video, preview is not stopped. So just return.
586            return;
587        }
588
589        if (mCameraDevice == null) {
590            // If the activity is paused and resumed, camera device has been
591            // released and we need to open the camera.
592            mCameraDevice = CameraHolder.instance().open();
593        }
594
595        mCameraDevice.lock();
596        setCameraParameters();
597        setPreviewDisplay(mSurfaceHolder);
598
599        try {
600            mCameraDevice.startPreview();
601            mPreviewing = true;
602        } catch (Throwable ex) {
603            closeCamera();
604            throw new RuntimeException("startPreview failed", ex);
605        }
606
607        // If setPreviewDisplay has been set with a valid surface, unlock now.
608        // If surface is null, unlock later. Otherwise, setPreviewDisplay in
609        // surfaceChanged will fail.
610        if (mSurfaceHolder != null) {
611            mCameraDevice.unlock();
612        }
613    }
614
615    private void closeCamera() {
616        Log.v(TAG, "closeCamera");
617        if (mCameraDevice == null) {
618            Log.d(TAG, "already stopped.");
619            return;
620        }
621        // If we don't lock the camera, release() will fail.
622        mCameraDevice.lock();
623        CameraHolder.instance().release();
624        mCameraDevice = null;
625        mPreviewing = false;
626    }
627
628    @Override
629    protected void onPause() {
630        super.onPause();
631        mGLRootView.onPause();
632        mPausing = true;
633
634        // Hide the preview now. Otherwise, the preview may be rotated during
635        // onPause and it is annoying to users.
636        mVideoPreview.setVisibility(View.INVISIBLE);
637
638        // This is similar to what mShutterButton.performClick() does,
639        // but not quite the same.
640        if (mMediaRecorderRecording) {
641            if (mIsVideoCaptureIntent) {
642                stopVideoRecording();
643                showAlert();
644            } else {
645                stopVideoRecordingAndGetThumbnail();
646            }
647        } else {
648            stopVideoRecording();
649        }
650        closeCamera();
651
652        if (mReceiver != null) {
653            unregisterReceiver(mReceiver);
654            mReceiver = null;
655        }
656        resetScreenOn();
657
658        if (!mIsVideoCaptureIntent) {
659            mThumbController.storeData(ImageManager.getLastVideoThumbPath());
660        }
661
662        if (mStorageHint != null) {
663            mStorageHint.cancel();
664            mStorageHint = null;
665        }
666
667        mHandler.removeMessages(INIT_RECORDER);
668    }
669
670    @Override
671    public void onUserInteraction() {
672        super.onUserInteraction();
673        if (!mMediaRecorderRecording) keepScreenOnAwhile();
674    }
675
676    @Override
677    public void onBackPressed() {
678        if (mPausing) return;
679        if (mMediaRecorderRecording) {
680            onStopVideoRecording(false);
681            return;
682        }
683        super.onBackPressed();
684    }
685
686    @Override
687    public boolean onKeyDown(int keyCode, KeyEvent event) {
688        // Do not handle any key if the activity is paused.
689        if (mPausing) {
690            return true;
691        }
692
693        switch (keyCode) {
694            case KeyEvent.KEYCODE_CAMERA:
695                if (event.getRepeatCount() == 0) {
696                    mShutterButton.performClick();
697                    return true;
698                }
699                break;
700            case KeyEvent.KEYCODE_DPAD_CENTER:
701                if (event.getRepeatCount() == 0) {
702                    mShutterButton.performClick();
703                    return true;
704                }
705                break;
706            case KeyEvent.KEYCODE_MENU:
707                if (mMediaRecorderRecording) {
708                    onStopVideoRecording(true);
709                    return true;
710                }
711                break;
712        }
713
714        return super.onKeyDown(keyCode, event);
715    }
716
717    @Override
718    public boolean onKeyUp(int keyCode, KeyEvent event) {
719        switch (keyCode) {
720            case KeyEvent.KEYCODE_CAMERA:
721                mShutterButton.setPressed(false);
722                return true;
723        }
724        return super.onKeyUp(keyCode, event);
725    }
726
727    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
728        // Make sure we have a surface in the holder before proceeding.
729        if (holder.getSurface() == null) {
730            Log.d(TAG, "holder.getSurface() == null");
731            return;
732        }
733
734        if (mPausing) {
735            // We're pausing, the screen is off and we already stopped
736            // video recording. We don't want to start the camera again
737            // in this case in order to conserve power.
738            // The fact that surfaceChanged is called _after_ an onPause appears
739            // to be legitimate since in that case the lockscreen always returns
740            // to portrait orientation possibly triggering the notification.
741            return;
742        }
743
744        // The mCameraDevice will be null if it is fail to connect to the
745        // camera hardware. In this case we will show a dialog and then
746        // finish the activity, so it's OK to ignore it.
747        if (mCameraDevice == null) return;
748
749        if (mMediaRecorderRecording) {
750            stopVideoRecording();
751        }
752
753        // Set preview display if the surface is being created. Preview was
754        // already started.
755        if (holder.isCreating()) {
756            setPreviewDisplay(holder);
757            mCameraDevice.unlock();
758            mHandler.sendEmptyMessage(INIT_RECORDER);
759        }
760    }
761
762    public void surfaceCreated(SurfaceHolder holder) {
763        mSurfaceHolder = holder;
764    }
765
766    public void surfaceDestroyed(SurfaceHolder holder) {
767        mSurfaceHolder = null;
768    }
769
770    private void gotoGallery() {
771        MenuHelper.gotoCameraVideoGallery(this);
772    }
773
774    @Override
775    public boolean onCreateOptionsMenu(Menu menu) {
776        super.onCreateOptionsMenu(menu);
777
778        if (mIsVideoCaptureIntent) {
779            // No options menu for attach mode.
780            return false;
781        } else {
782            addBaseMenuItems(menu);
783        }
784        return true;
785    }
786
787    private boolean isVideoCaptureIntent() {
788        String action = getIntent().getAction();
789        return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
790    }
791
792    private void doReturnToCaller(boolean valid) {
793        Intent resultIntent = new Intent();
794        int resultCode;
795        if (valid) {
796            resultCode = RESULT_OK;
797            resultIntent.setData(mCurrentVideoUri);
798        } else {
799            resultCode = RESULT_CANCELED;
800        }
801        setResult(resultCode, resultIntent);
802        finish();
803    }
804
805    /**
806     * Returns
807     *
808     * @return number of bytes available, or an ERROR code.
809     */
810    private static long getAvailableStorage() {
811        try {
812            if (!ImageManager.hasStorage()) {
813                return NO_STORAGE_ERROR;
814            } else {
815                String storageDirectory =
816                        Environment.getExternalStorageDirectory().toString();
817                StatFs stat = new StatFs(storageDirectory);
818                return (long) stat.getAvailableBlocks()
819                        * (long) stat.getBlockSize();
820            }
821        } catch (RuntimeException ex) {
822            // if we can't stat the filesystem then we don't know how many
823            // free bytes exist. It might be zero but just leave it
824            // blank since we really don't know.
825            return CANNOT_STAT_ERROR;
826        }
827    }
828
829    private void cleanupEmptyFile() {
830        if (mCameraVideoFilename != null) {
831            File f = new File(mCameraVideoFilename);
832            if (f.length() == 0 && f.delete()) {
833                Log.v(TAG, "Empty video file deleted: " + mCameraVideoFilename);
834                mCameraVideoFilename = null;
835            }
836        }
837    }
838
839    private android.hardware.Camera mCameraDevice;
840
841    // Prepares media recorder.
842    private void initializeRecorder() {
843        Log.v(TAG, "initializeRecorder");
844        if (mMediaRecorder != null) return;
845
846        // We will call initializeRecorder() again when the alert is hidden.
847        // If the mCameraDevice is null, then this activity is going to finish
848        if (isAlertVisible() || mCameraDevice == null) return;
849
850        Intent intent = getIntent();
851        Bundle myExtras = intent.getExtras();
852
853        long requestedSizeLimit = 0;
854        if (mIsVideoCaptureIntent && myExtras != null) {
855            Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
856            if (saveUri != null) {
857                try {
858                    mCameraVideoFileDescriptor =
859                            mContentResolver.openFileDescriptor(saveUri, "rw")
860                            .getFileDescriptor();
861                    mCurrentVideoUri = saveUri;
862                } catch (java.io.FileNotFoundException ex) {
863                    // invalid uri
864                    Log.e(TAG, ex.toString());
865                }
866            }
867            requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
868        }
869        mMediaRecorder = new MediaRecorder();
870
871        mMediaRecorder.setCamera(mCameraDevice);
872        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
873        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
874        mMediaRecorder.setOutputFormat(mProfile.mFileFormat);
875        mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
876
877        // Set output file.
878        if (mStorageStatus != STORAGE_STATUS_OK) {
879            mMediaRecorder.setOutputFile("/dev/null");
880        } else {
881            // Try Uri in the intent first. If it doesn't exist, use our own
882            // instead.
883            if (mCameraVideoFileDescriptor != null) {
884                mMediaRecorder.setOutputFile(mCameraVideoFileDescriptor);
885            } else {
886                createVideoPath();
887                mMediaRecorder.setOutputFile(mCameraVideoFilename);
888            }
889        }
890
891        // Use the same frame rate for both, since internally
892        // if the frame rate is too large, it can cause camera to become
893        // unstable. We need to fix the MediaRecorder to disable the support
894        // of setting frame rate for now.
895        mMediaRecorder.setVideoFrameRate(mProfile.mVideoFrameRate);
896        mMediaRecorder.setVideoSize(
897                mProfile.mVideoFrameWidth, mProfile.mVideoFrameHeight);
898        mMediaRecorder.setVideoEncodingBitRate(mProfile.mVideoBitRate);
899        mMediaRecorder.setAudioEncodingBitRate(mProfile.mAudioBitRate);
900        mMediaRecorder.setAudioChannels(mProfile.mAudioChannels);
901        mMediaRecorder.setAudioSamplingRate(mProfile.mAudioSampleRate);
902        mMediaRecorder.setVideoEncoder(mProfile.mVideoCodec);
903        mMediaRecorder.setAudioEncoder(mProfile.mAudioCodec);
904        mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
905
906        // Set maximum file size.
907        // remaining >= LOW_STORAGE_THRESHOLD at this point, reserve a quarter
908        // of that to make it more likely that recording can complete
909        // successfully.
910        long maxFileSize = getAvailableStorage() - LOW_STORAGE_THRESHOLD / 4;
911        if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
912            maxFileSize = requestedSizeLimit;
913        }
914
915        try {
916            mMediaRecorder.setMaxFileSize(maxFileSize);
917        } catch (RuntimeException exception) {
918            // We are going to ignore failure of setMaxFileSize here, as
919            // a) The composer selected may simply not support it, or
920            // b) The underlying media framework may not handle 64-bit range
921            // on the size restriction.
922        }
923
924        try {
925            mMediaRecorder.prepare();
926        } catch (IOException e) {
927            Log.e(TAG, "prepare failed for " + mCameraVideoFilename);
928            releaseMediaRecorder();
929            throw new RuntimeException(e);
930        }
931        mMediaRecorderRecording = false;
932
933        // Update the last video thumbnail.
934        if (!mIsVideoCaptureIntent) {
935            if (!mThumbController.isUriValid()) {
936                updateLastVideo();
937            }
938            mThumbController.updateDisplayIfNeeded();
939        }
940    }
941
942    private void releaseMediaRecorder() {
943        Log.v(TAG, "Releasing media recorder.");
944        if (mMediaRecorder != null) {
945            cleanupEmptyFile();
946            mMediaRecorder.reset();
947            mMediaRecorder.release();
948            mMediaRecorder = null;
949        }
950    }
951
952    private void createVideoPath() {
953        long dateTaken = System.currentTimeMillis();
954        String title = createName(dateTaken);
955        String filename = title + ".3gp"; // Used when emailing.
956        String cameraDirPath = ImageManager.CAMERA_IMAGE_BUCKET_NAME;
957        String filePath = cameraDirPath + "/" + filename;
958        File cameraDir = new File(cameraDirPath);
959        cameraDir.mkdirs();
960        ContentValues values = new ContentValues(7);
961        values.put(Video.Media.TITLE, title);
962        values.put(Video.Media.DISPLAY_NAME, filename);
963        values.put(Video.Media.DATE_TAKEN, dateTaken);
964        values.put(Video.Media.MIME_TYPE, "video/3gpp");
965        values.put(Video.Media.DATA, filePath);
966        mCameraVideoFilename = filePath;
967        Log.v(TAG, "Current camera video filename: " + mCameraVideoFilename);
968        mCurrentVideoValues = values;
969    }
970
971    private void registerVideo() {
972        if (mCameraVideoFileDescriptor == null) {
973            Uri videoTable = Uri.parse("content://media/external/video/media");
974            mCurrentVideoValues.put(Video.Media.SIZE,
975                    new File(mCurrentVideoFilename).length());
976            try {
977                mCurrentVideoUri = mContentResolver.insert(videoTable,
978                        mCurrentVideoValues);
979            } catch (Exception e) {
980                // We failed to insert into the database. This can happen if
981                // the SD card is unmounted.
982                mCurrentVideoUri = null;
983                mCurrentVideoFilename = null;
984            } finally {
985                Log.v(TAG, "Current video URI: " + mCurrentVideoUri);
986            }
987        }
988        mCurrentVideoValues = null;
989    }
990
991    private void deleteCurrentVideo() {
992        if (mCurrentVideoFilename != null) {
993            deleteVideoFile(mCurrentVideoFilename);
994            mCurrentVideoFilename = null;
995        }
996        if (mCurrentVideoUri != null) {
997            mContentResolver.delete(mCurrentVideoUri, null, null);
998            mCurrentVideoUri = null;
999        }
1000        updateAndShowStorageHint(true);
1001    }
1002
1003    private void deleteVideoFile(String fileName) {
1004        Log.v(TAG, "Deleting video " + fileName);
1005        File f = new File(fileName);
1006        if (!f.delete()) {
1007            Log.v(TAG, "Could not delete " + fileName);
1008        }
1009    }
1010
1011    private void addBaseMenuItems(Menu menu) {
1012        MenuHelper.addSwitchModeMenuItem(menu, false, new Runnable() {
1013            public void run() {
1014                switchToCameraMode();
1015            }
1016        });
1017        MenuItem gallery = menu.add(Menu.NONE, Menu.NONE,
1018                MenuHelper.POSITION_GOTO_GALLERY,
1019                R.string.camera_gallery_photos_text)
1020                .setOnMenuItemClickListener(
1021                    new OnMenuItemClickListener() {
1022                        public boolean onMenuItemClick(MenuItem item) {
1023                            gotoGallery();
1024                            return true;
1025                        }
1026                    });
1027        gallery.setIcon(android.R.drawable.ic_menu_gallery);
1028        mGalleryItems.add(gallery);
1029    }
1030
1031    private PreferenceGroup filterPreferenceScreenByIntent(
1032            PreferenceGroup screen) {
1033        Intent intent = getIntent();
1034        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
1035            CameraSettings.removePreferenceFromScreen(screen,
1036                    CameraSettings.KEY_VIDEO_QUALITY);
1037        }
1038
1039        if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
1040            CameraSettings.removePreferenceFromScreen(screen,
1041                    CameraSettings.KEY_VIDEO_QUALITY);
1042        }
1043        return screen;
1044    }
1045
1046    // from MediaRecorder.OnErrorListener
1047    public void onError(MediaRecorder mr, int what, int extra) {
1048        if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1049            // We may have run out of space on the sdcard.
1050            stopVideoRecording();
1051            updateAndShowStorageHint(true);
1052        }
1053    }
1054
1055    // from MediaRecorder.OnInfoListener
1056    public void onInfo(MediaRecorder mr, int what, int extra) {
1057        if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
1058            if (mMediaRecorderRecording) onStopVideoRecording(true);
1059        } else if (what
1060                == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
1061            if (mMediaRecorderRecording) onStopVideoRecording(true);
1062
1063            // Show the toast.
1064            Toast.makeText(VideoCamera.this, R.string.video_reach_size_limit,
1065                           Toast.LENGTH_LONG).show();
1066        }
1067    }
1068
1069    /*
1070     * Make sure we're not recording music playing in the background, ask the
1071     * MediaPlaybackService to pause playback.
1072     */
1073    private void pauseAudioPlayback() {
1074        // Shamelessly copied from MediaPlaybackService.java, which
1075        // should be public, but isn't.
1076        Intent i = new Intent("com.android.music.musicservicecommand");
1077        i.putExtra("command", "pause");
1078
1079        sendBroadcast(i);
1080    }
1081
1082    private void startVideoRecording() {
1083        Log.v(TAG, "startVideoRecording");
1084        if (!mMediaRecorderRecording) {
1085
1086            if (mStorageStatus != STORAGE_STATUS_OK) {
1087                Log.v(TAG, "Storage issue, ignore the start request");
1088                return;
1089            }
1090
1091            // Check mMediaRecorder to see whether it is initialized or not.
1092            if (mMediaRecorder == null) {
1093                Log.e(TAG, "MediaRecorder is not initialized.");
1094                return;
1095            }
1096
1097            pauseAudioPlayback();
1098
1099            try {
1100                mMediaRecorder.setOnErrorListener(this);
1101                mMediaRecorder.setOnInfoListener(this);
1102                mMediaRecorder.start(); // Recording is now started
1103            } catch (RuntimeException e) {
1104                Log.e(TAG, "Could not start media recorder. ", e);
1105                return;
1106            }
1107            mMediaRecorderRecording = true;
1108            mRecordingStartTime = SystemClock.uptimeMillis();
1109            updateRecordingIndicator(false);
1110            mRecordingTimeView.setText("");
1111            mRecordingTimeView.setVisibility(View.VISIBLE);
1112            updateRecordingTime();
1113            keepScreenOn();
1114        }
1115    }
1116
1117    private void updateRecordingIndicator(boolean showRecording) {
1118        int drawableId =
1119                showRecording ? R.drawable.btn_ic_video_record
1120                        : R.drawable.btn_ic_video_record_stop;
1121        Drawable drawable = getResources().getDrawable(drawableId);
1122        mShutterButton.setImageDrawable(drawable);
1123    }
1124
1125    private void stopVideoRecordingAndGetThumbnail() {
1126        stopVideoRecording();
1127        acquireVideoThumb();
1128    }
1129
1130    private void stopVideoRecordingAndReturn(boolean valid) {
1131        stopVideoRecording();
1132        doReturnToCaller(valid);
1133    }
1134
1135    private void stopVideoRecordingAndShowAlert() {
1136        stopVideoRecording();
1137        showAlert();
1138    }
1139
1140    private void showAlert() {
1141        fadeOut(findViewById(R.id.shutter_button));
1142        if (mCurrentVideoFilename != null) {
1143            mVideoFrame.setImageBitmap(
1144                    ThumbnailUtils.createVideoThumbnail(mCurrentVideoFilename));
1145            mVideoFrame.setVisibility(View.VISIBLE);
1146        }
1147        int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play};
1148        for (int id : pickIds) {
1149            View button = findViewById(id);
1150            fadeIn(((View) button.getParent()));
1151        }
1152    }
1153
1154    private void hideAlert() {
1155        mVideoFrame.setVisibility(View.INVISIBLE);
1156        fadeIn(findViewById(R.id.shutter_button));
1157        int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play};
1158        for (int id : pickIds) {
1159            View button = findViewById(id);
1160            fadeOut(((View) button.getParent()));
1161        }
1162    }
1163
1164    private static void fadeIn(View view) {
1165        view.setVisibility(View.VISIBLE);
1166        Animation animation = new AlphaAnimation(0F, 1F);
1167        animation.setDuration(500);
1168        view.startAnimation(animation);
1169    }
1170
1171    private static void fadeOut(View view) {
1172        view.setVisibility(View.INVISIBLE);
1173        Animation animation = new AlphaAnimation(1F, 0F);
1174        animation.setDuration(500);
1175        view.startAnimation(animation);
1176    }
1177
1178    private boolean isAlertVisible() {
1179        return this.mVideoFrame.getVisibility() == View.VISIBLE;
1180    }
1181
1182    private void viewLastVideo() {
1183        if (mThumbController.isUriValid()) {
1184            Intent intent = new Intent(Intent.ACTION_VIEW, mThumbController.getUri());
1185            try {
1186                startActivity(intent);
1187            } catch (ActivityNotFoundException ex) {
1188                Log.e(TAG, "review video fail", ex);
1189            }
1190        } else {
1191            Log.e(TAG, "Can't view last video.");
1192        }
1193    }
1194
1195    private void stopVideoRecording() {
1196        Log.v(TAG, "stopVideoRecording");
1197        boolean needToRegisterRecording = false;
1198        if (mMediaRecorderRecording || mMediaRecorder != null) {
1199            if (mMediaRecorderRecording && mMediaRecorder != null) {
1200                try {
1201                    mMediaRecorder.setOnErrorListener(null);
1202                    mMediaRecorder.setOnInfoListener(null);
1203                    mMediaRecorder.stop();
1204                } catch (RuntimeException e) {
1205                    Log.e(TAG, "stop fail: " + e.getMessage());
1206                }
1207
1208                mCurrentVideoFilename = mCameraVideoFilename;
1209                Log.v(TAG, "Setting current video filename: "
1210                        + mCurrentVideoFilename);
1211                needToRegisterRecording = true;
1212                mMediaRecorderRecording = false;
1213            }
1214            releaseMediaRecorder();
1215            updateRecordingIndicator(true);
1216            mRecordingTimeView.setVisibility(View.GONE);
1217            keepScreenOnAwhile();
1218        }
1219        if (needToRegisterRecording && mStorageStatus == STORAGE_STATUS_OK) {
1220            registerVideo();
1221        }
1222
1223        mCameraVideoFilename = null;
1224        mCameraVideoFileDescriptor = null;
1225    }
1226
1227    private void resetScreenOn() {
1228        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1229        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1230    }
1231
1232    private void keepScreenOnAwhile() {
1233        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1234        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1235        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1236    }
1237
1238    private void keepScreenOn() {
1239        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1240        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1241    }
1242
1243    private void hideAlertAndInitializeRecorder() {
1244        hideAlert();
1245        mHandler.sendEmptyMessage(INIT_RECORDER);
1246    }
1247
1248    private void acquireVideoThumb() {
1249        Bitmap videoFrame = ThumbnailUtils.createVideoThumbnail(
1250                mCurrentVideoFilename);
1251        mThumbController.setData(mCurrentVideoUri, videoFrame);
1252    }
1253
1254    private static ImageManager.DataLocation dataLocation() {
1255        return ImageManager.DataLocation.EXTERNAL;
1256    }
1257
1258    private void updateLastVideo() {
1259        IImageList list = ImageManager.makeImageList(
1260                        mContentResolver,
1261                        dataLocation(),
1262                        ImageManager.INCLUDE_VIDEOS,
1263                        ImageManager.SORT_ASCENDING,
1264                        ImageManager.CAMERA_IMAGE_BUCKET_ID);
1265        int count = list.getCount();
1266        if (count > 0) {
1267            IImage image = list.getImageAt(count - 1);
1268            Uri uri = image.fullSizeImageUri();
1269            mThumbController.setData(uri, image.miniThumbBitmap());
1270        } else {
1271            mThumbController.setData(null, null);
1272        }
1273        list.close();
1274    }
1275
1276    private void updateRecordingTime() {
1277        if (!mMediaRecorderRecording) {
1278            return;
1279        }
1280        long now = SystemClock.uptimeMillis();
1281        long delta = now - mRecordingStartTime;
1282
1283        // Starting a minute before reaching the max duration
1284        // limit, we'll countdown the remaining time instead.
1285        boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1286                && delta >= mMaxVideoDurationInMs - 60000);
1287
1288        long next_update_delay = 1000 - (delta % 1000);
1289        long seconds;
1290        if (countdownRemainingTime) {
1291            delta = Math.max(0, mMaxVideoDurationInMs - delta);
1292            seconds = (delta + 999) / 1000;
1293        } else {
1294            seconds = delta / 1000; // round to nearest
1295        }
1296
1297        long minutes = seconds / 60;
1298        long hours = minutes / 60;
1299        long remainderMinutes = minutes - (hours * 60);
1300        long remainderSeconds = seconds - (minutes * 60);
1301
1302        String secondsString = Long.toString(remainderSeconds);
1303        if (secondsString.length() < 2) {
1304            secondsString = "0" + secondsString;
1305        }
1306        String minutesString = Long.toString(remainderMinutes);
1307        if (minutesString.length() < 2) {
1308            minutesString = "0" + minutesString;
1309        }
1310        String text = minutesString + ":" + secondsString;
1311        if (hours > 0) {
1312            String hoursString = Long.toString(hours);
1313            if (hoursString.length() < 2) {
1314                hoursString = "0" + hoursString;
1315            }
1316            text = hoursString + ":" + text;
1317        }
1318        mRecordingTimeView.setText(text);
1319
1320        if (mRecordingTimeCountsDown != countdownRemainingTime) {
1321            // Avoid setting the color on every update, do it only
1322            // when it needs changing.
1323            mRecordingTimeCountsDown = countdownRemainingTime;
1324
1325            int color = getResources().getColor(countdownRemainingTime
1326                    ? R.color.recording_time_remaining_text
1327                    : R.color.recording_time_elapsed_text);
1328
1329            mRecordingTimeView.setTextColor(color);
1330        }
1331
1332        mHandler.sendEmptyMessageDelayed(
1333                UPDATE_RECORD_TIME, next_update_delay);
1334    }
1335
1336    private static boolean isSupported(String value, List<String> supported) {
1337        return supported == null ? false : supported.indexOf(value) >= 0;
1338    }
1339
1340    private void setCameraParameters() {
1341        mParameters = mCameraDevice.getParameters();
1342
1343        mParameters.setPreviewSize(mProfile.mVideoFrameWidth, mProfile.mVideoFrameHeight);
1344        mParameters.setPreviewFrameRate(mProfile.mVideoFrameRate);
1345
1346        // Set flash mode.
1347        String flashMode = mPreferences.getString(
1348                CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
1349                getString(R.string.pref_camera_video_flashmode_default));
1350        List<String> supportedFlash = mParameters.getSupportedFlashModes();
1351        if (isSupported(flashMode, supportedFlash)) {
1352            mParameters.setFlashMode(flashMode);
1353        } else {
1354            flashMode = mParameters.getFlashMode();
1355            if (flashMode == null) {
1356                flashMode = getString(
1357                        R.string.pref_camera_flashmode_no_flash);
1358            }
1359        }
1360
1361        // Set white balance parameter.
1362        String whiteBalance = mPreferences.getString(
1363                CameraSettings.KEY_WHITE_BALANCE,
1364                getString(R.string.pref_camera_whitebalance_default));
1365        if (isSupported(whiteBalance,
1366                mParameters.getSupportedWhiteBalance())) {
1367            mParameters.setWhiteBalance(whiteBalance);
1368        } else {
1369            whiteBalance = mParameters.getWhiteBalance();
1370            if (whiteBalance == null) {
1371                whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1372            }
1373        }
1374
1375        // Set color effect parameter.
1376        String colorEffect = mPreferences.getString(
1377                CameraSettings.KEY_COLOR_EFFECT,
1378                getString(R.string.pref_camera_coloreffect_default));
1379        if (isSupported(colorEffect, mParameters.getSupportedColorEffects())) {
1380            mParameters.setColorEffect(colorEffect);
1381        }
1382
1383        mCameraDevice.setParameters(mParameters);
1384    }
1385
1386    private boolean switchToCameraMode() {
1387        if (mMediaRecorderRecording) return false;
1388        MenuHelper.gotoCameraMode(this);
1389        ((ViewGroup) mGLRootView.getParent()).removeView(mGLRootView);
1390        finish();
1391        return true;
1392    }
1393
1394    public boolean onSwitchChanged(Switcher source, boolean onOff) {
1395        if (onOff == SWITCH_CAMERA) {
1396            return switchToCameraMode();
1397        } else {
1398            return true;
1399        }
1400    }
1401
1402    private void resetCameraParameters() {
1403        // We need to restart the preview if preview size is changed.
1404        Size size = mParameters.getPreviewSize();
1405        if (size.width != mProfile.mVideoFrameWidth
1406                || size.height != mProfile.mVideoFrameHeight) {
1407            // It is assumed media recorder is released before
1408            // onSharedPreferenceChanged, so we can close the camera here.
1409            closeCamera();
1410            try {
1411                resizeForPreviewAspectRatio();
1412                startPreview(); // Parameters will be set in startPreview().
1413            } catch (CameraHardwareException e) {
1414                showCameraBusyAndFinish();
1415            }
1416        } else {
1417            try {
1418                // We need to lock the camera before writing parameters.
1419                mCameraDevice.lock();
1420            } catch (RuntimeException e) {
1421                // When preferences are added for the first time, this method
1422                // will be called. But OnScreenSetting is not displayed yet and
1423                // media recorder still owns the camera. Lock will fail and we
1424                // just ignore it.
1425                return;
1426            }
1427            setCameraParameters();
1428            mCameraDevice.unlock();
1429        }
1430    }
1431
1432    public void onSizeChanged() {
1433        // TODO: update the content on GLRootView
1434    }
1435
1436    private class MyHeadUpDisplayListener implements HeadUpDisplay.Listener {
1437        public void onSharedPreferencesChanged() {
1438            mHandler.post(new Runnable() {
1439                public void run() {
1440                    VideoCamera.this.onSharedPreferencesChanged();
1441                }
1442            });
1443        }
1444
1445        public void onRestorePreferencesClicked() {
1446            mHandler.post(new Runnable() {
1447                public void run() {
1448                    VideoCamera.this.onRestorePreferencesClicked();
1449                }
1450            });
1451        }
1452
1453        public void onPopupWindowVisibilityChanged(final int visibility) {
1454            mHandler.post(new Runnable() {
1455                public void run() {
1456                    VideoCamera.this.onPopupWindowVisibilityChanged(visibility);
1457                }
1458            });
1459        }
1460    }
1461
1462    private void onPopupWindowVisibilityChanged(int visibility) {
1463        if (visibility == GLView.VISIBLE) {
1464            releaseMediaRecorder();
1465        } else {
1466            if (!mPausing) initializeRecorder();
1467        }
1468    }
1469
1470    private void onRestorePreferencesClicked() {
1471        Runnable runnable = new Runnable() {
1472            public void run() {
1473                mHeadUpDisplay.restorePreferences();
1474            }
1475        };
1476        MenuHelper.confirmAction(this,
1477                getString(R.string.confirm_restore_title),
1478                getString(R.string.confirm_restore_message),
1479                runnable);
1480    }
1481
1482    private void onSharedPreferencesChanged() {
1483        // ignore the events after "onPause()" or preview has not started yet
1484        if (mPausing) return;
1485        synchronized (mPreferences) {
1486            readVideoPreferences();
1487            // If mCameraDevice is not ready then we can set the parameter in
1488            // startPreview().
1489            if (mCameraDevice == null) return;
1490            resetCameraParameters();
1491        }
1492    }
1493}
1494