• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
handleMessage(Message msg)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
onReceive(Context context, Intent intent)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 
createName(long dateTaken)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 
showCameraErrorAndFinish()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 
restartPreview()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
onCreate(Bundle icicle)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 
changeHeadUpDisplayState()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 
initializeHeadUpDisplay()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 
attachHeadUpDisplay()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 
detachHeadUpDisplay()430     private void detachHeadUpDisplay() {
431         mHeadUpDisplay.collapse();
432         ((ViewGroup) mGLRootView.getParent()).removeView(mGLRootView);
433         mGLRootView = null;
434     }
435 
roundOrientation(int orientation)436     public static int roundOrientation(int orientation) {
437         return ((orientation + 45) / 90 * 90) % 360;
438     }
439 
440     private class MyOrientationEventListener
441             extends OrientationEventListener {
MyOrientationEventListener(Context context)442         public MyOrientationEventListener(Context context) {
443             super(context);
444         }
445 
446         @Override
onOrientationChanged(int orientation)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 
setOrientationIndicator(int degree)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
onStart()478     protected void onStart() {
479         super.onStart();
480         if (!mIsVideoCaptureIntent) {
481             mSwitcher.setSwitch(SWITCH_VIDEO);
482         }
483     }
484 
startPlayVideoActivity()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 
onClick(View v)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 
onShutterButtonFocus(ShutterButton button, boolean pressed)515     public void onShutterButtonFocus(ShutterButton button, boolean pressed) {
516         // Do nothing (everything happens in onShutterButtonClick).
517     }
518 
onStopVideoRecording(boolean valid)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 
onShutterButtonClick(ShutterButton button)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 
updateAndShowStorageHint(boolean mayHaveSd)550     private void updateAndShowStorageHint(boolean mayHaveSd) {
551         mStorageStatus = getStorageStatus(mayHaveSd);
552         showStorageHint();
553     }
554 
showStorageHint()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 
getStorageStatus(boolean mayHaveSd)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 
readVideoPreferences()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 
resizeForPreviewAspectRatio()624     private void resizeForPreviewAspectRatio() {
625         mPreviewFrameLayout.setAspectRatio(
626                 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
627     }
628 
629     @Override
onResume()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 
setPreviewDisplay(SurfaceHolder holder)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 
startPreview()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 
closeCamera()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
onPause()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
onUserInteraction()759     public void onUserInteraction() {
760         super.onUserInteraction();
761         if (!mMediaRecorderRecording) keepScreenOnAwhile();
762     }
763 
764     @Override
onBackPressed()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
onKeyDown(int keyCode, KeyEvent event)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
onKeyUp(int keyCode, KeyEvent event)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 
surfaceChanged(SurfaceHolder holder, int format, int w, int h)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 
surfaceCreated(SurfaceHolder holder)849     public void surfaceCreated(SurfaceHolder holder) {
850     }
851 
surfaceDestroyed(SurfaceHolder holder)852     public void surfaceDestroyed(SurfaceHolder holder) {
853         mSurfaceHolder = null;
854     }
855 
gotoGallery()856     private void gotoGallery() {
857         MenuHelper.gotoCameraVideoGallery(this);
858     }
859 
860     @Override
onCreateOptionsMenu(Menu menu)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 
isVideoCaptureIntent()873     private boolean isVideoCaptureIntent() {
874         String action = getIntent().getAction();
875         return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
876     }
877 
doReturnToCaller(boolean valid)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      */
getAvailableStorage()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 
cleanupEmptyFile()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.
initializeRecorder()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 
releaseMediaRecorder()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. Camera
1041         // device may be null if the activity is paused.
1042         if (mCameraDevice != null) mCameraDevice.lock();
1043     }
1044 
createVideoPath()1045     private void createVideoPath() {
1046         long dateTaken = System.currentTimeMillis();
1047         String title = createName(dateTaken);
1048         String filename = title + ".3gp"; // Used when emailing.
1049         String cameraDirPath = ImageManager.CAMERA_IMAGE_BUCKET_NAME;
1050         String filePath = cameraDirPath + "/" + filename;
1051         File cameraDir = new File(cameraDirPath);
1052         cameraDir.mkdirs();
1053         ContentValues values = new ContentValues(7);
1054         values.put(Video.Media.TITLE, title);
1055         values.put(Video.Media.DISPLAY_NAME, filename);
1056         values.put(Video.Media.DATE_TAKEN, dateTaken);
1057         values.put(Video.Media.MIME_TYPE, "video/3gpp");
1058         values.put(Video.Media.DATA, filePath);
1059         mVideoFilename = filePath;
1060         Log.v(TAG, "Current camera video filename: " + mVideoFilename);
1061         mCurrentVideoValues = values;
1062     }
1063 
registerVideo()1064     private void registerVideo() {
1065         if (mVideoFileDescriptor == null) {
1066             Uri videoTable = Uri.parse("content://media/external/video/media");
1067             mCurrentVideoValues.put(Video.Media.SIZE,
1068                     new File(mCurrentVideoFilename).length());
1069             try {
1070                 mCurrentVideoUri = mContentResolver.insert(videoTable,
1071                         mCurrentVideoValues);
1072             } catch (Exception e) {
1073                 // We failed to insert into the database. This can happen if
1074                 // the SD card is unmounted.
1075                 mCurrentVideoUri = null;
1076                 mCurrentVideoFilename = null;
1077             } finally {
1078                 Log.v(TAG, "Current video URI: " + mCurrentVideoUri);
1079             }
1080         }
1081         mCurrentVideoValues = null;
1082     }
1083 
deleteCurrentVideo()1084     private void deleteCurrentVideo() {
1085         if (mCurrentVideoFilename != null) {
1086             deleteVideoFile(mCurrentVideoFilename);
1087             mCurrentVideoFilename = null;
1088         }
1089         if (mCurrentVideoUri != null) {
1090             mContentResolver.delete(mCurrentVideoUri, null, null);
1091             mCurrentVideoUri = null;
1092         }
1093         updateAndShowStorageHint(true);
1094     }
1095 
deleteVideoFile(String fileName)1096     private void deleteVideoFile(String fileName) {
1097         Log.v(TAG, "Deleting video " + fileName);
1098         File f = new File(fileName);
1099         if (!f.delete()) {
1100             Log.v(TAG, "Could not delete " + fileName);
1101         }
1102     }
1103 
addBaseMenuItems(Menu menu)1104     private void addBaseMenuItems(Menu menu) {
1105         MenuHelper.addSwitchModeMenuItem(menu, false, new Runnable() {
1106             public void run() {
1107                 switchToCameraMode();
1108             }
1109         });
1110         MenuItem gallery = menu.add(Menu.NONE, Menu.NONE,
1111                 MenuHelper.POSITION_GOTO_GALLERY,
1112                 R.string.camera_gallery_photos_text)
1113                 .setOnMenuItemClickListener(
1114                     new OnMenuItemClickListener() {
1115                         public boolean onMenuItemClick(MenuItem item) {
1116                             gotoGallery();
1117                             return true;
1118                         }
1119                     });
1120         gallery.setIcon(android.R.drawable.ic_menu_gallery);
1121         mGalleryItems.add(gallery);
1122 
1123         if (mNumberOfCameras > 1) {
1124             menu.add(Menu.NONE, Menu.NONE,
1125                     MenuHelper.POSITION_SWITCH_CAMERA_ID,
1126                     R.string.switch_camera_id)
1127                     .setOnMenuItemClickListener(new OnMenuItemClickListener() {
1128                 public boolean onMenuItemClick(MenuItem item) {
1129                     switchCameraId((mCameraId + 1) % mNumberOfCameras);
1130                     return true;
1131                 }
1132             }).setIcon(android.R.drawable.ic_menu_camera);
1133         }
1134     }
1135 
switchCameraId(int cameraId)1136     private void switchCameraId(int cameraId) {
1137         if (mPausing) return;
1138         mCameraId = cameraId;
1139         CameraSettings.writePreferredCameraId(mPreferences, cameraId);
1140 
1141         // This is similar to what mShutterButton.performClick() does,
1142         // but not quite the same.
1143         if (mMediaRecorderRecording) {
1144             if (mIsVideoCaptureIntent) {
1145                 stopVideoRecording();
1146                 showAlert();
1147             } else {
1148                 stopVideoRecordingAndGetThumbnail();
1149             }
1150         } else {
1151             stopVideoRecording();
1152         }
1153         closeCamera();
1154 
1155         // Reload the preferences.
1156         mPreferences.setLocalId(this, mCameraId);
1157         CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
1158         // Read media profile again because camera id is changed.
1159         readVideoPreferences();
1160         resizeForPreviewAspectRatio();
1161         restartPreview();
1162 
1163         // Reload the UI.
1164         initializeHeadUpDisplay();
1165     }
1166 
filterPreferenceScreenByIntent( PreferenceGroup screen)1167     private PreferenceGroup filterPreferenceScreenByIntent(
1168             PreferenceGroup screen) {
1169         Intent intent = getIntent();
1170         if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
1171             CameraSettings.removePreferenceFromScreen(screen,
1172                     CameraSettings.KEY_VIDEO_QUALITY);
1173         }
1174 
1175         if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
1176             CameraSettings.removePreferenceFromScreen(screen,
1177                     CameraSettings.KEY_VIDEO_QUALITY);
1178         }
1179         return screen;
1180     }
1181 
1182     // from MediaRecorder.OnErrorListener
onError(MediaRecorder mr, int what, int extra)1183     public void onError(MediaRecorder mr, int what, int extra) {
1184         if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1185             // We may have run out of space on the sdcard.
1186             stopVideoRecording();
1187             updateAndShowStorageHint(true);
1188         }
1189     }
1190 
1191     // from MediaRecorder.OnInfoListener
onInfo(MediaRecorder mr, int what, int extra)1192     public void onInfo(MediaRecorder mr, int what, int extra) {
1193         if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
1194             if (mMediaRecorderRecording) onStopVideoRecording(true);
1195         } else if (what
1196                 == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
1197             if (mMediaRecorderRecording) onStopVideoRecording(true);
1198 
1199             // Show the toast.
1200             Toast.makeText(VideoCamera.this, R.string.video_reach_size_limit,
1201                            Toast.LENGTH_LONG).show();
1202         }
1203     }
1204 
1205     /*
1206      * Make sure we're not recording music playing in the background, ask the
1207      * MediaPlaybackService to pause playback.
1208      */
pauseAudioPlayback()1209     private void pauseAudioPlayback() {
1210         // Shamelessly copied from MediaPlaybackService.java, which
1211         // should be public, but isn't.
1212         Intent i = new Intent("com.android.music.musicservicecommand");
1213         i.putExtra("command", "pause");
1214 
1215         sendBroadcast(i);
1216     }
1217 
startVideoRecording()1218     private void startVideoRecording() {
1219         Log.v(TAG, "startVideoRecording");
1220         if (mStorageStatus != STORAGE_STATUS_OK) {
1221             Log.v(TAG, "Storage issue, ignore the start request");
1222             return;
1223         }
1224 
1225         initializeRecorder();
1226         if (mMediaRecorder == null) {
1227             Log.e(TAG, "Fail to initialize media recorder");
1228             return;
1229         }
1230 
1231         pauseAudioPlayback();
1232 
1233         try {
1234             mMediaRecorder.start(); // Recording is now started
1235         } catch (RuntimeException e) {
1236             Log.e(TAG, "Could not start media recorder. ", e);
1237             releaseMediaRecorder();
1238             return;
1239         }
1240         mHeadUpDisplay.setEnabled(false);
1241 
1242         mMediaRecorderRecording = true;
1243         mRecordingStartTime = SystemClock.uptimeMillis();
1244         updateRecordingIndicator(false);
1245         // Rotate the recording time.
1246         mRecordingTimeRect.setOrientation(mOrientationCompensation);
1247         mRecordingTimeView.setText("");
1248         mRecordingTimeView.setVisibility(View.VISIBLE);
1249         updateRecordingTime();
1250         keepScreenOn();
1251     }
1252 
updateRecordingIndicator(boolean showRecording)1253     private void updateRecordingIndicator(boolean showRecording) {
1254         int drawableId =
1255                 showRecording ? R.drawable.btn_ic_video_record
1256                         : R.drawable.btn_ic_video_record_stop;
1257         Drawable drawable = getResources().getDrawable(drawableId);
1258         mShutterButton.setImageDrawable(drawable);
1259     }
1260 
stopVideoRecordingAndGetThumbnail()1261     private void stopVideoRecordingAndGetThumbnail() {
1262         stopVideoRecording();
1263         acquireVideoThumb();
1264     }
1265 
stopVideoRecordingAndReturn(boolean valid)1266     private void stopVideoRecordingAndReturn(boolean valid) {
1267         stopVideoRecording();
1268         doReturnToCaller(valid);
1269     }
1270 
stopVideoRecordingAndShowAlert()1271     private void stopVideoRecordingAndShowAlert() {
1272         stopVideoRecording();
1273         showAlert();
1274     }
1275 
showAlert()1276     private void showAlert() {
1277         fadeOut(findViewById(R.id.shutter_button));
1278         if (mCurrentVideoFilename != null) {
1279             Bitmap src = ThumbnailUtils.createVideoThumbnail(
1280                     mCurrentVideoFilename, Video.Thumbnails.MINI_KIND);
1281             // MetadataRetriever already rotates the thumbnail. We should rotate
1282             // it back (and mirror if it is front-facing camera).
1283             CameraInfo[] info = CameraHolder.instance().getCameraInfo();
1284             if (info[mCameraId].facing == CameraInfo.CAMERA_FACING_BACK) {
1285                 src = Util.rotateAndMirror(src, -mOrientationHint, false);
1286             } else {
1287                 src = Util.rotateAndMirror(src, -mOrientationHint, true);
1288             }
1289             mVideoFrame.setImageBitmap(src);
1290             mVideoFrame.setVisibility(View.VISIBLE);
1291         }
1292         int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play};
1293         for (int id : pickIds) {
1294             View button = findViewById(id);
1295             fadeIn(((View) button.getParent()));
1296         }
1297     }
1298 
hideAlert()1299     private void hideAlert() {
1300         mVideoFrame.setVisibility(View.INVISIBLE);
1301         fadeIn(findViewById(R.id.shutter_button));
1302         int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play};
1303         for (int id : pickIds) {
1304             View button = findViewById(id);
1305             fadeOut(((View) button.getParent()));
1306         }
1307     }
1308 
fadeIn(View view)1309     private static void fadeIn(View view) {
1310         view.setVisibility(View.VISIBLE);
1311         Animation animation = new AlphaAnimation(0F, 1F);
1312         animation.setDuration(500);
1313         view.startAnimation(animation);
1314     }
1315 
fadeOut(View view)1316     private static void fadeOut(View view) {
1317         view.setVisibility(View.INVISIBLE);
1318         Animation animation = new AlphaAnimation(1F, 0F);
1319         animation.setDuration(500);
1320         view.startAnimation(animation);
1321     }
1322 
isAlertVisible()1323     private boolean isAlertVisible() {
1324         return this.mVideoFrame.getVisibility() == View.VISIBLE;
1325     }
1326 
viewLastVideo()1327     private void viewLastVideo() {
1328         Intent intent = null;
1329         if (mThumbController.isUriValid()) {
1330             intent = new Intent(Util.REVIEW_ACTION, mThumbController.getUri());
1331             try {
1332                 startActivity(intent);
1333             } catch (ActivityNotFoundException ex) {
1334                 try {
1335                     intent = new Intent(Intent.ACTION_VIEW, mThumbController.getUri());
1336                     startActivity(intent);
1337                 } catch (ActivityNotFoundException e) {
1338                     Log.e(TAG, "review video fail", e);
1339                 }
1340             }
1341         } else {
1342             Log.e(TAG, "Can't view last video.");
1343         }
1344     }
1345 
stopVideoRecording()1346     private void stopVideoRecording() {
1347         Log.v(TAG, "stopVideoRecording");
1348         if (mMediaRecorderRecording) {
1349             boolean needToRegisterRecording = false;
1350             mMediaRecorder.setOnErrorListener(null);
1351             mMediaRecorder.setOnInfoListener(null);
1352             try {
1353                 mMediaRecorder.stop();
1354                 mCurrentVideoFilename = mVideoFilename;
1355                 Log.v(TAG, "Setting current video filename: "
1356                         + mCurrentVideoFilename);
1357                 needToRegisterRecording = true;
1358             } catch (RuntimeException e) {
1359                 Log.e(TAG, "stop fail: " + e.getMessage());
1360                 deleteVideoFile(mVideoFilename);
1361             }
1362             mMediaRecorderRecording = false;
1363             mHeadUpDisplay.setEnabled(true);
1364             updateRecordingIndicator(true);
1365             mRecordingTimeView.setVisibility(View.GONE);
1366             keepScreenOnAwhile();
1367             if (needToRegisterRecording && mStorageStatus == STORAGE_STATUS_OK) {
1368                 registerVideo();
1369             }
1370             mVideoFilename = null;
1371             mVideoFileDescriptor = null;
1372         }
1373         releaseMediaRecorder();  // always release media recorder
1374     }
1375 
resetScreenOn()1376     private void resetScreenOn() {
1377         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1378         getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1379     }
1380 
keepScreenOnAwhile()1381     private void keepScreenOnAwhile() {
1382         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1383         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1384         mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1385     }
1386 
keepScreenOn()1387     private void keepScreenOn() {
1388         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1389         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1390     }
1391 
acquireVideoThumb()1392     private void acquireVideoThumb() {
1393         Bitmap videoFrame = ThumbnailUtils.createVideoThumbnail(
1394                 mCurrentVideoFilename, Video.Thumbnails.MINI_KIND);
1395         mThumbController.setData(mCurrentVideoUri, videoFrame);
1396         mThumbController.updateDisplayIfNeeded();
1397     }
1398 
dataLocation()1399     private static ImageManager.DataLocation dataLocation() {
1400         return ImageManager.DataLocation.EXTERNAL;
1401     }
1402 
updateThumbnailButton()1403     private void updateThumbnailButton() {
1404         // Update the last video thumbnail.
1405         if (!mIsVideoCaptureIntent) {
1406             if (!mThumbController.isUriValid()) {
1407                 updateLastVideo();
1408             }
1409             mThumbController.updateDisplayIfNeeded();
1410         }
1411     }
1412 
updateLastVideo()1413     private void updateLastVideo() {
1414         IImageList list = ImageManager.makeImageList(
1415                         mContentResolver,
1416                         dataLocation(),
1417                         ImageManager.INCLUDE_VIDEOS,
1418                         ImageManager.SORT_ASCENDING,
1419                         ImageManager.CAMERA_IMAGE_BUCKET_ID);
1420         int count = list.getCount();
1421         if (count > 0) {
1422             IImage image = list.getImageAt(count - 1);
1423             Uri uri = image.fullSizeImageUri();
1424             mThumbController.setData(uri, image.miniThumbBitmap());
1425         } else {
1426             mThumbController.setData(null, null);
1427         }
1428         list.close();
1429     }
1430 
updateRecordingTime()1431     private void updateRecordingTime() {
1432         if (!mMediaRecorderRecording) {
1433             return;
1434         }
1435         long now = SystemClock.uptimeMillis();
1436         long delta = now - mRecordingStartTime;
1437 
1438         // Starting a minute before reaching the max duration
1439         // limit, we'll countdown the remaining time instead.
1440         boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1441                 && delta >= mMaxVideoDurationInMs - 60000);
1442 
1443         long next_update_delay = 1000 - (delta % 1000);
1444         long seconds;
1445         if (countdownRemainingTime) {
1446             delta = Math.max(0, mMaxVideoDurationInMs - delta);
1447             seconds = (delta + 999) / 1000;
1448         } else {
1449             seconds = delta / 1000; // round to nearest
1450         }
1451 
1452         long minutes = seconds / 60;
1453         long hours = minutes / 60;
1454         long remainderMinutes = minutes - (hours * 60);
1455         long remainderSeconds = seconds - (minutes * 60);
1456 
1457         String secondsString = Long.toString(remainderSeconds);
1458         if (secondsString.length() < 2) {
1459             secondsString = "0" + secondsString;
1460         }
1461         String minutesString = Long.toString(remainderMinutes);
1462         if (minutesString.length() < 2) {
1463             minutesString = "0" + minutesString;
1464         }
1465         String text = minutesString + ":" + secondsString;
1466         if (hours > 0) {
1467             String hoursString = Long.toString(hours);
1468             if (hoursString.length() < 2) {
1469                 hoursString = "0" + hoursString;
1470             }
1471             text = hoursString + ":" + text;
1472         }
1473         mRecordingTimeView.setText(text);
1474 
1475         if (mRecordingTimeCountsDown != countdownRemainingTime) {
1476             // Avoid setting the color on every update, do it only
1477             // when it needs changing.
1478             mRecordingTimeCountsDown = countdownRemainingTime;
1479 
1480             int color = getResources().getColor(countdownRemainingTime
1481                     ? R.color.recording_time_remaining_text
1482                     : R.color.recording_time_elapsed_text);
1483 
1484             mRecordingTimeView.setTextColor(color);
1485         }
1486 
1487         mHandler.sendEmptyMessageDelayed(
1488                 UPDATE_RECORD_TIME, next_update_delay);
1489     }
1490 
isSupported(String value, List<String> supported)1491     private static boolean isSupported(String value, List<String> supported) {
1492         return supported == null ? false : supported.indexOf(value) >= 0;
1493     }
1494 
setCameraParameters()1495     private void setCameraParameters() {
1496         mParameters = mCameraDevice.getParameters();
1497 
1498         mParameters.setPreviewSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight);
1499         mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
1500 
1501         // Set flash mode.
1502         String flashMode = mPreferences.getString(
1503                 CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
1504                 getString(R.string.pref_camera_video_flashmode_default));
1505         List<String> supportedFlash = mParameters.getSupportedFlashModes();
1506         if (isSupported(flashMode, supportedFlash)) {
1507             mParameters.setFlashMode(flashMode);
1508         } else {
1509             flashMode = mParameters.getFlashMode();
1510             if (flashMode == null) {
1511                 flashMode = getString(
1512                         R.string.pref_camera_flashmode_no_flash);
1513             }
1514         }
1515 
1516         // Set white balance parameter.
1517         String whiteBalance = mPreferences.getString(
1518                 CameraSettings.KEY_WHITE_BALANCE,
1519                 getString(R.string.pref_camera_whitebalance_default));
1520         if (isSupported(whiteBalance,
1521                 mParameters.getSupportedWhiteBalance())) {
1522             mParameters.setWhiteBalance(whiteBalance);
1523         } else {
1524             whiteBalance = mParameters.getWhiteBalance();
1525             if (whiteBalance == null) {
1526                 whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1527             }
1528         }
1529 
1530         // Set color effect parameter.
1531         String colorEffect = mPreferences.getString(
1532                 CameraSettings.KEY_COLOR_EFFECT,
1533                 getString(R.string.pref_camera_coloreffect_default));
1534         if (isSupported(colorEffect, mParameters.getSupportedColorEffects())) {
1535             mParameters.setColorEffect(colorEffect);
1536         }
1537 
1538         mCameraDevice.setParameters(mParameters);
1539         // Keep preview size up to date.
1540         mParameters = mCameraDevice.getParameters();
1541     }
1542 
switchToCameraMode()1543     private boolean switchToCameraMode() {
1544         if (isFinishing() || mMediaRecorderRecording) return false;
1545         MenuHelper.gotoCameraMode(this);
1546         finish();
1547         return true;
1548     }
1549 
onSwitchChanged(Switcher source, boolean onOff)1550     public boolean onSwitchChanged(Switcher source, boolean onOff) {
1551         if (onOff == SWITCH_CAMERA) {
1552             return switchToCameraMode();
1553         } else {
1554             return true;
1555         }
1556     }
1557 
1558     @Override
onConfigurationChanged(Configuration config)1559     public void onConfigurationChanged(Configuration config) {
1560         super.onConfigurationChanged(config);
1561 
1562         // If the camera resumes behind the lock screen, the orientation
1563         // will be portrait. That causes OOM when we try to allocation GPU
1564         // memory for the GLSurfaceView again when the orientation changes. So,
1565         // we delayed initialization of HeadUpDisplay until the orientation
1566         // becomes landscape.
1567         changeHeadUpDisplayState();
1568     }
1569 
resetCameraParameters()1570     private void resetCameraParameters() {
1571         // We need to restart the preview if preview size is changed.
1572         Size size = mParameters.getPreviewSize();
1573         if (size.width != mProfile.videoFrameWidth
1574                 || size.height != mProfile.videoFrameHeight) {
1575             // It is assumed media recorder is released before
1576             // onSharedPreferenceChanged, so we can close the camera here.
1577             closeCamera();
1578             resizeForPreviewAspectRatio();
1579             restartPreview(); // Parameters will be set in startPreview().
1580         } else {
1581             setCameraParameters();
1582         }
1583     }
1584 
onSizeChanged()1585     public void onSizeChanged() {
1586         // TODO: update the content on GLRootView
1587     }
1588 
1589     private class MyHeadUpDisplayListener implements HeadUpDisplay.Listener {
onSharedPreferencesChanged()1590         public void onSharedPreferencesChanged() {
1591             mHandler.post(new Runnable() {
1592                 public void run() {
1593                     VideoCamera.this.onSharedPreferencesChanged();
1594                 }
1595             });
1596         }
1597 
onRestorePreferencesClicked()1598         public void onRestorePreferencesClicked() {
1599             mHandler.post(new Runnable() {
1600                 public void run() {
1601                     VideoCamera.this.onRestorePreferencesClicked();
1602                 }
1603             });
1604         }
1605 
onPopupWindowVisibilityChanged(final int visibility)1606         public void onPopupWindowVisibilityChanged(final int visibility) {
1607         }
1608     }
1609 
onRestorePreferencesClicked()1610     private void onRestorePreferencesClicked() {
1611         Runnable runnable = new Runnable() {
1612             public void run() {
1613                 mHeadUpDisplay.restorePreferences(mParameters);
1614             }
1615         };
1616         MenuHelper.confirmAction(this,
1617                 getString(R.string.confirm_restore_title),
1618                 getString(R.string.confirm_restore_message),
1619                 runnable);
1620     }
1621 
onSharedPreferencesChanged()1622     private void onSharedPreferencesChanged() {
1623         // ignore the events after "onPause()" or preview has not started yet
1624         if (mPausing) return;
1625         synchronized (mPreferences) {
1626             readVideoPreferences();
1627             // If mCameraDevice is not ready then we can set the parameter in
1628             // startPreview().
1629             if (mCameraDevice == null) return;
1630 
1631             // Check if camera id is changed.
1632             int cameraId = CameraSettings.readPreferredCameraId(mPreferences);
1633             if (mCameraId != cameraId) {
1634                 switchCameraId(cameraId);
1635             } else {
1636                 resetCameraParameters();
1637             }
1638         }
1639     }
1640 }
1641