VideoCamera.java revision dee42a6e53cb1d0c7194b0cb92028cca353d7c5c
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of 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,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera;
18
19import java.io.File;
20import java.io.IOException;
21import java.io.InputStream;
22import java.io.OutputStream;
23import java.text.SimpleDateFormat;
24import java.util.ArrayList;
25import java.util.Date;
26
27import android.app.Activity;
28import android.app.AlertDialog;
29import android.content.BroadcastReceiver;
30import android.content.ContentResolver;
31import android.content.ContentValues;
32import android.content.Context;
33import android.content.Intent;
34import android.content.IntentFilter;
35import android.content.SharedPreferences;
36import android.graphics.Bitmap;
37import android.graphics.drawable.ColorDrawable;
38import android.graphics.drawable.Drawable;
39import android.location.LocationManager;
40import android.media.MediaMetadataRetriever;
41import android.media.MediaRecorder;
42import android.net.Uri;
43import android.os.Bundle;
44import android.os.Environment;
45import android.os.Handler;
46import android.os.Message;
47import android.os.StatFs;
48import android.os.SystemClock;
49import android.preference.PreferenceManager;
50import android.provider.MediaStore;
51import android.provider.MediaStore.Video;
52import android.text.format.DateFormat;
53import android.util.Log;
54import android.view.KeyEvent;
55import android.view.Menu;
56import android.view.MenuItem;
57import android.view.SurfaceHolder;
58import android.view.View;
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
68public class VideoCamera extends Activity implements View.OnClickListener,
69    ShutterButton.OnShutterButtonListener, SurfaceHolder.Callback {
70
71    private static final String TAG = "videocamera";
72
73    private static final boolean DEBUG = true;
74    private static final boolean DEBUG_SUPPRESS_AUDIO_RECORDING = DEBUG && false;
75    private static final boolean DEBUG_DO_NOT_REUSE_MEDIA_RECORDER = DEBUG && true;
76    private static final boolean DEBUG_LOG_APP_LIFECYCLE = DEBUG && false;
77
78    private static final int CLEAR_SCREEN_DELAY = 4;
79    private static final int UPDATE_RECORD_TIME = 5;
80
81    private static final int SCREEN_DELAY = 2 * 60 * 1000;
82
83    private static final long NO_STORAGE_ERROR = -1L;
84    private static final long CANNOT_STAT_ERROR = -2L;
85    private static final long LOW_STORAGE_THRESHOLD = 512L * 1024L;
86
87    public static final int MENU_SETTINGS = 6;
88    public static final int MENU_GALLERY_PHOTOS = 7;
89    public static final int MENU_GALLERY_VIDEOS = 8;
90    public static final int MENU_SAVE_GALLERY_PHOTO = 34;
91    public static final int MENU_SAVE_PLAY_VIDEO = 35;
92    public static final int MENU_SAVE_SELECT_VIDEO = 36;
93    public static final int MENU_SAVE_NEW_VIDEO = 37;
94
95    SharedPreferences mPreferences;
96
97    private static final float VIDEO_ASPECT_RATIO = 176.0f / 144.0f;
98    VideoPreview mVideoPreview;
99    SurfaceHolder mSurfaceHolder = null;
100    ImageView mBlackout = null;
101    ImageView mVideoFrame;
102    Bitmap mVideoFrameBitmap;
103
104    private MediaRecorder mMediaRecorder;
105    private boolean mMediaRecorderRecording = false;
106    private boolean mNeedToRegisterRecording;
107    private long mRecordingStartTime;
108    // The video file that the hardware camera is about to record into
109    // (or is recording into.)
110    private String mCameraVideoFilename;
111
112    // The video file that has already been recorded, and that is being
113    // examined by the user.
114    private String mCurrentVideoFilename;
115    private Uri mCurrentVideoUri;
116    private ContentValues mCurrentVideoValues;
117
118    boolean mPausing = false;
119
120    static ContentResolver mContentResolver;
121    boolean mDidRegister = false;
122
123    int mCurrentZoomIndex = 0;
124
125    private ShutterButton mShutterButton;
126    private TextView mRecordingTimeView;
127    private boolean mHasSdCard;
128
129    ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>();
130
131    View mPostPictureAlert;
132    LocationManager mLocationManager = null;
133
134    private Handler mHandler = new MainHandler();
135
136    /** This Handler is used to post message back onto the main thread of the application */
137    private class MainHandler extends Handler {
138        @Override
139        public void handleMessage(Message msg) {
140            switch (msg.what) {
141
142                case CLEAR_SCREEN_DELAY: {
143                    clearScreenOnFlag();
144                    break;
145                }
146
147                case UPDATE_RECORD_TIME: {
148                    if (mMediaRecorderRecording) {
149                        long now = SystemClock.uptimeMillis();
150                        long delta = now - mRecordingStartTime;
151                        long seconds = delta / 1000;
152                        long minutes = seconds / 60;
153                        long remainderSeconds = seconds - (minutes * 60);
154
155                        String secondsString = Long.toString(remainderSeconds);
156                        if (secondsString.length() < 2) {
157                            secondsString = "0" + secondsString;
158                        }
159                        String minutesString = Long.toString(minutes);
160                        if (minutesString.length() < 2) {
161                            minutesString = "0" + minutesString;
162                        }
163                        String text = minutesString + ":" + secondsString;
164                        mRecordingTimeView.setText(text);
165                        // Work around a limitation of the T-Mobile G1: The T-Mobile
166                        // hardware blitter can't pixel-accurately scale and clip at the same time,
167                        // and the SurfaceFlinger doesn't attempt to work around this limitation.
168                        // In order to avoid visual corruption we must manually refresh the entire
169                        // surface view when changing any overlapping view's contents.
170                        mVideoPreview.invalidate();
171                        mHandler.sendEmptyMessageDelayed(UPDATE_RECORD_TIME, 1000);
172                    }
173                    break;
174                }
175
176                default:
177                    Log.v(TAG, "Unhandled message: " + msg.what);
178                  break;
179            }
180        }
181    };
182
183    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
184        @Override
185        public void onReceive(Context context, Intent intent) {
186            String action = intent.getAction();
187            if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
188                // SD card available
189                // TODO put up a "please wait" message
190                // TODO also listen for the media scanner finished message
191                showStorageToast();
192                mHasSdCard = true;
193            } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
194                // SD card unavailable
195                showStorageToast();
196                mHasSdCard = false;
197            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
198                Toast.makeText(VideoCamera.this, getResources().getString(R.string.wait), 5000);
199            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
200                showStorageToast();
201            }
202        }
203    };
204
205    static private String createName(long dateTaken) {
206        return DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken).toString();
207    }
208
209    /** Called with the activity is first created. */
210    @Override
211    public void onCreate(Bundle icicle) {
212        if (DEBUG_LOG_APP_LIFECYCLE) {
213            Log.v(TAG, "onCreate " + this.hashCode());
214        }
215        super.onCreate(icicle);
216
217        mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
218
219        mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
220        mContentResolver = getContentResolver();
221
222        //setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
223        requestWindowFeature(Window.FEATURE_PROGRESS);
224        setContentView(R.layout.video_camera);
225
226        mVideoPreview = (VideoPreview) findViewById(R.id.camera_preview);
227        mVideoPreview.setAspectRatio(VIDEO_ASPECT_RATIO);
228
229        // don't set mSurfaceHolder here. We have it set ONLY within
230        // surfaceCreated / surfaceDestroyed, other parts of the code
231        // assume that when it is set, the surface is also set.
232        SurfaceHolder holder = mVideoPreview.getHolder();
233        holder.addCallback(this);
234        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
235
236        mBlackout = (ImageView) findViewById(R.id.blackout);
237        mBlackout.setBackgroundDrawable(new ColorDrawable(0xFF000000));
238
239        mPostPictureAlert = findViewById(R.id.post_picture_panel);
240
241        int[] ids = new int[]{R.id.play, R.id.share, R.id.discard,
242                R.id.cancel, R.id.attach};
243        for (int id : ids) {
244            findViewById(id).setOnClickListener(this);
245        }
246
247        mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
248        mShutterButton.setOnShutterButtonListener(this);
249        mRecordingTimeView = (TextView) findViewById(R.id.recording_time);
250        mVideoFrame = (ImageView) findViewById(R.id.video_frame);
251    }
252
253    @Override
254    public void onStart() {
255        if (DEBUG_LOG_APP_LIFECYCLE) {
256            Log.v(TAG, "onStart " + this.hashCode());
257        }
258        super.onStart();
259
260        Thread t = new Thread(new Runnable() {
261            public void run() {
262                final boolean storageOK = getAvailableStorage() >= LOW_STORAGE_THRESHOLD;
263
264                if (!storageOK) {
265                    mHandler.post(new Runnable() {
266                        public void run() {
267                            showStorageToast();
268                        }
269                    });
270                }
271            }
272        });
273        t.start();
274    }
275
276    public void onClick(View v) {
277        switch (v.getId()) {
278
279            case R.id.attach:
280                doReturnToCaller(true);
281                break;
282
283            case R.id.cancel:
284                doReturnToCaller(false);
285                break;
286
287            case R.id.discard: {
288                discardCurrentVideoAndStartPreview();
289                break;
290            }
291
292            case R.id.share: {
293                Intent intent = new Intent();
294                intent.setAction(Intent.ACTION_SEND);
295                intent.setType("video/3gpp");
296                intent.putExtra(Intent.EXTRA_STREAM, mCurrentVideoUri);
297                try {
298                    startActivity(Intent.createChooser(intent, getText(R.string.sendVideo)));
299                } catch (android.content.ActivityNotFoundException ex) {
300                    Toast.makeText(VideoCamera.this, R.string.no_way_to_share_video, Toast.LENGTH_SHORT).show();
301                }
302
303                break;
304            }
305
306            case R.id.play: {
307                doPlayCurrentVideo();
308                break;
309            }
310        }
311    }
312
313    public void onShutterButtonFocus(ShutterButton button, boolean pressed) {
314        switch (button.getId()) {
315            case R.id.shutter_button:
316                if (pressed) {
317                    if (mMediaRecorderRecording) {
318                        stopVideoRecordingAndDisplayDialog();
319                    } else if (mVideoFrame.getVisibility() == View.VISIBLE) {
320                        doStartCaptureMode();
321                    } else {
322                        startVideoRecording();
323                    }
324                }
325                break;
326        }
327    }
328
329    public void onShutterButtonClick(ShutterButton button) {
330        // Do nothing (everything happens in onShutterButtonFocus).
331    }
332
333    private void doStartCaptureMode() {
334        if (isVideoCaptureIntent()) {
335            discardCurrentVideoAndStartPreview();
336        } else {
337            hideVideoFrameAndStartPreview();
338        }
339    }
340
341    private void doPlayCurrentVideo() {
342        Log.e(TAG, "Playing current video: " + mCurrentVideoUri);
343        Intent intent = new Intent(Intent.ACTION_VIEW, mCurrentVideoUri);
344        try {
345            startActivity(intent);
346        } catch (android.content.ActivityNotFoundException ex) {
347            Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
348        }
349    }
350
351    private void discardCurrentVideoAndStartPreview() {
352        deleteCurrentVideo();
353        hideVideoFrameAndStartPreview();
354    }
355
356    private void showStorageToast() {
357        long remaining = getAvailableStorage();
358
359        if (remaining == NO_STORAGE_ERROR) {
360            Toast.makeText(this, getString(R.string.no_storage), Toast.LENGTH_LONG).show();
361        } else if (remaining < LOW_STORAGE_THRESHOLD) {
362            new AlertDialog.Builder(this).setTitle(R.string.spaceIsLow_title)
363                .setMessage(R.string.spaceIsLow_content)
364                .show();
365        }
366    }
367
368    @Override
369    public void onResume() {
370        if (DEBUG_LOG_APP_LIFECYCLE) {
371            Log.v(TAG, "onResume " + this.hashCode());
372        }
373        super.onResume();
374
375        setScreenTimeoutLong();
376
377        mPausing = false;
378
379        // install an intent filter to receive SD card related events.
380        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
381        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
382        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
383        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
384        intentFilter.addDataScheme("file");
385        registerReceiver(mReceiver, intentFilter);
386        mDidRegister = true;
387        mHasSdCard = ImageManager.hasStorage();
388
389        mBlackout.setVisibility(View.INVISIBLE);
390        if (mVideoFrameBitmap == null) {
391            initializeVideo();
392        } else {
393            showPostRecordingAlert();
394        }
395    }
396
397    @Override
398    public void onStop() {
399        if (DEBUG_LOG_APP_LIFECYCLE) {
400            Log.v(TAG, "onStop " + this.hashCode());
401        }
402        stopVideoRecording();
403        setScreenTimeoutSystemDefault();
404        super.onStop();
405    }
406
407    @Override
408    protected void onPause() {
409        if (DEBUG_LOG_APP_LIFECYCLE) {
410            Log.v(TAG, "onPause " + this.hashCode());
411        }
412        super.onPause();
413
414        stopVideoRecording();
415        hidePostPictureAlert();
416
417        mPausing = true;
418
419        if (mDidRegister) {
420            unregisterReceiver(mReceiver);
421            mDidRegister = false;
422        }
423        mBlackout.setVisibility(View.VISIBLE);
424        setScreenTimeoutSystemDefault();
425    }
426
427    @Override
428    public boolean onKeyDown(int keyCode, KeyEvent event) {
429        setScreenTimeoutLong();
430
431        switch (keyCode) {
432            case KeyEvent.KEYCODE_BACK:
433                if (mMediaRecorderRecording) {
434                    Log.v(TAG, "onKeyBack");
435                    stopVideoRecordingAndDisplayDialog();
436                    return true;
437                } else if(isPostRecordingAlertVisible()) {
438                    hideVideoFrameAndStartPreview();
439                    return true;
440                }
441                break;
442            case KeyEvent.KEYCODE_CAMERA:
443                if (event.getRepeatCount() == 0) {
444                    // If we get a dpad center event without any focused view, move the
445                    // focus to the shutter button and press it.
446                    if (mShutterButton.isInTouchMode()) {
447                        mShutterButton.requestFocusFromTouch();
448                    } else {
449                        mShutterButton.requestFocus();
450                    }
451                    mShutterButton.setPressed(true);
452                    return true;
453                }
454                return true;
455            case KeyEvent.KEYCODE_DPAD_CENTER:
456                if (event.getRepeatCount() == 0) {
457                    // If we get a dpad center event without any focused view, move the
458                    // focus to the shutter button and press it.
459                    if (mShutterButton.isInTouchMode()) {
460                        mShutterButton.requestFocusFromTouch();
461                    } else {
462                        mShutterButton.requestFocus();
463                    }
464                    mShutterButton.setPressed(true);
465                }
466                break;
467            case KeyEvent.KEYCODE_MENU:
468                if (mMediaRecorderRecording) {
469                    stopVideoRecordingAndDisplayDialog();
470                    return true;
471                }
472                break;
473        }
474
475        return super.onKeyDown(keyCode, event);
476    }
477
478    @Override
479    public boolean onKeyUp(int keyCode, KeyEvent event) {
480        switch(keyCode) {
481        case KeyEvent.KEYCODE_CAMERA:
482            mShutterButton.setPressed(false);
483            return true;
484        }
485        return super.onKeyUp(keyCode, event);
486    }
487
488    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
489        stopVideoRecording();
490        initializeVideo();
491    }
492
493    public void surfaceCreated(SurfaceHolder holder) {
494        mSurfaceHolder = holder;
495    }
496
497    public void surfaceDestroyed(SurfaceHolder holder) {
498        mSurfaceHolder = null;
499    }
500
501    void gotoGallery() {
502        MenuHelper.gotoCameraVideoGallery(this);
503    }
504
505    @Override
506    public boolean onPrepareOptionsMenu(Menu menu) {
507        super.onPrepareOptionsMenu(menu);
508
509        for (int i = 1; i <= MenuHelper.MENU_ITEM_MAX; i++) {
510            if (i != MenuHelper.GENERIC_ITEM) {
511                menu.setGroupVisible(i, false);
512            }
513        }
514
515        menu.setGroupVisible(MenuHelper.VIDEO_MODE_ITEM, true);
516        return true;
517    }
518
519    @Override
520    public boolean onCreateOptionsMenu(Menu menu) {
521        super.onCreateOptionsMenu(menu);
522
523        if (isVideoCaptureIntent()) {
524            // No options menu for attach mode.
525            return false;
526        } else {
527            addBaseMenuItems(menu);
528            MenuHelper.addImageMenuItems(
529                    menu,
530                    MenuHelper.INCLUDE_ALL & ~MenuHelper.INCLUDE_ROTATE_MENU,
531                    false,
532                    VideoCamera.this,
533                    mHandler,
534
535                    // Handler for deletion
536                    new Runnable() {
537                        public void run() {
538                            // What do we do here?
539                            // mContentResolver.delete(uri, null, null);
540                        }
541                    },
542                    new MenuHelper.MenuInvoker() {
543                        public void run(final MenuHelper.MenuCallback cb) {
544                        }
545                    });
546
547            MenuItem gallery = menu.add(MenuHelper.IMAGE_SAVING_ITEM, MENU_SAVE_GALLERY_PHOTO, 0,
548                    R.string.camera_gallery_photos_text).setOnMenuItemClickListener(
549                            new MenuItem.OnMenuItemClickListener() {
550                public boolean onMenuItemClick(MenuItem item) {
551                    gotoGallery();
552                    return true;
553                }
554            });
555            gallery.setIcon(android.R.drawable.ic_menu_gallery);
556        }
557        return true;
558    }
559
560    private boolean isVideoCaptureIntent() {
561        String action = getIntent().getAction();
562        return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
563    }
564
565    private void doReturnToCaller(boolean success) {
566        Intent resultIntent = new Intent();
567        int resultCode;
568        if (success) {
569            resultCode = RESULT_OK;
570            Uri saveUri = null;
571
572            Bundle myExtras = getIntent().getExtras();
573            if (myExtras != null) {
574                saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
575            }
576
577            if (saveUri != null) {
578                // TODO: Record the video directly into the content provider stream when
579                // bug 1582062 is fixed. Until then we copy the video data from the
580                // original location to the requested location and then delete the original.
581                OutputStream outputStream = null;
582                InputStream inputStream = null;
583
584                try {
585                    inputStream = mContentResolver.openInputStream(mCurrentVideoUri);
586                    outputStream = mContentResolver.openOutputStream(saveUri);
587                    byte[] buffer = new byte[64*1024];
588                    while(true) {
589                        int bytesRead = inputStream.read(buffer);
590                        if (bytesRead < 0) {
591                            break;
592                        }
593                        outputStream.write(buffer, 0, bytesRead);
594                    }
595                } catch (IOException ex) {
596                    Log.e(TAG, "Could not copy video file to Uri", ex);
597                } finally {
598                    if (inputStream != null) {
599                        try {
600                            inputStream.close();
601                        } catch (IOException ex) {
602                            Log.e(TAG, "Could not close video file", ex);
603                        }
604                    }
605                    if (outputStream != null) {
606                        try {
607                            outputStream.close();
608                        } catch (IOException ex) {
609                            Log.e(TAG, "Could not close output uri", ex);
610                        }
611                    }
612                    deleteCurrentVideo();
613                }
614            } else {
615                resultIntent.setData(mCurrentVideoUri);
616            }
617        } else {
618            resultCode = RESULT_CANCELED;
619        }
620        setResult(resultCode, resultIntent);
621        finish();
622    }
623
624    /**
625     * Returns
626     * @return number of bytes available, or an ERROR code.
627     */
628    private static long getAvailableStorage() {
629        try {
630            if (!ImageManager.hasStorage()) {
631                return NO_STORAGE_ERROR;
632            } else {
633                String storageDirectory = Environment.getExternalStorageDirectory().toString();
634                StatFs stat = new StatFs(storageDirectory);
635                return ((long)stat.getAvailableBlocks() * (long)stat.getBlockSize());
636            }
637        } catch (Exception ex) {
638            // if we can't stat the filesystem then we don't know how many
639            // free bytes exist.  It might be zero but just leave it
640            // blank since we really don't know.
641            return CANNOT_STAT_ERROR;
642        }
643    }
644
645    private void initializeVideo() {
646        Log.v(TAG, "initializeVideo");
647        releaseMediaRecorder();
648
649        if (mSurfaceHolder == null) {
650            Log.v(TAG, "SurfaceHolder is null");
651            return;
652        }
653
654        mMediaRecorder = new MediaRecorder();
655        mNeedToRegisterRecording = false;
656
657        if (DEBUG_SUPPRESS_AUDIO_RECORDING) {
658            Log.v(TAG, "DEBUG_SUPPRESS_AUDIO_RECORDING is true.");
659        } else {
660            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
661        }
662        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
663        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
664        createVideoPath();
665        mMediaRecorder.setOutputFile(mCameraVideoFilename);
666        boolean videoQualityHigh = getBooleanPreference(CameraSettings.KEY_VIDEO_QUALITY,
667                CameraSettings.DEFAULT_VIDEO_QUALITY_VALUE);
668
669        {
670            Intent intent = getIntent();
671            if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
672                int extraVideoQuality = intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
673                videoQualityHigh = (extraVideoQuality > 0);
674            }
675        }
676
677        // Use the same frame rate for both, since internally
678        // if the frame rate is too large, it can cause camera to become
679        // unstable. We need to fix the MediaRecorder to disable the support
680        // of setting frame rate for now.
681        mMediaRecorder.setVideoFrameRate(20);
682        if (videoQualityHigh) {
683            mMediaRecorder.setVideoSize(352,288);
684        } else {
685            mMediaRecorder.setVideoSize(176,144);
686        }
687        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H263);
688        if (!DEBUG_SUPPRESS_AUDIO_RECORDING) {
689            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
690        }
691        mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
692        try {
693            mMediaRecorder.prepare();
694        } catch (IOException exception) {
695            Log.e(TAG, "prepare failed for " + mCameraVideoFilename);
696            releaseMediaRecorder();
697            // TODO: add more exception handling logic here
698            return;
699        }
700        mMediaRecorderRecording = false;
701    }
702
703    private void releaseMediaRecorder() {
704        Log.v(TAG, "Releasing media recorder.");
705        if (mMediaRecorder != null) {
706            mMediaRecorder.reset();
707            mMediaRecorder.release();
708            mMediaRecorder = null;
709        }
710    }
711
712    private void restartPreview() {
713        if (DEBUG_DO_NOT_REUSE_MEDIA_RECORDER) {
714            Log.v(TAG, "DEBUG_DO_NOT_REUSE_MEDIA_RECORDER recreating mMediaRecorder.");
715            initializeVideo();
716        } else {
717            try {
718                mMediaRecorder.prepare();
719            } catch (IOException exception) {
720                Log.e(TAG, "prepare failed for " + mCameraVideoFilename);
721                releaseMediaRecorder();
722                // TODO: add more exception handling logic here
723            }
724        }
725    }
726
727    private int getIntPreference(String key, int defaultValue) {
728        String s = mPreferences.getString(key, "");
729        int result = defaultValue;
730        try {
731            result = Integer.parseInt(s);
732        } catch (NumberFormatException e) {
733            // Ignore, result is already the default value.
734        }
735        return result;
736    }
737
738    private boolean getBooleanPreference(String key, boolean defaultValue) {
739        return getIntPreference(key, defaultValue ? 1 : 0) != 0;
740    }
741
742    private void createVideoPath() {
743        long dateTaken = System.currentTimeMillis();
744        String title = createName(dateTaken);
745        String displayName = title + ".3gp"; // Used when emailing.
746        String cameraDirPath = ImageManager.CAMERA_IMAGE_BUCKET_NAME;
747        File cameraDir = new File(cameraDirPath);
748        cameraDir.mkdirs();
749        SimpleDateFormat dateFormat = new SimpleDateFormat(
750                getString(R.string.video_file_name_format));
751        Date date = new Date(dateTaken);
752        String filepart = dateFormat.format(date);
753        String filename = cameraDirPath + "/" + filepart + ".3gp";
754        ContentValues values = new ContentValues(7);
755        values.put(Video.Media.TITLE, title);
756        values.put(Video.Media.DISPLAY_NAME, displayName);
757        values.put(Video.Media.DESCRIPTION, "");
758        values.put(Video.Media.DATE_TAKEN, dateTaken);
759        values.put(Video.Media.MIME_TYPE, "video/3gpp");
760        values.put(Video.Media.DATA, filename);
761        mCameraVideoFilename = filename;
762        Log.v(TAG, "Current camera video filename: " + mCameraVideoFilename);
763        mCurrentVideoValues = values;
764    }
765
766    private void registerVideo() {
767        Uri videoTable = Uri.parse("content://media/external/video/media");
768        mCurrentVideoUri = mContentResolver.insert(videoTable,
769                mCurrentVideoValues);
770        Log.v(TAG, "Current video URI: " + mCurrentVideoUri);
771        mCurrentVideoValues = null;
772    }
773
774    private void deleteCurrentVideo() {
775        if (mCurrentVideoFilename != null) {
776            deleteVideoFile(mCurrentVideoFilename);
777            mCurrentVideoFilename = null;
778        }
779        if (mCurrentVideoUri != null) {
780            mContentResolver.delete(mCurrentVideoUri, null, null);
781            mCurrentVideoUri = null;
782        }
783    }
784
785    private void deleteVideoFile(String fileName) {
786        Log.v(TAG, "Deleting video " + fileName);
787        File f = new File(fileName);
788        if (! f.delete()) {
789            Log.v(TAG, "Could not delete " + fileName);
790        }
791    }
792
793    private void addBaseMenuItems(Menu menu) {
794        MenuHelper.addSwitchModeMenuItem(menu, this, false);
795        {
796            MenuItem gallery = menu.add(MenuHelper.IMAGE_MODE_ITEM, MENU_GALLERY_PHOTOS, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new OnMenuItemClickListener() {
797                public boolean onMenuItemClick(MenuItem item) {
798                    gotoGallery();
799                    return true;
800                }
801            });
802            gallery.setIcon(android.R.drawable.ic_menu_gallery);
803            mGalleryItems.add(gallery);
804        }
805        {
806            MenuItem gallery = menu.add(MenuHelper.VIDEO_MODE_ITEM, MENU_GALLERY_VIDEOS, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new OnMenuItemClickListener() {
807                public boolean onMenuItemClick(MenuItem item) {
808                    gotoGallery();
809                    return true;
810                }
811            });
812            gallery.setIcon(android.R.drawable.ic_menu_gallery);
813            mGalleryItems.add(gallery);
814        }
815
816        MenuItem item = menu.add(MenuHelper.GENERIC_ITEM, MENU_SETTINGS, 0, R.string.settings).setOnMenuItemClickListener(new OnMenuItemClickListener() {
817            public boolean onMenuItemClick(MenuItem item) {
818                Intent intent = new Intent();
819                intent.setClass(VideoCamera.this, CameraSettings.class);
820                startActivity(intent);
821                return true;
822            }
823        });
824        item.setIcon(android.R.drawable.ic_menu_preferences);
825    }
826
827    private void startVideoRecording() {
828        Log.v(TAG, "startVideoRecording");
829        if (!mMediaRecorderRecording) {
830
831            if (!mHasSdCard) {
832                Toast.makeText(this, getString(
833                        R.string.no_storage), Toast.LENGTH_LONG).show();
834                Log.v(TAG, "No SD card, ignore start recording");
835                return;
836            }
837
838            // Check mMediaRecorder to see whether it is initialized or not.
839            if (mMediaRecorder == null) {
840                initializeVideo();
841            }
842            try {
843                mMediaRecorder.start();   // Recording is now started
844            } catch (RuntimeException e) {
845                Log.e(TAG, "Could not start media recorder. ", e);
846                return;
847            }
848            mMediaRecorderRecording = true;
849            mRecordingStartTime = SystemClock.uptimeMillis();
850            updateRecordingIndicator(true);
851            mRecordingTimeView.setText("");
852            mRecordingTimeView.setVisibility(View.VISIBLE);
853            mHandler.sendEmptyMessage(UPDATE_RECORD_TIME);
854            setScreenTimeoutInfinite();
855        }
856    }
857
858    private void updateRecordingIndicator(boolean showRecording) {
859        int drawableId = showRecording ? R.drawable.ic_camera_bar_indicator_record
860            : R.drawable.ic_camera_indicator_video;
861        Drawable drawable = getResources().getDrawable(drawableId);
862        mShutterButton.setImageDrawable(drawable);
863    }
864
865    private void stopVideoRecordingAndDisplayDialog() {
866        Log.v(TAG, "stopVideoRecordingAndDisplayDialog");
867        if (mMediaRecorderRecording) {
868            stopVideoRecording();
869            acquireAndShowVideoFrame();
870            showPostRecordingAlert();
871        }
872    }
873
874    private void showPostRecordingAlert() {
875        int[] pickIds = {R.id.attach, R.id.cancel};
876        int[] normalIds = {R.id.share, R.id.discard};
877        int[] alwaysOnIds = {R.id.play};
878        int[] hideIds = pickIds;
879        int[] connectIds = normalIds;
880        if (isVideoCaptureIntent()) {
881            hideIds = normalIds;
882            connectIds = pickIds;
883        }
884        for(int id : hideIds) {
885            mPostPictureAlert.findViewById(id).setVisibility(View.GONE);
886        }
887        connectAndFadeIn(connectIds);
888        connectAndFadeIn(alwaysOnIds);
889        mPostPictureAlert.setVisibility(View.VISIBLE);
890    }
891
892    private void connectAndFadeIn(int[] connectIds) {
893        for(int id : connectIds) {
894            View view = mPostPictureAlert.findViewById(id);
895            view.setOnClickListener(this);
896            Animation animation = new AlphaAnimation(0F, 1F);
897            animation.setDuration(500);
898            view.setAnimation(animation);
899        }
900    }
901
902    private void hidePostPictureAlert() {
903        mPostPictureAlert.setVisibility(View.INVISIBLE);
904    }
905
906    private boolean isPostRecordingAlertVisible() {
907        return mPostPictureAlert.getVisibility() == View.VISIBLE;
908    }
909
910    private void stopVideoRecording() {
911        Log.v(TAG, "stopVideoRecording");
912        if (mMediaRecorderRecording || mMediaRecorder != null) {
913            if (mMediaRecorderRecording) {
914                mMediaRecorder.stop();
915                mCurrentVideoFilename = mCameraVideoFilename;
916                Log.v(TAG, "Setting current video filename: " + mCurrentVideoFilename);
917                mCameraVideoFilename = null;
918                mNeedToRegisterRecording = true;
919                mMediaRecorderRecording = false;
920            }
921            releaseMediaRecorder();
922            updateRecordingIndicator(false);
923            mRecordingTimeView.setVisibility(View.GONE);
924            setScreenTimeoutLong();
925        }
926        if (mNeedToRegisterRecording) {
927            registerVideo();
928            mNeedToRegisterRecording = false;
929        }
930        mCameraVideoFilename = null;
931    }
932
933    private void setScreenTimeoutSystemDefault() {
934        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
935        clearScreenOnFlag();
936    }
937
938    private void setScreenTimeoutLong() {
939        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
940        setScreenOnFlag();
941        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
942    }
943
944    private void setScreenTimeoutInfinite() {
945        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
946        setScreenOnFlag();
947    }
948
949    private void clearScreenOnFlag() {
950        Window w = getWindow();
951        final int keepScreenOnFlag = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
952        if ((w.getAttributes().flags & keepScreenOnFlag) != 0) {
953            w.clearFlags(keepScreenOnFlag);
954        }
955    }
956
957    private void setScreenOnFlag() {
958        Window w = getWindow();
959        final int keepScreenOnFlag = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
960        if ((w.getAttributes().flags & keepScreenOnFlag) == 0) {
961            w.addFlags(keepScreenOnFlag);
962        }
963    }
964
965    private void hideVideoFrameAndStartPreview() {
966        hidePostPictureAlert();
967        hideVideoFrame();
968        restartPreview();
969    }
970
971    private void acquireAndShowVideoFrame() {
972        recycleVideoFrameBitmap();
973        mVideoFrameBitmap = ImageManager.createVideoThumbnail(mCurrentVideoFilename);
974        mVideoFrame.setImageBitmap(mVideoFrameBitmap);
975        mVideoFrame.setVisibility(View.VISIBLE);
976    }
977
978    private void hideVideoFrame() {
979        recycleVideoFrameBitmap();
980        mVideoFrame.setVisibility(View.GONE);
981    }
982
983    private void recycleVideoFrameBitmap() {
984        if (mVideoFrameBitmap != null) {
985            mVideoFrame.setImageDrawable(null);
986            mVideoFrameBitmap.recycle();
987            mVideoFrameBitmap = null;
988        }
989    }
990}
991
992