blob: 678520b508178b76ddcfad360d9f7e53ff14c144 [file] [log] [blame]
Michael Kolb8872c232013-01-29 10:33:22 -08001/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera;
18
19import android.annotation.TargetApi;
20import android.app.Activity;
21import android.content.ActivityNotFoundException;
22import android.content.BroadcastReceiver;
23import android.content.ContentResolver;
24import android.content.ContentValues;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.SharedPreferences.Editor;
29import android.content.res.Configuration;
30import android.graphics.Bitmap;
31import android.graphics.SurfaceTexture;
32import android.hardware.Camera.CameraInfo;
33import android.hardware.Camera.Parameters;
Michael Kolb8872c232013-01-29 10:33:22 -080034import android.hardware.Camera.Size;
35import android.location.Location;
36import android.media.CamcorderProfile;
37import android.media.CameraProfile;
38import android.media.MediaRecorder;
39import android.net.Uri;
40import android.os.Build;
41import android.os.Bundle;
42import android.os.Handler;
43import android.os.Message;
44import android.os.ParcelFileDescriptor;
45import android.os.SystemClock;
46import android.provider.MediaStore;
Ruben Brunk16007962013-04-19 15:27:57 -070047import android.provider.MediaStore.MediaColumns;
Michael Kolb8872c232013-01-29 10:33:22 -080048import android.provider.MediaStore.Video;
49import android.util.Log;
Michael Kolb8872c232013-01-29 10:33:22 -080050import android.view.KeyEvent;
Michael Kolb8872c232013-01-29 10:33:22 -080051import android.view.OrientationEventListener;
Michael Kolb8872c232013-01-29 10:33:22 -080052import android.view.View;
Michael Kolb8872c232013-01-29 10:33:22 -080053import android.view.WindowManager;
Michael Kolb8872c232013-01-29 10:33:22 -080054import android.widget.Toast;
55
Angus Kong20fad242013-11-11 18:23:46 -080056import com.android.camera.app.CameraManager.CameraPictureCallback;
57import com.android.camera.app.CameraManager.CameraProxy;
58import com.android.camera.app.AppController;
Angus Kongfd4fc0e2013-11-07 15:38:09 -080059import com.android.camera.app.MediaSaver;
ztenghuia16e7b52013-08-23 11:47:56 -070060import com.android.camera.exif.ExifInterface;
Angus Kong20fad242013-11-11 18:23:46 -080061import com.android.camera.module.ModuleController;
Michael Kolb8872c232013-01-29 10:33:22 -080062import com.android.camera.ui.RotateTextToast;
Sascha Haeberling638e6f02013-09-18 14:28:51 -070063import com.android.camera.util.AccessibilityUtils;
64import com.android.camera.util.ApiHelper;
Angus Kongb50b5cb2013-08-09 14:55:20 -070065import com.android.camera.util.CameraUtil;
Sascha Haeberling8e963a52013-08-06 11:43:02 -070066import com.android.camera.util.UsageStatistics;
67import com.android.camera2.R;
Michael Kolb8872c232013-01-29 10:33:22 -080068
Angus Kongb50b5cb2013-08-09 14:55:20 -070069import java.io.File;
70import java.io.IOException;
71import java.text.SimpleDateFormat;
72import java.util.Date;
73import java.util.Iterator;
74import java.util.List;
75
Angus Kong20fad242013-11-11 18:23:46 -080076public class VideoModule implements CameraModule, ModuleController, VideoController,
77 CameraPreference.OnPreferenceChangedListener, ShutterButton.OnShutterButtonListener,
78 MediaRecorder.OnErrorListener, MediaRecorder.OnInfoListener {
Michael Kolb8872c232013-01-29 10:33:22 -080079
80 private static final String TAG = "CAM_VideoModule";
81
Michael Kolb8872c232013-01-29 10:33:22 -080082 private static final int CHECK_DISPLAY_ROTATION = 3;
83 private static final int CLEAR_SCREEN_DELAY = 4;
84 private static final int UPDATE_RECORD_TIME = 5;
85 private static final int ENABLE_SHUTTER_BUTTON = 6;
86 private static final int SHOW_TAP_TO_SNAPSHOT_TOAST = 7;
87 private static final int SWITCH_CAMERA = 8;
88 private static final int SWITCH_CAMERA_START_ANIMATION = 9;
Michael Kolb8872c232013-01-29 10:33:22 -080089
90 private static final int SCREEN_DELAY = 2 * 60 * 1000;
91
92 private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
93
94 /**
95 * An unpublished intent flag requesting to start recording straight away
96 * and return as soon as recording is stopped.
97 * TODO: consider publishing by moving into MediaStore.
98 */
99 private static final String EXTRA_QUICK_CAPTURE =
100 "android.intent.extra.quickCapture";
101
Michael Kolb8872c232013-01-29 10:33:22 -0800102 // module fields
103 private CameraActivity mActivity;
Michael Kolb8872c232013-01-29 10:33:22 -0800104 private boolean mPaused;
105 private int mCameraId;
106 private Parameters mParameters;
107
Doris Liu6432cd62013-06-13 17:20:31 -0700108 private boolean mIsInReviewMode;
Michael Kolb8872c232013-01-29 10:33:22 -0800109 private boolean mSnapshotInProgress = false;
110
Michael Kolb8872c232013-01-29 10:33:22 -0800111 private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
112
113 private ComboPreferences mPreferences;
114 private PreferenceGroup mPreferenceGroup;
Angus Kong395ee2d2013-07-15 12:42:41 -0700115 // Preference must be read before starting preview. We check this before starting
116 // preview.
117 private boolean mPreferenceRead;
Michael Kolb8872c232013-01-29 10:33:22 -0800118
Michael Kolb8872c232013-01-29 10:33:22 -0800119 private boolean mIsVideoCaptureIntent;
120 private boolean mQuickCapture;
121
122 private MediaRecorder mMediaRecorder;
Michael Kolb8872c232013-01-29 10:33:22 -0800123
124 private boolean mSwitchingCamera;
125 private boolean mMediaRecorderRecording = false;
126 private long mRecordingStartTime;
127 private boolean mRecordingTimeCountsDown = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800128 private long mOnResumeTime;
129 // The video file that the hardware camera is about to record into
130 // (or is recording into.)
131 private String mVideoFilename;
132 private ParcelFileDescriptor mVideoFileDescriptor;
133
134 // The video file that has already been recorded, and that is being
135 // examined by the user.
136 private String mCurrentVideoFilename;
137 private Uri mCurrentVideoUri;
ztenghui70bd0242013-10-14 11:15:44 -0700138 private boolean mCurrentVideoUriFromMediaSaved;
Michael Kolb8872c232013-01-29 10:33:22 -0800139 private ContentValues mCurrentVideoValues;
140
141 private CamcorderProfile mProfile;
142
143 // The video duration limit. 0 menas no limit.
144 private int mMaxVideoDurationInMs;
145
146 // Time Lapse parameters.
147 private boolean mCaptureTimeLapse = false;
148 // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
149 private int mTimeBetweenTimeLapseFrameCaptureMs = 0;
Michael Kolb8872c232013-01-29 10:33:22 -0800150
151 boolean mPreviewing = false; // True if preview is started.
152 // The display rotation in degrees. This is only valid when mPreviewing is
153 // true.
154 private int mDisplayRotation;
155 private int mCameraDisplayOrientation;
156
Doris Liu6827ce22013-03-12 19:24:28 -0700157 private int mDesiredPreviewWidth;
158 private int mDesiredPreviewHeight;
Michael Kolb8872c232013-01-29 10:33:22 -0800159 private ContentResolver mContentResolver;
160
161 private LocationManager mLocationManager;
162
Michael Kolb8872c232013-01-29 10:33:22 -0800163 private int mPendingSwitchCameraId;
Michael Kolb8872c232013-01-29 10:33:22 -0800164 private final Handler mHandler = new MainHandler();
Doris Liu6827ce22013-03-12 19:24:28 -0700165 private VideoUI mUI;
Doris Liu6432cd62013-06-13 17:20:31 -0700166 private CameraProxy mCameraDevice;
167
Michael Kolb8872c232013-01-29 10:33:22 -0800168 // The degrees of the device rotated clockwise from its natural orientation.
169 private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
170
171 private int mZoomValue; // The current zoom value.
Doris Liu6827ce22013-03-12 19:24:28 -0700172
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800173 private final MediaSaver.OnMediaSavedListener mOnVideoSavedListener =
174 new MediaSaver.OnMediaSavedListener() {
Angus Kong83a99ae2013-04-17 15:37:07 -0700175 @Override
176 public void onMediaSaved(Uri uri) {
177 if (uri != null) {
Doris Liu2a7f44c2013-08-12 15:18:53 -0700178 mCurrentVideoUri = uri;
ztenghui70bd0242013-10-14 11:15:44 -0700179 mCurrentVideoUriFromMediaSaved = true;
Doris Liu2a7f44c2013-08-12 15:18:53 -0700180 onVideoSaved();
Sascha Haeberling37f36112013-08-06 14:31:52 -0700181 mActivity.notifyNewMedia(uri);
Angus Kong83a99ae2013-04-17 15:37:07 -0700182 }
183 }
184 };
185
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800186 private final MediaSaver.OnMediaSavedListener mOnPhotoSavedListener =
187 new MediaSaver.OnMediaSavedListener() {
Angus Kong86d36312013-01-31 18:22:44 -0800188 @Override
189 public void onMediaSaved(Uri uri) {
190 if (uri != null) {
Sascha Haeberling37f36112013-08-06 14:31:52 -0700191 mActivity.notifyNewMedia(uri);
Angus Kong86d36312013-01-31 18:22:44 -0800192 }
193 }
194 };
195
Michael Kolb8872c232013-01-29 10:33:22 -0800196 private void openCamera() {
Angus Kong4f795b82013-09-16 14:25:35 -0700197 if (mCameraDevice == null) {
198 mCameraDevice = CameraUtil.openCamera(
199 mActivity, mCameraId, mHandler,
200 mActivity.getCameraOpenErrorCallback());
Michael Kolb8872c232013-01-29 10:33:22 -0800201 }
Angus Kong4f795b82013-09-16 14:25:35 -0700202 if (mCameraDevice == null) {
203 // Error.
204 return;
205 }
206 mParameters = mCameraDevice.getParameters();
Michael Kolb8872c232013-01-29 10:33:22 -0800207 }
208
209 // This Handler is used to post message back onto the main thread of the
210 // application
211 private class MainHandler extends Handler {
212 @Override
213 public void handleMessage(Message msg) {
214 switch (msg.what) {
215
216 case ENABLE_SHUTTER_BUTTON:
Doris Liu6827ce22013-03-12 19:24:28 -0700217 mUI.enableShutter(true);
Michael Kolb8872c232013-01-29 10:33:22 -0800218 break;
219
220 case CLEAR_SCREEN_DELAY: {
221 mActivity.getWindow().clearFlags(
222 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
223 break;
224 }
225
226 case UPDATE_RECORD_TIME: {
227 updateRecordingTime();
228 break;
229 }
230
231 case CHECK_DISPLAY_ROTATION: {
232 // Restart the preview if display rotation has changed.
233 // Sometimes this happens when the device is held upside
234 // down and camera app is opened. Rotation animation will
235 // take some time and the rotation value we have got may be
236 // wrong. Framework does not have a callback for this now.
Angus Kongb50b5cb2013-08-09 14:55:20 -0700237 if ((CameraUtil.getDisplayRotation(mActivity) != mDisplayRotation)
Michael Kolb8872c232013-01-29 10:33:22 -0800238 && !mMediaRecorderRecording && !mSwitchingCamera) {
239 startPreview();
240 }
241 if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
242 mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
243 }
244 break;
245 }
246
247 case SHOW_TAP_TO_SNAPSHOT_TOAST: {
248 showTapToSnapshotToast();
249 break;
250 }
251
252 case SWITCH_CAMERA: {
253 switchCamera();
254 break;
255 }
256
257 case SWITCH_CAMERA_START_ANIMATION: {
Doris Liu6432cd62013-06-13 17:20:31 -0700258 //TODO:
259 //((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
Michael Kolb8872c232013-01-29 10:33:22 -0800260
261 // Enable all camera controls.
262 mSwitchingCamera = false;
263 break;
264 }
265
Michael Kolb8872c232013-01-29 10:33:22 -0800266 default:
267 Log.v(TAG, "Unhandled message: " + msg.what);
268 break;
269 }
270 }
271 }
272
273 private BroadcastReceiver mReceiver = null;
274
275 private class MyBroadcastReceiver extends BroadcastReceiver {
276 @Override
277 public void onReceive(Context context, Intent intent) {
278 String action = intent.getAction();
279 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
280 stopVideoRecording();
281 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
282 Toast.makeText(mActivity,
283 mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
284 }
285 }
286 }
287
288 private String createName(long dateTaken) {
289 Date date = new Date(dateTaken);
290 SimpleDateFormat dateFormat = new SimpleDateFormat(
291 mActivity.getString(R.string.video_file_name_format));
292
293 return dateFormat.format(date);
294 }
295
296 private int getPreferredCameraId(ComboPreferences preferences) {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700297 int intentCameraId = CameraUtil.getCameraFacingIntentExtras(mActivity);
Michael Kolb8872c232013-01-29 10:33:22 -0800298 if (intentCameraId != -1) {
299 // Testing purpose. Launch a specific camera through the intent
300 // extras.
301 return intentCameraId;
302 } else {
303 return CameraSettings.readPreferredCameraId(preferences);
304 }
305 }
306
307 private void initializeSurfaceView() {
Doris Liu6827ce22013-03-12 19:24:28 -0700308 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { // API level < 16
Doris Liu6432cd62013-06-13 17:20:31 -0700309 mUI.initializeSurfaceView();
Michael Kolb8872c232013-01-29 10:33:22 -0800310 }
311 }
312
313 @Override
Doris Liu6432cd62013-06-13 17:20:31 -0700314 public void init(CameraActivity activity, View root) {
Michael Kolb8872c232013-01-29 10:33:22 -0800315 mActivity = activity;
Doris Liu6827ce22013-03-12 19:24:28 -0700316 mUI = new VideoUI(activity, this, root);
Michael Kolb8872c232013-01-29 10:33:22 -0800317 mPreferences = new ComboPreferences(mActivity);
318 CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
319 mCameraId = getPreferredCameraId(mPreferences);
320
321 mPreferences.setLocalId(mActivity, mCameraId);
322 CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
323
Michael Kolb8872c232013-01-29 10:33:22 -0800324 /*
325 * To reduce startup time, we start the preview in another thread.
326 * We make sure the preview is started at the end of onCreate.
327 */
Angus Kong20fad242013-11-11 18:23:46 -0800328 requestCamera(mCameraId);
Michael Kolb8872c232013-01-29 10:33:22 -0800329
330 mContentResolver = mActivity.getContentResolver();
331
Michael Kolb8872c232013-01-29 10:33:22 -0800332 // Surface texture is from camera screen nail and startPreview needs it.
333 // This must be done before startPreview.
334 mIsVideoCaptureIntent = isVideoCaptureIntent();
Michael Kolb8872c232013-01-29 10:33:22 -0800335 initializeSurfaceView();
336
Doris Liu6827ce22013-03-12 19:24:28 -0700337 mUI.setPrefChangedListener(this);
Michael Kolb8872c232013-01-29 10:33:22 -0800338
Michael Kolb8872c232013-01-29 10:33:22 -0800339 mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
340 mLocationManager = new LocationManager(mActivity, null);
341
Doris Liu6827ce22013-03-12 19:24:28 -0700342 mUI.setOrientationIndicator(0, false);
Michael Kolb8872c232013-01-29 10:33:22 -0800343 setDisplayOrientation();
344
Doris Liu6827ce22013-03-12 19:24:28 -0700345 mUI.showTimeLapseUI(mCaptureTimeLapse);
Michael Kolb8872c232013-01-29 10:33:22 -0800346 mPendingSwitchCameraId = -1;
Doris Liu6827ce22013-03-12 19:24:28 -0700347 }
348
349 // SingleTapListener
350 // Preview area is touched. Take a picture.
351 @Override
352 public void onSingleTapUp(View view, int x, int y) {
Doris Liu38605742013-08-13 15:01:52 -0700353 takeASnapshot();
354 }
355
356 private void takeASnapshot() {
Sascha Haeberlinga514b142013-08-13 16:10:01 -0700357 // Only take snapshots if video snapshot is supported by device
Doris Liu38605742013-08-13 15:01:52 -0700358 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
Doris Liu3973deb2013-08-21 13:42:22 -0700359 if (!mMediaRecorderRecording || mPaused || mSnapshotInProgress) {
Sascha Haeberlinga514b142013-08-13 16:10:01 -0700360 return;
361 }
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800362 MediaSaver s = mActivity.getMediaSaver();
Sascha Haeberlinga514b142013-08-13 16:10:01 -0700363 if (s == null || s.isQueueFull()) {
Doris Liu38605742013-08-13 15:01:52 -0700364 return;
365 }
366
367 // Set rotation and gps data.
Angus Kong20fad242013-11-11 18:23:46 -0800368 int rotation = CameraUtil.getJpegRotation(mActivity, mCameraId, mOrientation);
Doris Liu38605742013-08-13 15:01:52 -0700369 mParameters.setRotation(rotation);
370 Location loc = mLocationManager.getCurrentLocation();
371 CameraUtil.setGpsParameters(mParameters, loc);
372 mCameraDevice.setParameters(mParameters);
373
374 Log.v(TAG, "Video snapshot start");
375 mCameraDevice.takePicture(mHandler,
376 null, null, null, new JpegPictureCallback(loc));
377 showVideoSnapshotUI(true);
378 mSnapshotInProgress = true;
379 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
380 UsageStatistics.ACTION_CAPTURE_DONE, "VideoSnapshot");
Doris Liu6827ce22013-03-12 19:24:28 -0700381 }
Michael Kolb8872c232013-01-29 10:33:22 -0800382 }
383
384 @Override
385 public void onStop() {}
386
387 private void loadCameraPreferences() {
388 CameraSettings settings = new CameraSettings(mActivity, mParameters,
Angus Kong20fad242013-11-11 18:23:46 -0800389 mCameraId, mActivity.getCameraProvider().getCameraInfo());
Michael Kolb8872c232013-01-29 10:33:22 -0800390 // Remove the video quality preference setting when the quality is given in the intent.
391 mPreferenceGroup = filterPreferenceScreenByIntent(
392 settings.getPreferenceGroup(R.xml.video_preferences));
393 }
394
Michael Kolb8872c232013-01-29 10:33:22 -0800395 private void initializeVideoControl() {
396 loadCameraPreferences();
Doris Liu6827ce22013-03-12 19:24:28 -0700397 mUI.initializePopup(mPreferenceGroup);
Michael Kolb8872c232013-01-29 10:33:22 -0800398 }
399
Michael Kolb8872c232013-01-29 10:33:22 -0800400 @Override
401 public void onOrientationChanged(int orientation) {
402 // We keep the last known orientation. So if the user first orient
403 // the camera then point the camera to floor or sky, we still have
404 // the correct orientation.
405 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return;
Angus Kongb50b5cb2013-08-09 14:55:20 -0700406 int newOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800407
408 if (mOrientation != newOrientation) {
409 mOrientation = newOrientation;
Michael Kolb8872c232013-01-29 10:33:22 -0800410 }
411
412 // Show the toast after getting the first orientation changed.
413 if (mHandler.hasMessages(SHOW_TAP_TO_SNAPSHOT_TOAST)) {
414 mHandler.removeMessages(SHOW_TAP_TO_SNAPSHOT_TOAST);
415 showTapToSnapshotToast();
416 }
417 }
418
Angus Kong20fad242013-11-11 18:23:46 -0800419 @Override
420 public void onCameraAvailable(CameraProxy cameraProxy) {
421 mCameraDevice = cameraProxy;
422 readVideoPreferences();
423 resizeForPreviewAspectRatio();
424 startPreview();
425 initializeVideoSnapshot();
426 initializeVideoControl();
427 mUI.initializeZoom(mParameters);
428 }
429
Michael Kolb8872c232013-01-29 10:33:22 -0800430 private void startPlayVideoActivity() {
431 Intent intent = new Intent(Intent.ACTION_VIEW);
432 intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
433 try {
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700434 mActivity
435 .startActivityForResult(intent, CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
Michael Kolb8872c232013-01-29 10:33:22 -0800436 } catch (ActivityNotFoundException ex) {
437 Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
438 }
439 }
440
ztenghui7b265a62013-09-09 14:58:44 -0700441 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800442 @OnClickAttr
443 public void onReviewPlayClicked(View v) {
444 startPlayVideoActivity();
445 }
446
ztenghui7b265a62013-09-09 14:58:44 -0700447 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800448 @OnClickAttr
449 public void onReviewDoneClicked(View v) {
Doris Liu69ef5ea2013-05-07 13:48:10 -0700450 mIsInReviewMode = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800451 doReturnToCaller(true);
452 }
453
ztenghui7b265a62013-09-09 14:58:44 -0700454 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800455 @OnClickAttr
456 public void onReviewCancelClicked(View v) {
ztenghuiaf3a1972013-09-17 16:51:15 -0700457 // TODO: It should be better to not even insert the URI at all before we
458 // confirm done in review, which means we need to handle temporary video
459 // files in a quite different way than we currently had.
ztenghui70bd0242013-10-14 11:15:44 -0700460 // Make sure we don't delete the Uri sent from the video capture intent.
461 if (mCurrentVideoUriFromMediaSaved) {
ztenghuidfe5b152013-10-08 17:07:15 -0700462 mContentResolver.delete(mCurrentVideoUri, null, null);
463 }
ztenghui638bf9a2013-10-09 10:52:33 -0700464 mIsInReviewMode = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800465 doReturnToCaller(false);
466 }
467
Doris Liu69ef5ea2013-05-07 13:48:10 -0700468 @Override
469 public boolean isInReviewMode() {
470 return mIsInReviewMode;
471 }
472
Michael Kolb8872c232013-01-29 10:33:22 -0800473 private void onStopVideoRecording() {
Michael Kolb8872c232013-01-29 10:33:22 -0800474 boolean recordFail = stopVideoRecording();
475 if (mIsVideoCaptureIntent) {
Doris Liu3973deb2013-08-21 13:42:22 -0700476 if (mQuickCapture) {
477 doReturnToCaller(!recordFail);
478 } else if (!recordFail) {
479 showCaptureResult();
Michael Kolb8872c232013-01-29 10:33:22 -0800480 }
481 } else if (!recordFail){
482 // Start capture animation.
483 if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
484 // The capture animation is disabled on ICS because we use SurfaceView
485 // for preview during recording. When the recording is done, we switch
486 // back to use SurfaceTexture for preview and we need to stop then start
487 // the preview. This will cause the preview flicker since the preview
488 // will not be continuous for a short period of time.
Sascha Haeberling4f91ab52013-05-21 11:26:13 -0700489
Sascha Haeberling37f36112013-08-06 14:31:52 -0700490 mUI.animateFlash();
Doris Liu3973deb2013-08-21 13:42:22 -0700491 mUI.animateCapture();
Michael Kolb8872c232013-01-29 10:33:22 -0800492 }
493 }
494 }
495
Doris Liu2a7f44c2013-08-12 15:18:53 -0700496 public void onVideoSaved() {
497 if (mIsVideoCaptureIntent) {
498 showCaptureResult();
499 }
500 }
501
Michael Kolb8872c232013-01-29 10:33:22 -0800502 public void onProtectiveCurtainClick(View v) {
503 // Consume clicks
504 }
505
506 @Override
507 public void onShutterButtonClick() {
Doris Liu6827ce22013-03-12 19:24:28 -0700508 if (mUI.collapseCameraControls() || mSwitchingCamera) return;
Michael Kolb8872c232013-01-29 10:33:22 -0800509
510 boolean stop = mMediaRecorderRecording;
511
512 if (stop) {
513 onStopVideoRecording();
514 } else {
515 startVideoRecording();
516 }
Doris Liu6827ce22013-03-12 19:24:28 -0700517 mUI.enableShutter(false);
Michael Kolb8872c232013-01-29 10:33:22 -0800518
519 // Keep the shutter button disabled when in video capture intent
520 // mode and recording is stopped. It'll be re-enabled when
521 // re-take button is clicked.
522 if (!(mIsVideoCaptureIntent && stop)) {
523 mHandler.sendEmptyMessageDelayed(
524 ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
525 }
526 }
527
528 @Override
529 public void onShutterButtonFocus(boolean pressed) {
Doris Liu61f2b082013-03-27 17:25:43 -0700530 mUI.setShutterPressed(pressed);
Michael Kolb8872c232013-01-29 10:33:22 -0800531 }
532
533 private void readVideoPreferences() {
534 // The preference stores values from ListPreference and is thus string type for all values.
535 // We need to convert it to int manually.
Doris Liu3f7e0042013-07-31 11:25:09 -0700536 String videoQuality = mPreferences.getString(CameraSettings.KEY_VIDEO_QUALITY,
537 null);
538 if (videoQuality == null) {
539 // check for highest quality before setting default value
540 videoQuality = CameraSettings.getSupportedHighestVideoQuality(mCameraId,
541 mActivity.getResources().getString(R.string.pref_video_quality_default));
542 mPreferences.edit().putString(CameraSettings.KEY_VIDEO_QUALITY, videoQuality);
543 }
Michael Kolb8872c232013-01-29 10:33:22 -0800544 int quality = Integer.valueOf(videoQuality);
545
546 // Set video quality.
547 Intent intent = mActivity.getIntent();
548 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
549 int extraVideoQuality =
550 intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
551 if (extraVideoQuality > 0) {
552 quality = CamcorderProfile.QUALITY_HIGH;
553 } else { // 0 is mms.
554 quality = CamcorderProfile.QUALITY_LOW;
555 }
556 }
557
558 // Set video duration limit. The limit is read from the preference,
559 // unless it is specified in the intent.
560 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
561 int seconds =
562 intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
563 mMaxVideoDurationInMs = 1000 * seconds;
564 } else {
565 mMaxVideoDurationInMs = CameraSettings.getMaxVideoDuration(mActivity);
566 }
567
Michael Kolb8872c232013-01-29 10:33:22 -0800568 // Read time lapse recording interval.
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700569 String frameIntervalStr = mPreferences.getString(
570 CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
571 mActivity.getString(R.string.pref_video_time_lapse_frame_interval_default));
572 mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
573 mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
Michael Kolb8872c232013-01-29 10:33:22 -0800574 // TODO: This should be checked instead directly +1000.
575 if (mCaptureTimeLapse) quality += 1000;
576 mProfile = CamcorderProfile.get(mCameraId, quality);
577 getDesiredPreviewSize();
Angus Kong395ee2d2013-07-15 12:42:41 -0700578 mPreferenceRead = true;
Michael Kolb8872c232013-01-29 10:33:22 -0800579 }
580
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700581 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
Michael Kolb8872c232013-01-29 10:33:22 -0800582 private void getDesiredPreviewSize() {
ztenghuief01a312013-10-14 15:25:16 -0700583 if (mCameraDevice == null) {
584 return;
585 }
Doris Liu6432cd62013-06-13 17:20:31 -0700586 mParameters = mCameraDevice.getParameters();
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700587 if (mParameters.getSupportedVideoSizes() == null) {
Michael Kolb8872c232013-01-29 10:33:22 -0800588 mDesiredPreviewWidth = mProfile.videoFrameWidth;
589 mDesiredPreviewHeight = mProfile.videoFrameHeight;
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700590 } else { // Driver supports separates outputs for preview and video.
591 List<Size> sizes = mParameters.getSupportedPreviewSizes();
592 Size preferred = mParameters.getPreferredPreviewSizeForVideo();
593 int product = preferred.width * preferred.height;
594 Iterator<Size> it = sizes.iterator();
595 // Remove the preview sizes that are not preferred.
596 while (it.hasNext()) {
597 Size size = it.next();
598 if (size.width * size.height > product) {
599 it.remove();
600 }
601 }
602 Size optimalSize = CameraUtil.getOptimalPreviewSize(mActivity, sizes,
603 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
604 mDesiredPreviewWidth = optimalSize.width;
605 mDesiredPreviewHeight = optimalSize.height;
Michael Kolb8872c232013-01-29 10:33:22 -0800606 }
Doris Liu6432cd62013-06-13 17:20:31 -0700607 mUI.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800608 Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
609 ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
610 }
611
612 private void resizeForPreviewAspectRatio() {
Doris Liu6827ce22013-03-12 19:24:28 -0700613 mUI.setAspectRatio(
Michael Kolb8872c232013-01-29 10:33:22 -0800614 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
615 }
616
617 @Override
618 public void installIntentFilter() {
619 // install an intent filter to receive SD card related events.
620 IntentFilter intentFilter =
621 new IntentFilter(Intent.ACTION_MEDIA_EJECT);
622 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
623 intentFilter.addDataScheme("file");
624 mReceiver = new MyBroadcastReceiver();
625 mActivity.registerReceiver(mReceiver, intentFilter);
626 }
627
628 @Override
629 public void onResumeBeforeSuper() {
630 mPaused = false;
631 }
632
633 @Override
634 public void onResumeAfterSuper() {
Doris Liu6827ce22013-03-12 19:24:28 -0700635 mUI.enableShutter(false);
Michael Kolb8872c232013-01-29 10:33:22 -0800636 mZoomValue = 0;
637
638 showVideoSnapshotUI(false);
639
Michael Kolb8872c232013-01-29 10:33:22 -0800640 if (!mPreviewing) {
Angus Kong20fad242013-11-11 18:23:46 -0800641 requestCamera(mCameraId);
Doris Liuc774ff92013-03-20 19:25:47 -0700642 } else {
643 // preview already started
644 mUI.enableShutter(true);
Michael Kolb8872c232013-01-29 10:33:22 -0800645 }
646
Doris Liu59390062013-10-10 17:20:56 -0700647 mUI.initDisplayChangeListener();
Michael Kolb8872c232013-01-29 10:33:22 -0800648
649 keepScreenOnAwhile();
650
651 // Initialize location service.
652 boolean recordLocation = RecordLocationPreference.get(mPreferences,
653 mContentResolver);
654 mLocationManager.recordLocation(recordLocation);
655
656 if (mPreviewing) {
657 mOnResumeTime = SystemClock.uptimeMillis();
658 mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
659 }
Michael Kolb8872c232013-01-29 10:33:22 -0800660
Bobby Georgescu0a7dd572013-03-12 22:45:17 -0700661 UsageStatistics.onContentViewChanged(
662 UsageStatistics.COMPONENT_CAMERA, "VideoModule");
Michael Kolb8872c232013-01-29 10:33:22 -0800663 }
664
665 private void setDisplayOrientation() {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700666 mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
667 mCameraDisplayOrientation = CameraUtil.getDisplayOrientation(mDisplayRotation, mCameraId);
Doris Liu6432cd62013-06-13 17:20:31 -0700668 // Change the camera display orientation
669 if (mCameraDevice != null) {
670 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800671 }
Doris Liu6432cd62013-06-13 17:20:31 -0700672 }
673
674 @Override
675 public void updateCameraOrientation() {
676 if (mMediaRecorderRecording) return;
Angus Kongb50b5cb2013-08-09 14:55:20 -0700677 if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
Doris Liu6432cd62013-06-13 17:20:31 -0700678 setDisplayOrientation();
679 }
Michael Kolb8872c232013-01-29 10:33:22 -0800680 }
681
Doris Liu6827ce22013-03-12 19:24:28 -0700682 @Override
683 public int onZoomChanged(int index) {
684 // Not useful to change zoom value when the activity is paused.
685 if (mPaused) return index;
686 mZoomValue = index;
Doris Liu6432cd62013-06-13 17:20:31 -0700687 if (mParameters == null || mCameraDevice == null) return index;
Doris Liu6827ce22013-03-12 19:24:28 -0700688 // Set zoom parameters asynchronously
689 mParameters.setZoom(mZoomValue);
Doris Liu6432cd62013-06-13 17:20:31 -0700690 mCameraDevice.setParameters(mParameters);
691 Parameters p = mCameraDevice.getParameters();
Doris Liu6827ce22013-03-12 19:24:28 -0700692 if (p != null) return p.getZoom();
693 return index;
694 }
Angus Kong395ee2d2013-07-15 12:42:41 -0700695
Michael Kolb8872c232013-01-29 10:33:22 -0800696 private void startPreview() {
697 Log.v(TAG, "startPreview");
698
Angus Kong395ee2d2013-07-15 12:42:41 -0700699 SurfaceTexture surfaceTexture = mUI.getSurfaceTexture();
ztenghuief01a312013-10-14 15:25:16 -0700700 if (!mPreferenceRead || surfaceTexture == null || mPaused == true ||
701 mCameraDevice == null) {
702 return;
703 }
Angus Kong395ee2d2013-07-15 12:42:41 -0700704
Doris Liu6432cd62013-06-13 17:20:31 -0700705 mCameraDevice.setErrorCallback(mErrorCallback);
Michael Kolb8872c232013-01-29 10:33:22 -0800706 if (mPreviewing == true) {
707 stopPreview();
Michael Kolb8872c232013-01-29 10:33:22 -0800708 }
709
Michael Kolb8872c232013-01-29 10:33:22 -0800710 setDisplayOrientation();
Doris Liu6432cd62013-06-13 17:20:31 -0700711 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800712 setCameraParameters();
713
714 try {
Doris Liu3973deb2013-08-21 13:42:22 -0700715 mCameraDevice.setPreviewTexture(surfaceTexture);
716 mCameraDevice.startPreview();
717 mPreviewing = true;
718 onPreviewStarted();
Michael Kolb8872c232013-01-29 10:33:22 -0800719 } catch (Throwable ex) {
720 closeCamera();
721 throw new RuntimeException("startPreview failed", ex);
Michael Kolb8872c232013-01-29 10:33:22 -0800722 }
Michael Kolbb1aeb392013-03-11 12:37:40 -0700723 }
724
725 private void onPreviewStarted() {
Doris Liu6827ce22013-03-12 19:24:28 -0700726 mUI.enableShutter(true);
Michael Kolb8872c232013-01-29 10:33:22 -0800727 }
728
Doris Liu6827ce22013-03-12 19:24:28 -0700729 @Override
730 public void stopPreview() {
Doris Liu6432cd62013-06-13 17:20:31 -0700731 if (!mPreviewing) return;
732 mCameraDevice.stopPreview();
Michael Kolb8872c232013-01-29 10:33:22 -0800733 mPreviewing = false;
734 }
735
Michael Kolb8872c232013-01-29 10:33:22 -0800736 private void closeCamera() {
Michael Kolb8872c232013-01-29 10:33:22 -0800737 Log.v(TAG, "closeCamera");
Doris Liu6432cd62013-06-13 17:20:31 -0700738 if (mCameraDevice == null) {
Michael Kolb8872c232013-01-29 10:33:22 -0800739 Log.d(TAG, "already stopped.");
740 return;
741 }
Doris Liu6432cd62013-06-13 17:20:31 -0700742 mCameraDevice.setZoomChangeListener(null);
743 mCameraDevice.setErrorCallback(null);
Angus Kong20fad242013-11-11 18:23:46 -0800744 mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
Angus Kong395ee2d2013-07-15 12:42:41 -0700745 mCameraDevice = null;
Michael Kolb8872c232013-01-29 10:33:22 -0800746 mPreviewing = false;
747 mSnapshotInProgress = false;
748 }
749
750 private void releasePreviewResources() {
Doris Liu6432cd62013-06-13 17:20:31 -0700751 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
752 mUI.hideSurfaceView();
Michael Kolb8872c232013-01-29 10:33:22 -0800753 }
754 }
755
756 @Override
757 public void onPauseBeforeSuper() {
758 mPaused = true;
759
Doris Liu3a45c332013-10-15 19:10:28 -0700760 mUI.showPreviewCover();
Michael Kolb8872c232013-01-29 10:33:22 -0800761 if (mMediaRecorderRecording) {
762 // Camera will be released in onStopVideoRecording.
763 onStopVideoRecording();
764 } else {
Angus Kong20fad242013-11-11 18:23:46 -0800765 stopPreview();
Michael Kolb8872c232013-01-29 10:33:22 -0800766 closeCamera();
Doris Liu3973deb2013-08-21 13:42:22 -0700767 releaseMediaRecorder();
Michael Kolb8872c232013-01-29 10:33:22 -0800768 }
Doris Liu3973deb2013-08-21 13:42:22 -0700769
770 closeVideoFileDescriptor();
771
Michael Kolb8872c232013-01-29 10:33:22 -0800772
773 releasePreviewResources();
774
775 if (mReceiver != null) {
776 mActivity.unregisterReceiver(mReceiver);
777 mReceiver = null;
778 }
779 resetScreenOn();
780
781 if (mLocationManager != null) mLocationManager.recordLocation(false);
782
783 mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
784 mHandler.removeMessages(SWITCH_CAMERA);
785 mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION);
786 mPendingSwitchCameraId = -1;
787 mSwitchingCamera = false;
Angus Kong395ee2d2013-07-15 12:42:41 -0700788 mPreferenceRead = false;
Doris Liuc3679c02013-08-08 18:08:43 -0700789
790 mUI.collapseCameraControls();
Doris Liu59390062013-10-10 17:20:56 -0700791 mUI.removeDisplayChangeListener();
Michael Kolb8872c232013-01-29 10:33:22 -0800792 }
793
794 @Override
795 public void onPauseAfterSuper() {
796 }
797
798 @Override
799 public void onUserInteraction() {
800 if (!mMediaRecorderRecording && !mActivity.isFinishing()) {
801 keepScreenOnAwhile();
802 }
803 }
804
805 @Override
806 public boolean onBackPressed() {
807 if (mPaused) return true;
808 if (mMediaRecorderRecording) {
809 onStopVideoRecording();
810 return true;
Doris Liu6827ce22013-03-12 19:24:28 -0700811 } else if (mUI.hidePieRenderer()) {
Michael Kolb8872c232013-01-29 10:33:22 -0800812 return true;
813 } else {
Doris Liu6827ce22013-03-12 19:24:28 -0700814 return mUI.removeTopLevelPopup();
Michael Kolb8872c232013-01-29 10:33:22 -0800815 }
816 }
817
818 @Override
819 public boolean onKeyDown(int keyCode, KeyEvent event) {
820 // Do not handle any key if the activity is paused.
821 if (mPaused) {
822 return true;
823 }
824
825 switch (keyCode) {
826 case KeyEvent.KEYCODE_CAMERA:
827 if (event.getRepeatCount() == 0) {
Doris Liu6827ce22013-03-12 19:24:28 -0700828 mUI.clickShutter();
Michael Kolb8872c232013-01-29 10:33:22 -0800829 return true;
830 }
831 break;
832 case KeyEvent.KEYCODE_DPAD_CENTER:
833 if (event.getRepeatCount() == 0) {
Doris Liu6827ce22013-03-12 19:24:28 -0700834 mUI.clickShutter();
Michael Kolb8872c232013-01-29 10:33:22 -0800835 return true;
836 }
837 break;
838 case KeyEvent.KEYCODE_MENU:
839 if (mMediaRecorderRecording) return true;
840 break;
841 }
842 return false;
843 }
844
845 @Override
846 public boolean onKeyUp(int keyCode, KeyEvent event) {
847 switch (keyCode) {
848 case KeyEvent.KEYCODE_CAMERA:
Doris Liu6827ce22013-03-12 19:24:28 -0700849 mUI.pressShutter(false);
Michael Kolb8872c232013-01-29 10:33:22 -0800850 return true;
851 }
852 return false;
853 }
854
Doris Liu6827ce22013-03-12 19:24:28 -0700855 @Override
856 public boolean isVideoCaptureIntent() {
Michael Kolb8872c232013-01-29 10:33:22 -0800857 String action = mActivity.getIntent().getAction();
858 return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
859 }
860
861 private void doReturnToCaller(boolean valid) {
862 Intent resultIntent = new Intent();
863 int resultCode;
864 if (valid) {
865 resultCode = Activity.RESULT_OK;
866 resultIntent.setData(mCurrentVideoUri);
867 } else {
868 resultCode = Activity.RESULT_CANCELED;
869 }
870 mActivity.setResultEx(resultCode, resultIntent);
871 mActivity.finish();
872 }
873
874 private void cleanupEmptyFile() {
875 if (mVideoFilename != null) {
876 File f = new File(mVideoFilename);
877 if (f.length() == 0 && f.delete()) {
878 Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
879 mVideoFilename = null;
880 }
881 }
882 }
883
884 private void setupMediaRecorderPreviewDisplay() {
885 // Nothing to do here if using SurfaceTexture.
Doris Liu6432cd62013-06-13 17:20:31 -0700886 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
Michael Kolb8872c232013-01-29 10:33:22 -0800887 // We stop the preview here before unlocking the device because we
888 // need to change the SurfaceTexture to SurfaceView for preview.
889 stopPreview();
Angus Kong9ef99252013-07-18 18:04:19 -0700890 mCameraDevice.setPreviewDisplay(mUI.getSurfaceHolder());
Michael Kolb8872c232013-01-29 10:33:22 -0800891 // The orientation for SurfaceTexture is different from that for
892 // SurfaceView. For SurfaceTexture we don't need to consider the
893 // display rotation. Just consider the sensor's orientation and we
894 // will set the orientation correctly when showing the texture.
895 // Gallery will handle the orientation for the preview. For
896 // SurfaceView we will have to take everything into account so the
897 // display rotation is considered.
Doris Liu6432cd62013-06-13 17:20:31 -0700898 mCameraDevice.setDisplayOrientation(
Angus Kongb50b5cb2013-08-09 14:55:20 -0700899 CameraUtil.getDisplayOrientation(mDisplayRotation, mCameraId));
Angus Kong9ef99252013-07-18 18:04:19 -0700900 mCameraDevice.startPreview();
Michael Kolb8872c232013-01-29 10:33:22 -0800901 mPreviewing = true;
Doris Liu6827ce22013-03-12 19:24:28 -0700902 mMediaRecorder.setPreviewDisplay(mUI.getSurfaceHolder().getSurface());
Michael Kolb8872c232013-01-29 10:33:22 -0800903 }
904 }
905
906 // Prepares media recorder.
907 private void initializeRecorder() {
908 Log.v(TAG, "initializeRecorder");
909 // If the mCameraDevice is null, then this activity is going to finish
Doris Liu6432cd62013-06-13 17:20:31 -0700910 if (mCameraDevice == null) return;
Michael Kolb8872c232013-01-29 10:33:22 -0800911
Doris Liu6432cd62013-06-13 17:20:31 -0700912 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
Michael Kolb8872c232013-01-29 10:33:22 -0800913 // Set the SurfaceView to visible so the surface gets created.
914 // surfaceCreated() is called immediately when the visibility is
915 // changed to visible. Thus, mSurfaceViewReady should become true
916 // right after calling setVisibility().
Doris Liu6827ce22013-03-12 19:24:28 -0700917 mUI.showSurfaceView();
Michael Kolb8872c232013-01-29 10:33:22 -0800918 }
919
920 Intent intent = mActivity.getIntent();
921 Bundle myExtras = intent.getExtras();
922
923 long requestedSizeLimit = 0;
924 closeVideoFileDescriptor();
ztenghui70bd0242013-10-14 11:15:44 -0700925 mCurrentVideoUriFromMediaSaved = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800926 if (mIsVideoCaptureIntent && myExtras != null) {
927 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
928 if (saveUri != null) {
929 try {
930 mVideoFileDescriptor =
931 mContentResolver.openFileDescriptor(saveUri, "rw");
932 mCurrentVideoUri = saveUri;
933 } catch (java.io.FileNotFoundException ex) {
934 // invalid uri
935 Log.e(TAG, ex.toString());
936 }
937 }
938 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
939 }
940 mMediaRecorder = new MediaRecorder();
941
942 setupMediaRecorderPreviewDisplay();
943 // Unlock the camera object before passing it to media recorder.
Doris Liu6432cd62013-06-13 17:20:31 -0700944 mCameraDevice.unlock();
Doris Liu6432cd62013-06-13 17:20:31 -0700945 mMediaRecorder.setCamera(mCameraDevice.getCamera());
Michael Kolb8872c232013-01-29 10:33:22 -0800946 if (!mCaptureTimeLapse) {
947 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
948 }
949 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
950 mMediaRecorder.setProfile(mProfile);
951 mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
952 if (mCaptureTimeLapse) {
953 double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs;
954 setCaptureRate(mMediaRecorder, fps);
955 }
956
957 setRecordLocation();
958
959 // Set output file.
960 // Try Uri in the intent first. If it doesn't exist, use our own
961 // instead.
962 if (mVideoFileDescriptor != null) {
963 mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
964 } else {
965 generateVideoFilename(mProfile.fileFormat);
966 mMediaRecorder.setOutputFile(mVideoFilename);
967 }
968
969 // Set maximum file size.
Angus Kong2dcc0a92013-09-25 13:00:08 -0700970 long maxFileSize = mActivity.getStorageSpaceBytes() - Storage.LOW_STORAGE_THRESHOLD_BYTES;
Michael Kolb8872c232013-01-29 10:33:22 -0800971 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
972 maxFileSize = requestedSizeLimit;
973 }
974
975 try {
976 mMediaRecorder.setMaxFileSize(maxFileSize);
977 } catch (RuntimeException exception) {
978 // We are going to ignore failure of setMaxFileSize here, as
979 // a) The composer selected may simply not support it, or
980 // b) The underlying media framework may not handle 64-bit range
981 // on the size restriction.
982 }
983
984 // See android.hardware.Camera.Parameters.setRotation for
985 // documentation.
986 // Note that mOrientation here is the device orientation, which is the opposite of
987 // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
988 // which is the orientation the graphics need to rotate in order to render correctly.
989 int rotation = 0;
990 if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
Angus Kong20fad242013-11-11 18:23:46 -0800991 CameraInfo info = mActivity.getCameraProvider().getCameraInfo()[mCameraId];
Michael Kolb8872c232013-01-29 10:33:22 -0800992 if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
993 rotation = (info.orientation - mOrientation + 360) % 360;
994 } else { // back-facing camera
995 rotation = (info.orientation + mOrientation) % 360;
996 }
997 }
998 mMediaRecorder.setOrientationHint(rotation);
999
1000 try {
1001 mMediaRecorder.prepare();
1002 } catch (IOException e) {
1003 Log.e(TAG, "prepare failed for " + mVideoFilename, e);
1004 releaseMediaRecorder();
1005 throw new RuntimeException(e);
1006 }
1007
1008 mMediaRecorder.setOnErrorListener(this);
1009 mMediaRecorder.setOnInfoListener(this);
1010 }
1011
Michael Kolb8872c232013-01-29 10:33:22 -08001012 private static void setCaptureRate(MediaRecorder recorder, double fps) {
1013 recorder.setCaptureRate(fps);
1014 }
1015
Michael Kolb8872c232013-01-29 10:33:22 -08001016 private void setRecordLocation() {
Sascha Haeberling638e6f02013-09-18 14:28:51 -07001017 Location loc = mLocationManager.getCurrentLocation();
1018 if (loc != null) {
1019 mMediaRecorder.setLocation((float) loc.getLatitude(),
1020 (float) loc.getLongitude());
Michael Kolb8872c232013-01-29 10:33:22 -08001021 }
1022 }
1023
Michael Kolb8872c232013-01-29 10:33:22 -08001024 private void releaseMediaRecorder() {
1025 Log.v(TAG, "Releasing media recorder.");
1026 if (mMediaRecorder != null) {
1027 cleanupEmptyFile();
1028 mMediaRecorder.reset();
1029 mMediaRecorder.release();
1030 mMediaRecorder = null;
1031 }
1032 mVideoFilename = null;
1033 }
1034
Michael Kolb8872c232013-01-29 10:33:22 -08001035 private void generateVideoFilename(int outputFileFormat) {
1036 long dateTaken = System.currentTimeMillis();
1037 String title = createName(dateTaken);
1038 // Used when emailing.
1039 String filename = title + convertOutputFormatToFileExt(outputFileFormat);
1040 String mime = convertOutputFormatToMimeType(outputFileFormat);
1041 String path = Storage.DIRECTORY + '/' + filename;
1042 String tmpPath = path + ".tmp";
Ruben Brunk16007962013-04-19 15:27:57 -07001043 mCurrentVideoValues = new ContentValues(9);
Michael Kolb8872c232013-01-29 10:33:22 -08001044 mCurrentVideoValues.put(Video.Media.TITLE, title);
1045 mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
1046 mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
Ruben Brunk16007962013-04-19 15:27:57 -07001047 mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000);
Michael Kolb8872c232013-01-29 10:33:22 -08001048 mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
1049 mCurrentVideoValues.put(Video.Media.DATA, path);
1050 mCurrentVideoValues.put(Video.Media.RESOLUTION,
1051 Integer.toString(mProfile.videoFrameWidth) + "x" +
1052 Integer.toString(mProfile.videoFrameHeight));
1053 Location loc = mLocationManager.getCurrentLocation();
1054 if (loc != null) {
1055 mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
1056 mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
1057 }
Michael Kolb8872c232013-01-29 10:33:22 -08001058 mVideoFilename = tmpPath;
1059 Log.v(TAG, "New video filename: " + mVideoFilename);
1060 }
1061
Angus Kong83a99ae2013-04-17 15:37:07 -07001062 private void saveVideo() {
Michael Kolb8872c232013-01-29 10:33:22 -08001063 if (mVideoFileDescriptor == null) {
Michael Kolb8872c232013-01-29 10:33:22 -08001064 long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
1065 if (duration > 0) {
1066 if (mCaptureTimeLapse) {
1067 duration = getTimeLapseVideoLength(duration);
1068 }
Michael Kolb8872c232013-01-29 10:33:22 -08001069 } else {
1070 Log.w(TAG, "Video duration <= 0 : " + duration);
1071 }
Angus Kongfd4fc0e2013-11-07 15:38:09 -08001072 mActivity.getMediaSaver().addVideo(mCurrentVideoFilename,
Angus Kong83a99ae2013-04-17 15:37:07 -07001073 duration, mCurrentVideoValues,
1074 mOnVideoSavedListener, mContentResolver);
Michael Kolb8872c232013-01-29 10:33:22 -08001075 }
1076 mCurrentVideoValues = null;
Michael Kolb8872c232013-01-29 10:33:22 -08001077 }
1078
1079 private void deleteVideoFile(String fileName) {
1080 Log.v(TAG, "Deleting video " + fileName);
1081 File f = new File(fileName);
1082 if (!f.delete()) {
1083 Log.v(TAG, "Could not delete " + fileName);
1084 }
1085 }
1086
1087 private PreferenceGroup filterPreferenceScreenByIntent(
1088 PreferenceGroup screen) {
1089 Intent intent = mActivity.getIntent();
1090 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
Angus Kong20fad242013-11-11 18:23:46 -08001091 CameraSettings.removePreferenceFromScreen(screen, CameraSettings.KEY_VIDEO_QUALITY);
Michael Kolb8872c232013-01-29 10:33:22 -08001092 }
1093
1094 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
1095 CameraSettings.removePreferenceFromScreen(screen,
1096 CameraSettings.KEY_VIDEO_QUALITY);
1097 }
1098 return screen;
1099 }
1100
1101 // from MediaRecorder.OnErrorListener
1102 @Override
1103 public void onError(MediaRecorder mr, int what, int extra) {
1104 Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
1105 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1106 // We may have run out of space on the sdcard.
1107 stopVideoRecording();
1108 mActivity.updateStorageSpaceAndHint();
1109 }
1110 }
1111
1112 // from MediaRecorder.OnInfoListener
1113 @Override
1114 public void onInfo(MediaRecorder mr, int what, int extra) {
1115 if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
1116 if (mMediaRecorderRecording) onStopVideoRecording();
1117 } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
1118 if (mMediaRecorderRecording) onStopVideoRecording();
1119
1120 // Show the toast.
1121 Toast.makeText(mActivity, R.string.video_reach_size_limit,
1122 Toast.LENGTH_LONG).show();
1123 }
1124 }
1125
1126 /*
1127 * Make sure we're not recording music playing in the background, ask the
1128 * MediaPlaybackService to pause playback.
1129 */
1130 private void pauseAudioPlayback() {
1131 // Shamelessly copied from MediaPlaybackService.java, which
1132 // should be public, but isn't.
1133 Intent i = new Intent("com.android.music.musicservicecommand");
1134 i.putExtra("command", "pause");
1135
1136 mActivity.sendBroadcast(i);
1137 }
1138
1139 // For testing.
1140 public boolean isRecording() {
1141 return mMediaRecorderRecording;
1142 }
1143
1144 private void startVideoRecording() {
1145 Log.v(TAG, "startVideoRecording");
Sascha Haeberling37f36112013-08-06 14:31:52 -07001146 mUI.cancelAnimations();
Doris Liu6432cd62013-06-13 17:20:31 -07001147 mUI.setSwipingEnabled(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001148
1149 mActivity.updateStorageSpaceAndHint();
Angus Kong2dcc0a92013-09-25 13:00:08 -07001150 if (mActivity.getStorageSpaceBytes() <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
Michael Kolb8872c232013-01-29 10:33:22 -08001151 Log.v(TAG, "Storage issue, ignore the start request");
1152 return;
1153 }
1154
Angus Kong9ef99252013-07-18 18:04:19 -07001155 //??
1156 //if (!mCameraDevice.waitDone()) return;
Michael Kolb8872c232013-01-29 10:33:22 -08001157 mCurrentVideoUri = null;
Doris Liu3973deb2013-08-21 13:42:22 -07001158
1159 initializeRecorder();
1160 if (mMediaRecorder == null) {
1161 Log.e(TAG, "Fail to initialize media recorder");
1162 return;
Michael Kolb8872c232013-01-29 10:33:22 -08001163 }
1164
1165 pauseAudioPlayback();
1166
Doris Liu3973deb2013-08-21 13:42:22 -07001167 try {
1168 mMediaRecorder.start(); // Recording is now started
1169 } catch (RuntimeException e) {
1170 Log.e(TAG, "Could not start media recorder. ", e);
1171 releaseMediaRecorder();
1172 // If start fails, frameworks will not lock the camera for us.
1173 mCameraDevice.lock();
1174 return;
Michael Kolb8872c232013-01-29 10:33:22 -08001175 }
1176
1177 // Make sure the video recording has started before announcing
1178 // this in accessibility.
Doris Liu6432cd62013-06-13 17:20:31 -07001179 AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
Michael Kolb8872c232013-01-29 10:33:22 -08001180 mActivity.getString(R.string.video_recording_started));
1181
Angus Kong104e0012013-04-03 16:56:18 -07001182 // The parameters might have been altered by MediaRecorder already.
1183 // We need to force mCameraDevice to refresh before getting it.
Doris Liu6432cd62013-06-13 17:20:31 -07001184 mCameraDevice.refreshParameters();
Michael Kolb8872c232013-01-29 10:33:22 -08001185 // The parameters may have been changed by MediaRecorder upon starting
1186 // recording. We need to alter the parameters if we support camcorder
1187 // zoom. To reduce latency when setting the parameters during zoom, we
1188 // update mParameters here once.
Sascha Haeberling638e6f02013-09-18 14:28:51 -07001189 mParameters = mCameraDevice.getParameters();
Michael Kolb8872c232013-01-29 10:33:22 -08001190
Doris Liu6827ce22013-03-12 19:24:28 -07001191 mUI.enableCameraControls(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001192
1193 mMediaRecorderRecording = true;
Angus Kong9f1db522013-11-09 16:25:59 -08001194 mActivity.lockOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001195 mRecordingStartTime = SystemClock.uptimeMillis();
Doris Liufe6596c2013-10-08 11:03:37 -07001196 mUI.showRecordingUI(true);
Michael Kolb8872c232013-01-29 10:33:22 -08001197
1198 updateRecordingTime();
1199 keepScreenOn();
Bobby Georgescu301b6462013-04-01 15:33:17 -07001200 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1201 UsageStatistics.ACTION_CAPTURE_START, "Video");
Michael Kolb8872c232013-01-29 10:33:22 -08001202 }
1203
Sascha Haeberling37f36112013-08-06 14:31:52 -07001204 private Bitmap getVideoThumbnail() {
Michael Kolb8872c232013-01-29 10:33:22 -08001205 Bitmap bitmap = null;
1206 if (mVideoFileDescriptor != null) {
1207 bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
Doris Liu3c2fca32013-02-13 18:28:03 -08001208 mDesiredPreviewWidth);
Doris Liu2a7f44c2013-08-12 15:18:53 -07001209 } else if (mCurrentVideoUri != null) {
1210 try {
1211 mVideoFileDescriptor = mContentResolver.openFileDescriptor(mCurrentVideoUri, "r");
1212 bitmap = Thumbnail.createVideoThumbnailBitmap(
1213 mVideoFileDescriptor.getFileDescriptor(), mDesiredPreviewWidth);
1214 } catch (java.io.FileNotFoundException ex) {
1215 // invalid uri
1216 Log.e(TAG, ex.toString());
1217 }
Michael Kolb8872c232013-01-29 10:33:22 -08001218 }
Doris Liu3973deb2013-08-21 13:42:22 -07001219
Michael Kolb8872c232013-01-29 10:33:22 -08001220 if (bitmap != null) {
1221 // MetadataRetriever already rotates the thumbnail. We should rotate
1222 // it to match the UI orientation (and mirror if it is front-facing camera).
Angus Kong20fad242013-11-11 18:23:46 -08001223 CameraInfo[] info = mActivity.getCameraProvider().getCameraInfo();
Michael Kolb8872c232013-01-29 10:33:22 -08001224 boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT);
Angus Kongb50b5cb2013-08-09 14:55:20 -07001225 bitmap = CameraUtil.rotateAndMirror(bitmap, 0, mirror);
Sascha Haeberling37f36112013-08-06 14:31:52 -07001226 }
1227 return bitmap;
1228 }
1229
1230 private void showCaptureResult() {
1231 mIsInReviewMode = true;
1232 Bitmap bitmap = getVideoThumbnail();
1233 if (bitmap != null) {
Doris Liu6827ce22013-03-12 19:24:28 -07001234 mUI.showReviewImage(bitmap);
Michael Kolb8872c232013-01-29 10:33:22 -08001235 }
Doris Liu6827ce22013-03-12 19:24:28 -07001236 mUI.showReviewControls();
1237 mUI.enableCameraControls(false);
1238 mUI.showTimeLapseUI(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001239 }
1240
Michael Kolb8872c232013-01-29 10:33:22 -08001241 private boolean stopVideoRecording() {
1242 Log.v(TAG, "stopVideoRecording");
Doris Liu6432cd62013-06-13 17:20:31 -07001243 mUI.setSwipingEnabled(true);
Michael Kolb8872c232013-01-29 10:33:22 -08001244
1245 boolean fail = false;
1246 if (mMediaRecorderRecording) {
1247 boolean shouldAddToMediaStoreNow = false;
1248
1249 try {
Doris Liu3973deb2013-08-21 13:42:22 -07001250 mMediaRecorder.setOnErrorListener(null);
1251 mMediaRecorder.setOnInfoListener(null);
1252 mMediaRecorder.stop();
1253 shouldAddToMediaStoreNow = true;
Michael Kolb8872c232013-01-29 10:33:22 -08001254 mCurrentVideoFilename = mVideoFilename;
1255 Log.v(TAG, "stopVideoRecording: Setting current video filename: "
1256 + mCurrentVideoFilename);
Doris Liu6432cd62013-06-13 17:20:31 -07001257 AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
Michael Kolb8872c232013-01-29 10:33:22 -08001258 mActivity.getString(R.string.video_recording_stopped));
1259 } catch (RuntimeException e) {
1260 Log.e(TAG, "stop fail", e);
1261 if (mVideoFilename != null) deleteVideoFile(mVideoFilename);
1262 fail = true;
1263 }
1264 mMediaRecorderRecording = false;
Angus Kong9f1db522013-11-09 16:25:59 -08001265 mActivity.unlockOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001266
1267 // If the activity is paused, this means activity is interrupted
1268 // during recording. Release the camera as soon as possible because
1269 // face unlock or other applications may need to use the camera.
Michael Kolb8872c232013-01-29 10:33:22 -08001270 if (mPaused) {
Doris Liu3973deb2013-08-21 13:42:22 -07001271 closeCamera();
Michael Kolb8872c232013-01-29 10:33:22 -08001272 }
1273
Doris Liufe6596c2013-10-08 11:03:37 -07001274 mUI.showRecordingUI(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001275 if (!mIsVideoCaptureIntent) {
Doris Liu6827ce22013-03-12 19:24:28 -07001276 mUI.enableCameraControls(true);
Michael Kolb8872c232013-01-29 10:33:22 -08001277 }
1278 // The orientation was fixed during video recording. Now make it
1279 // reflect the device orientation as video recording is stopped.
Doris Liu6827ce22013-03-12 19:24:28 -07001280 mUI.setOrientationIndicator(0, true);
Michael Kolb8872c232013-01-29 10:33:22 -08001281 keepScreenOnAwhile();
Doris Liu2a7f44c2013-08-12 15:18:53 -07001282 if (shouldAddToMediaStoreNow && !fail) {
1283 if (mVideoFileDescriptor == null) {
1284 saveVideo();
1285 } else if (mIsVideoCaptureIntent) {
1286 // if no file save is needed, we can show the post capture UI now
1287 showCaptureResult();
1288 }
Michael Kolb8872c232013-01-29 10:33:22 -08001289 }
1290 }
Doris Liu3973deb2013-08-21 13:42:22 -07001291 // release media recorder
1292 releaseMediaRecorder();
1293 if (!mPaused) {
1294 mCameraDevice.lock();
1295 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
1296 stopPreview();
1297 mUI.hideSurfaceView();
1298 // Switch back to use SurfaceTexture for preview.
1299 startPreview();
Michael Kolb8872c232013-01-29 10:33:22 -08001300 }
1301 }
1302 // Update the parameters here because the parameters might have been altered
1303 // by MediaRecorder.
Doris Liu6432cd62013-06-13 17:20:31 -07001304 if (!mPaused) mParameters = mCameraDevice.getParameters();
Bobby Georgescu301b6462013-04-01 15:33:17 -07001305 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1306 fail ? UsageStatistics.ACTION_CAPTURE_FAIL :
1307 UsageStatistics.ACTION_CAPTURE_DONE, "Video",
1308 SystemClock.uptimeMillis() - mRecordingStartTime);
Michael Kolb8872c232013-01-29 10:33:22 -08001309 return fail;
1310 }
1311
1312 private void resetScreenOn() {
1313 mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1314 mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1315 }
1316
1317 private void keepScreenOnAwhile() {
1318 mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1319 mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1320 mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1321 }
1322
1323 private void keepScreenOn() {
1324 mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1325 mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1326 }
1327
1328 private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
1329 long seconds = milliSeconds / 1000; // round down to compute seconds
1330 long minutes = seconds / 60;
1331 long hours = minutes / 60;
1332 long remainderMinutes = minutes - (hours * 60);
1333 long remainderSeconds = seconds - (minutes * 60);
1334
1335 StringBuilder timeStringBuilder = new StringBuilder();
1336
1337 // Hours
1338 if (hours > 0) {
1339 if (hours < 10) {
1340 timeStringBuilder.append('0');
1341 }
1342 timeStringBuilder.append(hours);
1343
1344 timeStringBuilder.append(':');
1345 }
1346
1347 // Minutes
1348 if (remainderMinutes < 10) {
1349 timeStringBuilder.append('0');
1350 }
1351 timeStringBuilder.append(remainderMinutes);
1352 timeStringBuilder.append(':');
1353
1354 // Seconds
1355 if (remainderSeconds < 10) {
1356 timeStringBuilder.append('0');
1357 }
1358 timeStringBuilder.append(remainderSeconds);
1359
1360 // Centi seconds
1361 if (displayCentiSeconds) {
1362 timeStringBuilder.append('.');
1363 long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
1364 if (remainderCentiSeconds < 10) {
1365 timeStringBuilder.append('0');
1366 }
1367 timeStringBuilder.append(remainderCentiSeconds);
1368 }
1369
1370 return timeStringBuilder.toString();
1371 }
1372
1373 private long getTimeLapseVideoLength(long deltaMs) {
1374 // For better approximation calculate fractional number of frames captured.
1375 // This will update the video time at a higher resolution.
1376 double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
1377 return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
1378 }
1379
1380 private void updateRecordingTime() {
1381 if (!mMediaRecorderRecording) {
1382 return;
1383 }
1384 long now = SystemClock.uptimeMillis();
1385 long delta = now - mRecordingStartTime;
1386
1387 // Starting a minute before reaching the max duration
1388 // limit, we'll countdown the remaining time instead.
1389 boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1390 && delta >= mMaxVideoDurationInMs - 60000);
1391
1392 long deltaAdjusted = delta;
1393 if (countdownRemainingTime) {
1394 deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
1395 }
1396 String text;
1397
1398 long targetNextUpdateDelay;
1399 if (!mCaptureTimeLapse) {
1400 text = millisecondToTimeString(deltaAdjusted, false);
1401 targetNextUpdateDelay = 1000;
1402 } else {
1403 // The length of time lapse video is different from the length
1404 // of the actual wall clock time elapsed. Display the video length
1405 // only in format hh:mm:ss.dd, where dd are the centi seconds.
1406 text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
1407 targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
1408 }
1409
Doris Liu6827ce22013-03-12 19:24:28 -07001410 mUI.setRecordingTime(text);
Michael Kolb8872c232013-01-29 10:33:22 -08001411
1412 if (mRecordingTimeCountsDown != countdownRemainingTime) {
1413 // Avoid setting the color on every update, do it only
1414 // when it needs changing.
1415 mRecordingTimeCountsDown = countdownRemainingTime;
1416
1417 int color = mActivity.getResources().getColor(countdownRemainingTime
1418 ? R.color.recording_time_remaining_text
1419 : R.color.recording_time_elapsed_text);
1420
Doris Liu6827ce22013-03-12 19:24:28 -07001421 mUI.setRecordingTimeTextColor(color);
Michael Kolb8872c232013-01-29 10:33:22 -08001422 }
1423
1424 long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
1425 mHandler.sendEmptyMessageDelayed(
1426 UPDATE_RECORD_TIME, actualNextUpdateDelay);
1427 }
1428
1429 private static boolean isSupported(String value, List<String> supported) {
1430 return supported == null ? false : supported.indexOf(value) >= 0;
1431 }
1432
1433 @SuppressWarnings("deprecation")
1434 private void setCameraParameters() {
1435 mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
Angus Kongb50b5cb2013-08-09 14:55:20 -07001436 int[] fpsRange = CameraUtil.getMaxPreviewFpsRange(mParameters);
Doris Liu6432cd62013-06-13 17:20:31 -07001437 if (fpsRange.length > 0) {
1438 mParameters.setPreviewFpsRange(
1439 fpsRange[Parameters.PREVIEW_FPS_MIN_INDEX],
1440 fpsRange[Parameters.PREVIEW_FPS_MAX_INDEX]);
1441 } else {
1442 mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
1443 }
Michael Kolb8872c232013-01-29 10:33:22 -08001444
ztenghui7b265a62013-09-09 14:58:44 -07001445 forceFlashOffIfSupported(!mUI.isVisible());
Michael Kolb8872c232013-01-29 10:33:22 -08001446
1447 // Set white balance parameter.
1448 String whiteBalance = mPreferences.getString(
1449 CameraSettings.KEY_WHITE_BALANCE,
1450 mActivity.getString(R.string.pref_camera_whitebalance_default));
1451 if (isSupported(whiteBalance,
1452 mParameters.getSupportedWhiteBalance())) {
1453 mParameters.setWhiteBalance(whiteBalance);
1454 } else {
1455 whiteBalance = mParameters.getWhiteBalance();
1456 if (whiteBalance == null) {
1457 whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1458 }
1459 }
1460
1461 // Set zoom.
1462 if (mParameters.isZoomSupported()) {
1463 mParameters.setZoom(mZoomValue);
1464 }
1465
1466 // Set continuous autofocus.
1467 List<String> supportedFocus = mParameters.getSupportedFocusModes();
1468 if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
1469 mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
1470 }
1471
Angus Kongb50b5cb2013-08-09 14:55:20 -07001472 mParameters.set(CameraUtil.RECORDING_HINT, CameraUtil.TRUE);
Michael Kolb8872c232013-01-29 10:33:22 -08001473
1474 // Enable video stabilization. Convenience methods not available in API
1475 // level <= 14
1476 String vstabSupported = mParameters.get("video-stabilization-supported");
1477 if ("true".equals(vstabSupported)) {
1478 mParameters.set("video-stabilization", "true");
1479 }
1480
1481 // Set picture size.
1482 // The logic here is different from the logic in still-mode camera.
1483 // There we determine the preview size based on the picture size, but
1484 // here we determine the picture size based on the preview size.
1485 List<Size> supported = mParameters.getSupportedPictureSizes();
Angus Kongb50b5cb2013-08-09 14:55:20 -07001486 Size optimalSize = CameraUtil.getOptimalVideoSnapshotPictureSize(supported,
Michael Kolb8872c232013-01-29 10:33:22 -08001487 (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
1488 Size original = mParameters.getPictureSize();
1489 if (!original.equals(optimalSize)) {
1490 mParameters.setPictureSize(optimalSize.width, optimalSize.height);
1491 }
1492 Log.v(TAG, "Video snapshot size is " + optimalSize.width + "x" +
1493 optimalSize.height);
1494
1495 // Set JPEG quality.
1496 int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1497 CameraProfile.QUALITY_HIGH);
1498 mParameters.setJpegQuality(jpegQuality);
1499
Doris Liu6432cd62013-06-13 17:20:31 -07001500 mCameraDevice.setParameters(mParameters);
Michael Kolb8872c232013-01-29 10:33:22 -08001501 // Keep preview size up to date.
Doris Liu6432cd62013-06-13 17:20:31 -07001502 mParameters = mCameraDevice.getParameters();
Sascha Haeberlingf8b877c2013-09-09 17:32:48 -07001503
1504 // Update UI based on the new parameters.
1505 mUI.updateOnScreenIndicators(mParameters, mPreferences);
Michael Kolb8872c232013-01-29 10:33:22 -08001506 }
1507
1508 @Override
1509 public void onActivityResult(int requestCode, int resultCode, Intent data) {
Doris Liu3973deb2013-08-21 13:42:22 -07001510 // Do nothing.
Michael Kolb8872c232013-01-29 10:33:22 -08001511 }
1512
Michael Kolb8872c232013-01-29 10:33:22 -08001513 @Override
Angus Kong20fad242013-11-11 18:23:46 -08001514 public void init(AppController app, boolean isSecureCamera, boolean isCaptureIntent) {
1515 init((CameraActivity) app.getAndroidContext(), app.getModuleLayoutRoot());
1516 }
1517
1518 @Override
1519 public void resume() {
1520 onResumeBeforeSuper();
1521 onResumeAfterSuper();
1522 }
1523
1524 @Override
1525 public void pause() {
1526 onPauseBeforeSuper();
1527 onPauseAfterSuper();
1528 }
1529
1530 @Override
1531 public void destroy() {
1532
1533 }
1534
1535 @Override
1536 public void onPreviewSizeChanged(int width, int height) {
1537 // TODO: implement this
1538 }
1539
1540 @Override
Michael Kolb8872c232013-01-29 10:33:22 -08001541 public void onConfigurationChanged(Configuration newConfig) {
Doris Liu6a0de792013-02-26 10:54:25 -08001542 Log.v(TAG, "onConfigurationChanged");
Michael Kolb8872c232013-01-29 10:33:22 -08001543 setDisplayOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001544 }
1545
1546 @Override
1547 public void onOverriddenPreferencesClicked() {
1548 }
1549
1550 @Override
1551 // TODO: Delete this after old camera code is removed
1552 public void onRestorePreferencesClicked() {
1553 }
1554
Michael Kolb8872c232013-01-29 10:33:22 -08001555 @Override
1556 public void onSharedPreferenceChanged() {
1557 // ignore the events after "onPause()" or preview has not started yet
Sascha Haeberlingf8b877c2013-09-09 17:32:48 -07001558 if (mPaused) {
1559 return;
1560 }
Michael Kolb8872c232013-01-29 10:33:22 -08001561 synchronized (mPreferences) {
1562 // If mCameraDevice is not ready then we can set the parameter in
1563 // startPreview().
Doris Liu6432cd62013-06-13 17:20:31 -07001564 if (mCameraDevice == null) return;
Michael Kolb8872c232013-01-29 10:33:22 -08001565
1566 boolean recordLocation = RecordLocationPreference.get(
1567 mPreferences, mContentResolver);
1568 mLocationManager.recordLocation(recordLocation);
1569
Michael Kolb8872c232013-01-29 10:33:22 -08001570 readVideoPreferences();
Doris Liu6827ce22013-03-12 19:24:28 -07001571 mUI.showTimeLapseUI(mCaptureTimeLapse);
Michael Kolb8872c232013-01-29 10:33:22 -08001572 // We need to restart the preview if preview size is changed.
1573 Size size = mParameters.getPreviewSize();
1574 if (size.width != mDesiredPreviewWidth
1575 || size.height != mDesiredPreviewHeight) {
Doris Liu3973deb2013-08-21 13:42:22 -07001576
1577 stopPreview();
Michael Kolb8872c232013-01-29 10:33:22 -08001578 resizeForPreviewAspectRatio();
1579 startPreview(); // Parameters will be set in startPreview().
1580 } else {
1581 setCameraParameters();
1582 }
Michael Kolb87880792013-04-30 15:38:49 -07001583 mUI.updateOnScreenIndicators(mParameters, mPreferences);
Michael Kolb8872c232013-01-29 10:33:22 -08001584 }
1585 }
1586
Doris Liu6827ce22013-03-12 19:24:28 -07001587 protected void setCameraId(int cameraId) {
1588 ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID);
1589 pref.setValue("" + cameraId);
Michael Kolb8872c232013-01-29 10:33:22 -08001590 }
1591
1592 private void switchCamera() {
Sascha Haeberlingf8b877c2013-09-09 17:32:48 -07001593 if (mPaused) {
1594 return;
1595 }
Michael Kolb8872c232013-01-29 10:33:22 -08001596
1597 Log.d(TAG, "Start to switch camera.");
1598 mCameraId = mPendingSwitchCameraId;
1599 mPendingSwitchCameraId = -1;
Doris Liu6827ce22013-03-12 19:24:28 -07001600 setCameraId(mCameraId);
Michael Kolb8872c232013-01-29 10:33:22 -08001601
1602 closeCamera();
Angus Kong20fad242013-11-11 18:23:46 -08001603 requestCamera(mCameraId);
Doris Liu6827ce22013-03-12 19:24:28 -07001604 mUI.collapseCameraControls();
Michael Kolb8872c232013-01-29 10:33:22 -08001605 // Restart the camera and initialize the UI. From onCreate.
1606 mPreferences.setLocalId(mActivity, mCameraId);
1607 CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
Michael Kolb8872c232013-01-29 10:33:22 -08001608
1609 // From onResume
Doris Liu6432cd62013-06-13 17:20:31 -07001610 mZoomValue = 0;
Doris Liu6827ce22013-03-12 19:24:28 -07001611 mUI.setOrientationIndicator(0, false);
Michael Kolb8872c232013-01-29 10:33:22 -08001612
Doris Liu6432cd62013-06-13 17:20:31 -07001613 // Start switch camera animation. Post a message because
1614 // onFrameAvailable from the old camera may already exist.
1615 mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
Michael Kolb87880792013-04-30 15:38:49 -07001616 mUI.updateOnScreenIndicators(mParameters, mPreferences);
Michael Kolb8872c232013-01-29 10:33:22 -08001617 }
1618
1619 // Preview texture has been copied. Now camera can be released and the
1620 // animation can be started.
1621 @Override
1622 public void onPreviewTextureCopied() {
1623 mHandler.sendEmptyMessage(SWITCH_CAMERA);
1624 }
1625
1626 @Override
1627 public void onCaptureTextureCopied() {
1628 }
1629
Michael Kolb8872c232013-01-29 10:33:22 -08001630 private void initializeVideoSnapshot() {
Doris Liu537718c2013-02-20 16:29:44 -08001631 if (mParameters == null) return;
Angus Kongb50b5cb2013-08-09 14:55:20 -07001632 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
Michael Kolb8872c232013-01-29 10:33:22 -08001633 // Show the tap to focus toast if this is the first start.
1634 if (mPreferences.getBoolean(
1635 CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, true)) {
1636 // Delay the toast for one second to wait for orientation.
1637 mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_SNAPSHOT_TOAST, 1000);
1638 }
Michael Kolb8872c232013-01-29 10:33:22 -08001639 }
1640 }
1641
1642 void showVideoSnapshotUI(boolean enabled) {
Doris Liu537718c2013-02-20 16:29:44 -08001643 if (mParameters == null) return;
Angus Kongb50b5cb2013-08-09 14:55:20 -07001644 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
Doris Liu6432cd62013-06-13 17:20:31 -07001645 if (enabled) {
Sascha Haeberling37f36112013-08-06 14:31:52 -07001646 mUI.animateFlash();
Doris Liu3973deb2013-08-21 13:42:22 -07001647 mUI.animateCapture();
Michael Kolb8872c232013-01-29 10:33:22 -08001648 } else {
Doris Liu6827ce22013-03-12 19:24:28 -07001649 mUI.showPreviewBorder(enabled);
Michael Kolb8872c232013-01-29 10:33:22 -08001650 }
Doris Liu6827ce22013-03-12 19:24:28 -07001651 mUI.enableShutter(!enabled);
Michael Kolb8872c232013-01-29 10:33:22 -08001652 }
1653 }
1654
ztenghui7b265a62013-09-09 14:58:44 -07001655 private void forceFlashOffIfSupported(boolean forceOff) {
1656 String flashMode;
1657 if (!forceOff) {
1658 flashMode = mPreferences.getString(
1659 CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
1660 mActivity.getString(R.string.pref_camera_video_flashmode_default));
1661 } else {
1662 flashMode = Parameters.FLASH_MODE_OFF;
1663 }
1664 List<String> supportedFlash = mParameters.getSupportedFlashModes();
1665 if (isSupported(flashMode, supportedFlash)) {
1666 mParameters.setFlashMode(flashMode);
1667 } else {
1668 flashMode = mParameters.getFlashMode();
1669 if (flashMode == null) {
1670 flashMode = mActivity.getString(
1671 R.string.pref_camera_flashmode_no_flash);
Michael Kolb8872c232013-01-29 10:33:22 -08001672 }
Michael Kolb8872c232013-01-29 10:33:22 -08001673 }
1674 }
1675
ztenghui7b265a62013-09-09 14:58:44 -07001676 /**
1677 * Used to update the flash mode. Video mode can turn on the flash as torch
1678 * mode, which we would like to turn on and off when we switching in and
1679 * out to the preview.
1680 *
1681 * @param forceOff whether we want to force the flash off.
1682 */
1683 private void forceFlashOff(boolean forceOff) {
1684 if (!mPreviewing || mParameters.getFlashMode() == null) {
1685 return;
1686 }
1687 forceFlashOffIfSupported(forceOff);
1688 mCameraDevice.setParameters(mParameters);
ztenghui285a5be2013-10-09 15:59:47 -07001689 mUI.updateOnScreenIndicators(mParameters, mPreferences);
ztenghui7b265a62013-09-09 14:58:44 -07001690 }
1691
Michael Kolb8872c232013-01-29 10:33:22 -08001692 @Override
ztenghui7b265a62013-09-09 14:58:44 -07001693 public void onPreviewFocusChanged(boolean previewFocused) {
1694 mUI.onPreviewFocusChanged(previewFocused);
1695 forceFlashOff(!previewFocused);
Michael Kolb8872c232013-01-29 10:33:22 -08001696 }
1697
Erin Dahlgren3044d8c2013-10-10 18:23:45 -07001698 @Override
1699 public boolean arePreviewControlsVisible() {
1700 return mUI.arePreviewControlsVisible();
1701 }
1702
Angus Kong9ef99252013-07-18 18:04:19 -07001703 private final class JpegPictureCallback implements CameraPictureCallback {
Michael Kolb8872c232013-01-29 10:33:22 -08001704 Location mLocation;
1705
1706 public JpegPictureCallback(Location loc) {
1707 mLocation = loc;
1708 }
1709
1710 @Override
Angus Kong9ef99252013-07-18 18:04:19 -07001711 public void onPictureTaken(byte [] jpegData, CameraProxy camera) {
Michael Kolb8872c232013-01-29 10:33:22 -08001712 Log.v(TAG, "onPictureTaken");
1713 mSnapshotInProgress = false;
1714 showVideoSnapshotUI(false);
1715 storeImage(jpegData, mLocation);
1716 }
1717 }
1718
1719 private void storeImage(final byte[] data, Location loc) {
1720 long dateTaken = System.currentTimeMillis();
Angus Kongb50b5cb2013-08-09 14:55:20 -07001721 String title = CameraUtil.createJpegName(dateTaken);
Angus Kong0d00a892013-03-26 11:40:40 -07001722 ExifInterface exif = Exif.getExif(data);
1723 int orientation = Exif.getOrientation(exif);
Doris Liu6df2d962013-08-20 16:31:29 -07001724
Angus Kongfd4fc0e2013-11-07 15:38:09 -08001725 mActivity.getMediaSaver().addImage(
Doris Liu6df2d962013-08-20 16:31:29 -07001726 data, title, dateTaken, loc, orientation,
Angus Kong83a99ae2013-04-17 15:37:07 -07001727 exif, mOnPhotoSavedListener, mContentResolver);
Michael Kolb8872c232013-01-29 10:33:22 -08001728 }
1729
Michael Kolb8872c232013-01-29 10:33:22 -08001730 private String convertOutputFormatToMimeType(int outputFileFormat) {
1731 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1732 return "video/mp4";
1733 }
1734 return "video/3gpp";
1735 }
1736
1737 private String convertOutputFormatToFileExt(int outputFileFormat) {
1738 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1739 return ".mp4";
1740 }
1741 return ".3gp";
1742 }
1743
1744 private void closeVideoFileDescriptor() {
1745 if (mVideoFileDescriptor != null) {
1746 try {
1747 mVideoFileDescriptor.close();
1748 } catch (IOException e) {
1749 Log.e(TAG, "Fail to close fd", e);
1750 }
1751 mVideoFileDescriptor = null;
1752 }
1753 }
1754
1755 private void showTapToSnapshotToast() {
1756 new RotateTextToast(mActivity, R.string.video_snapshot_hint, 0)
1757 .show();
1758 // Clear the preference.
1759 Editor editor = mPreferences.edit();
1760 editor.putBoolean(CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, false);
1761 editor.apply();
1762 }
1763
Michael Kolb8872c232013-01-29 10:33:22 -08001764 @Override
1765 public boolean updateStorageHintOnResume() {
1766 return true;
1767 }
1768
1769 // required by OnPreferenceChangedListener
1770 @Override
1771 public void onCameraPickerClicked(int cameraId) {
1772 if (mPaused || mPendingSwitchCameraId != -1) return;
1773
1774 mPendingSwitchCameraId = cameraId;
Doris Liu6432cd62013-06-13 17:20:31 -07001775 Log.d(TAG, "Start to copy texture.");
Angus Kong20fad242013-11-11 18:23:46 -08001776
Doris Liu6432cd62013-06-13 17:20:31 -07001777 // Disable all camera controls.
1778 mSwitchingCamera = true;
Sascha Haeberling0d63b892013-08-19 11:29:04 -07001779 switchCamera();
Michael Kolb8872c232013-01-29 10:33:22 -08001780
Doris Liu6a0de792013-02-26 10:54:25 -08001781 }
1782
1783 @Override
Michael Kolb8872c232013-01-29 10:33:22 -08001784 public void onShowSwitcherPopup() {
Doris Liu6827ce22013-03-12 19:24:28 -07001785 mUI.onShowSwitcherPopup();
Michael Kolb8872c232013-01-29 10:33:22 -08001786 }
Angus Kong86d36312013-01-31 18:22:44 -08001787
1788 @Override
Angus Kongfd4fc0e2013-11-07 15:38:09 -08001789 public void onMediaSaverAvailable(MediaSaver s) {
Angus Kong86d36312013-01-31 18:22:44 -08001790 // do nothing.
1791 }
Angus Kong395ee2d2013-07-15 12:42:41 -07001792
1793 @Override
1794 public void onPreviewUIReady() {
1795 startPreview();
1796 }
1797
1798 @Override
1799 public void onPreviewUIDestroyed() {
1800 stopPreview();
1801 }
Angus Kong20fad242013-11-11 18:23:46 -08001802
1803 private void requestCamera(int id) {
1804 mActivity.getCameraProvider().requestCamera(id);
1805 }
Michael Kolb8872c232013-01-29 10:33:22 -08001806}