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