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