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