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