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