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