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