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