blob: 11598796b540fd69ff3436e687f4ce9c2f101b45 [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 Kong9ef99252013-07-18 18:04:19 -070056import com.android.camera.CameraManager.CameraPictureCallback;
Doris Liu6432cd62013-06-13 17:20:31 -070057import com.android.camera.CameraManager.CameraProxy;
Angus Kongfd4fc0e2013-11-07 15:38:09 -080058import com.android.camera.app.MediaSaver;
Angus Kongb50b5cb2013-08-09 14:55:20 -070059import com.android.camera.app.OrientationManager;
ztenghuia16e7b52013-08-23 11:47:56 -070060import com.android.camera.exif.ExifInterface;
Michael Kolb8872c232013-01-29 10:33:22 -080061import com.android.camera.ui.RotateTextToast;
Sascha Haeberling638e6f02013-09-18 14:28:51 -070062import com.android.camera.util.AccessibilityUtils;
63import com.android.camera.util.ApiHelper;
Angus Kongb50b5cb2013-08-09 14:55:20 -070064import com.android.camera.util.CameraUtil;
Sascha Haeberling8e963a52013-08-06 11:43:02 -070065import com.android.camera.util.UsageStatistics;
66import com.android.camera2.R;
Michael Kolb8872c232013-01-29 10:33:22 -080067
Angus Kongb50b5cb2013-08-09 14:55:20 -070068import java.io.File;
69import java.io.IOException;
70import java.text.SimpleDateFormat;
71import java.util.Date;
72import java.util.Iterator;
73import java.util.List;
74
Michael Kolb8872c232013-01-29 10:33:22 -080075public class VideoModule implements CameraModule,
Doris Liu6827ce22013-03-12 19:24:28 -070076 VideoController,
Michael Kolb8872c232013-01-29 10:33:22 -080077 CameraPreference.OnPreferenceChangedListener,
78 ShutterButton.OnShutterButtonListener,
79 MediaRecorder.OnErrorListener,
Doris Liu3973deb2013-08-21 13:42:22 -070080 MediaRecorder.OnInfoListener {
Michael Kolb8872c232013-01-29 10:33:22 -080081
82 private static final String TAG = "CAM_VideoModule";
83
Michael Kolb8872c232013-01-29 10:33:22 -080084 private static final int CHECK_DISPLAY_ROTATION = 3;
85 private static final int CLEAR_SCREEN_DELAY = 4;
86 private static final int UPDATE_RECORD_TIME = 5;
87 private static final int ENABLE_SHUTTER_BUTTON = 6;
88 private static final int SHOW_TAP_TO_SNAPSHOT_TOAST = 7;
89 private static final int SWITCH_CAMERA = 8;
90 private static final int SWITCH_CAMERA_START_ANIMATION = 9;
Michael Kolb8872c232013-01-29 10:33:22 -080091
92 private static final int SCREEN_DELAY = 2 * 60 * 1000;
93
94 private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
95
96 /**
97 * An unpublished intent flag requesting to start recording straight away
98 * and return as soon as recording is stopped.
99 * TODO: consider publishing by moving into MediaStore.
100 */
101 private static final String EXTRA_QUICK_CAPTURE =
102 "android.intent.extra.quickCapture";
103
Michael Kolb8872c232013-01-29 10:33:22 -0800104 // module fields
105 private CameraActivity mActivity;
Michael Kolb8872c232013-01-29 10:33:22 -0800106 private boolean mPaused;
107 private int mCameraId;
108 private Parameters mParameters;
109
Doris Liu6432cd62013-06-13 17:20:31 -0700110 private boolean mIsInReviewMode;
Michael Kolb8872c232013-01-29 10:33:22 -0800111 private boolean mSnapshotInProgress = false;
112
Michael Kolb8872c232013-01-29 10:33:22 -0800113 private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
114
115 private ComboPreferences mPreferences;
116 private PreferenceGroup mPreferenceGroup;
Angus Kong395ee2d2013-07-15 12:42:41 -0700117 // Preference must be read before starting preview. We check this before starting
118 // preview.
119 private boolean mPreferenceRead;
Michael Kolb8872c232013-01-29 10:33:22 -0800120
Michael Kolb8872c232013-01-29 10:33:22 -0800121 private boolean mIsVideoCaptureIntent;
122 private boolean mQuickCapture;
123
124 private MediaRecorder mMediaRecorder;
Michael Kolb8872c232013-01-29 10:33:22 -0800125
126 private boolean mSwitchingCamera;
127 private boolean mMediaRecorderRecording = false;
128 private long mRecordingStartTime;
129 private boolean mRecordingTimeCountsDown = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800130 private long mOnResumeTime;
131 // The video file that the hardware camera is about to record into
132 // (or is recording into.)
133 private String mVideoFilename;
134 private ParcelFileDescriptor mVideoFileDescriptor;
135
136 // The video file that has already been recorded, and that is being
137 // examined by the user.
138 private String mCurrentVideoFilename;
139 private Uri mCurrentVideoUri;
ztenghui70bd0242013-10-14 11:15:44 -0700140 private boolean mCurrentVideoUriFromMediaSaved;
Michael Kolb8872c232013-01-29 10:33:22 -0800141 private ContentValues mCurrentVideoValues;
142
143 private CamcorderProfile mProfile;
144
145 // The video duration limit. 0 menas no limit.
146 private int mMaxVideoDurationInMs;
147
148 // Time Lapse parameters.
149 private boolean mCaptureTimeLapse = false;
150 // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
151 private int mTimeBetweenTimeLapseFrameCaptureMs = 0;
Michael Kolb8872c232013-01-29 10:33:22 -0800152
153 boolean mPreviewing = false; // True if preview is started.
154 // The display rotation in degrees. This is only valid when mPreviewing is
155 // true.
156 private int mDisplayRotation;
157 private int mCameraDisplayOrientation;
158
Doris Liu6827ce22013-03-12 19:24:28 -0700159 private int mDesiredPreviewWidth;
160 private int mDesiredPreviewHeight;
Michael Kolb8872c232013-01-29 10:33:22 -0800161 private ContentResolver mContentResolver;
162
163 private LocationManager mLocationManager;
Doris Liu6432cd62013-06-13 17:20:31 -0700164 private OrientationManager mOrientationManager;
Michael Kolb8872c232013-01-29 10:33:22 -0800165
Michael Kolb8872c232013-01-29 10:33:22 -0800166 private int mPendingSwitchCameraId;
Michael Kolb8872c232013-01-29 10:33:22 -0800167 private final Handler mHandler = new MainHandler();
Doris Liu6827ce22013-03-12 19:24:28 -0700168 private VideoUI mUI;
Doris Liu6432cd62013-06-13 17:20:31 -0700169 private CameraProxy mCameraDevice;
170
Michael Kolb8872c232013-01-29 10:33:22 -0800171 // The degrees of the device rotated clockwise from its natural orientation.
172 private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
173
174 private int mZoomValue; // The current zoom value.
Doris Liu6827ce22013-03-12 19:24:28 -0700175
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800176 private final MediaSaver.OnMediaSavedListener mOnVideoSavedListener =
177 new MediaSaver.OnMediaSavedListener() {
Angus Kong83a99ae2013-04-17 15:37:07 -0700178 @Override
179 public void onMediaSaved(Uri uri) {
180 if (uri != null) {
Doris Liu2a7f44c2013-08-12 15:18:53 -0700181 mCurrentVideoUri = uri;
ztenghui70bd0242013-10-14 11:15:44 -0700182 mCurrentVideoUriFromMediaSaved = true;
Doris Liu2a7f44c2013-08-12 15:18:53 -0700183 onVideoSaved();
Sascha Haeberling37f36112013-08-06 14:31:52 -0700184 mActivity.notifyNewMedia(uri);
Angus Kong83a99ae2013-04-17 15:37:07 -0700185 }
186 }
187 };
188
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800189 private final MediaSaver.OnMediaSavedListener mOnPhotoSavedListener =
190 new MediaSaver.OnMediaSavedListener() {
Angus Kong86d36312013-01-31 18:22:44 -0800191 @Override
192 public void onMediaSaved(Uri uri) {
193 if (uri != null) {
Sascha Haeberling37f36112013-08-06 14:31:52 -0700194 mActivity.notifyNewMedia(uri);
Angus Kong86d36312013-01-31 18:22:44 -0800195 }
196 }
197 };
198
199
Michael Kolb8872c232013-01-29 10:33:22 -0800200 protected class CameraOpenThread extends Thread {
201 @Override
202 public void run() {
203 openCamera();
204 }
205 }
206
207 private void openCamera() {
Angus Kong4f795b82013-09-16 14:25:35 -0700208 if (mCameraDevice == null) {
209 mCameraDevice = CameraUtil.openCamera(
210 mActivity, mCameraId, mHandler,
211 mActivity.getCameraOpenErrorCallback());
Michael Kolb8872c232013-01-29 10:33:22 -0800212 }
Angus Kong4f795b82013-09-16 14:25:35 -0700213 if (mCameraDevice == null) {
214 // Error.
215 return;
216 }
217 mParameters = mCameraDevice.getParameters();
Michael Kolb8872c232013-01-29 10:33:22 -0800218 }
219
220 // This Handler is used to post message back onto the main thread of the
221 // application
222 private class MainHandler extends Handler {
223 @Override
224 public void handleMessage(Message msg) {
225 switch (msg.what) {
226
227 case ENABLE_SHUTTER_BUTTON:
Doris Liu6827ce22013-03-12 19:24:28 -0700228 mUI.enableShutter(true);
Michael Kolb8872c232013-01-29 10:33:22 -0800229 break;
230
231 case CLEAR_SCREEN_DELAY: {
232 mActivity.getWindow().clearFlags(
233 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
234 break;
235 }
236
237 case UPDATE_RECORD_TIME: {
238 updateRecordingTime();
239 break;
240 }
241
242 case CHECK_DISPLAY_ROTATION: {
243 // Restart the preview if display rotation has changed.
244 // Sometimes this happens when the device is held upside
245 // down and camera app is opened. Rotation animation will
246 // take some time and the rotation value we have got may be
247 // wrong. Framework does not have a callback for this now.
Angus Kongb50b5cb2013-08-09 14:55:20 -0700248 if ((CameraUtil.getDisplayRotation(mActivity) != mDisplayRotation)
Michael Kolb8872c232013-01-29 10:33:22 -0800249 && !mMediaRecorderRecording && !mSwitchingCamera) {
250 startPreview();
251 }
252 if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
253 mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
254 }
255 break;
256 }
257
258 case SHOW_TAP_TO_SNAPSHOT_TOAST: {
259 showTapToSnapshotToast();
260 break;
261 }
262
263 case SWITCH_CAMERA: {
264 switchCamera();
265 break;
266 }
267
268 case SWITCH_CAMERA_START_ANIMATION: {
Doris Liu6432cd62013-06-13 17:20:31 -0700269 //TODO:
270 //((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
Michael Kolb8872c232013-01-29 10:33:22 -0800271
272 // Enable all camera controls.
273 mSwitchingCamera = false;
274 break;
275 }
276
Michael Kolb8872c232013-01-29 10:33:22 -0800277 default:
278 Log.v(TAG, "Unhandled message: " + msg.what);
279 break;
280 }
281 }
282 }
283
284 private BroadcastReceiver mReceiver = null;
285
286 private class MyBroadcastReceiver extends BroadcastReceiver {
287 @Override
288 public void onReceive(Context context, Intent intent) {
289 String action = intent.getAction();
290 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
291 stopVideoRecording();
292 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
293 Toast.makeText(mActivity,
294 mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
295 }
296 }
297 }
298
299 private String createName(long dateTaken) {
300 Date date = new Date(dateTaken);
301 SimpleDateFormat dateFormat = new SimpleDateFormat(
302 mActivity.getString(R.string.video_file_name_format));
303
304 return dateFormat.format(date);
305 }
306
307 private int getPreferredCameraId(ComboPreferences preferences) {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700308 int intentCameraId = CameraUtil.getCameraFacingIntentExtras(mActivity);
Michael Kolb8872c232013-01-29 10:33:22 -0800309 if (intentCameraId != -1) {
310 // Testing purpose. Launch a specific camera through the intent
311 // extras.
312 return intentCameraId;
313 } else {
314 return CameraSettings.readPreferredCameraId(preferences);
315 }
316 }
317
318 private void initializeSurfaceView() {
Doris Liu6827ce22013-03-12 19:24:28 -0700319 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { // API level < 16
Doris Liu6432cd62013-06-13 17:20:31 -0700320 mUI.initializeSurfaceView();
Michael Kolb8872c232013-01-29 10:33:22 -0800321 }
322 }
323
324 @Override
Doris Liu6432cd62013-06-13 17:20:31 -0700325 public void init(CameraActivity activity, View root) {
Michael Kolb8872c232013-01-29 10:33:22 -0800326 mActivity = activity;
Doris Liu6827ce22013-03-12 19:24:28 -0700327 mUI = new VideoUI(activity, this, root);
Michael Kolb8872c232013-01-29 10:33:22 -0800328 mPreferences = new ComboPreferences(mActivity);
329 CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
330 mCameraId = getPreferredCameraId(mPreferences);
331
332 mPreferences.setLocalId(mActivity, mCameraId);
333 CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
334
Doris Liu6432cd62013-06-13 17:20:31 -0700335 mOrientationManager = new OrientationManager(mActivity);
Michael Kolb8872c232013-01-29 10:33:22 -0800336
337 /*
338 * To reduce startup time, we start the preview in another thread.
339 * We make sure the preview is started at the end of onCreate.
340 */
341 CameraOpenThread cameraOpenThread = new CameraOpenThread();
342 cameraOpenThread.start();
343
344 mContentResolver = mActivity.getContentResolver();
345
Michael Kolb8872c232013-01-29 10:33:22 -0800346 // Surface texture is from camera screen nail and startPreview needs it.
347 // This must be done before startPreview.
348 mIsVideoCaptureIntent = isVideoCaptureIntent();
Michael Kolb8872c232013-01-29 10:33:22 -0800349 initializeSurfaceView();
350
351 // Make sure camera device is opened.
352 try {
353 cameraOpenThread.join();
ztenghuief01a312013-10-14 15:25:16 -0700354 if (mCameraDevice == null) {
Michael Kolb8872c232013-01-29 10:33:22 -0800355 return;
356 }
357 } catch (InterruptedException ex) {
358 // ignore
359 }
360
361 readVideoPreferences();
Doris Liu6827ce22013-03-12 19:24:28 -0700362 mUI.setPrefChangedListener(this);
Michael Kolb8872c232013-01-29 10:33:22 -0800363
Michael Kolb8872c232013-01-29 10:33:22 -0800364 mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
365 mLocationManager = new LocationManager(mActivity, null);
366
Doris Liu6827ce22013-03-12 19:24:28 -0700367 mUI.setOrientationIndicator(0, false);
Michael Kolb8872c232013-01-29 10:33:22 -0800368 setDisplayOrientation();
369
Doris Liu6827ce22013-03-12 19:24:28 -0700370 mUI.showTimeLapseUI(mCaptureTimeLapse);
Michael Kolb8872c232013-01-29 10:33:22 -0800371 initializeVideoSnapshot();
372 resizeForPreviewAspectRatio();
373
374 initializeVideoControl();
375 mPendingSwitchCameraId = -1;
Doris Liu6827ce22013-03-12 19:24:28 -0700376 }
377
378 // SingleTapListener
379 // Preview area is touched. Take a picture.
380 @Override
381 public void onSingleTapUp(View view, int x, int y) {
Doris Liu38605742013-08-13 15:01:52 -0700382 takeASnapshot();
383 }
384
385 private void takeASnapshot() {
Sascha Haeberlinga514b142013-08-13 16:10:01 -0700386 // Only take snapshots if video snapshot is supported by device
Doris Liu38605742013-08-13 15:01:52 -0700387 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
Doris Liu3973deb2013-08-21 13:42:22 -0700388 if (!mMediaRecorderRecording || mPaused || mSnapshotInProgress) {
Sascha Haeberlinga514b142013-08-13 16:10:01 -0700389 return;
390 }
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800391 MediaSaver s = mActivity.getMediaSaver();
Sascha Haeberlinga514b142013-08-13 16:10:01 -0700392 if (s == null || s.isQueueFull()) {
Doris Liu38605742013-08-13 15:01:52 -0700393 return;
394 }
395
396 // Set rotation and gps data.
397 int rotation = CameraUtil.getJpegRotation(mCameraId, mOrientation);
398 mParameters.setRotation(rotation);
399 Location loc = mLocationManager.getCurrentLocation();
400 CameraUtil.setGpsParameters(mParameters, loc);
401 mCameraDevice.setParameters(mParameters);
402
403 Log.v(TAG, "Video snapshot start");
404 mCameraDevice.takePicture(mHandler,
405 null, null, null, new JpegPictureCallback(loc));
406 showVideoSnapshotUI(true);
407 mSnapshotInProgress = true;
408 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
409 UsageStatistics.ACTION_CAPTURE_DONE, "VideoSnapshot");
Doris Liu6827ce22013-03-12 19:24:28 -0700410 }
Michael Kolb8872c232013-01-29 10:33:22 -0800411 }
412
413 @Override
414 public void onStop() {}
415
416 private void loadCameraPreferences() {
417 CameraSettings settings = new CameraSettings(mActivity, mParameters,
418 mCameraId, CameraHolder.instance().getCameraInfo());
419 // Remove the video quality preference setting when the quality is given in the intent.
420 mPreferenceGroup = filterPreferenceScreenByIntent(
421 settings.getPreferenceGroup(R.xml.video_preferences));
422 }
423
Michael Kolb8872c232013-01-29 10:33:22 -0800424 private void initializeVideoControl() {
425 loadCameraPreferences();
Doris Liu6827ce22013-03-12 19:24:28 -0700426 mUI.initializePopup(mPreferenceGroup);
Michael Kolb8872c232013-01-29 10:33:22 -0800427 }
428
Michael Kolb8872c232013-01-29 10:33:22 -0800429 @Override
430 public void onOrientationChanged(int orientation) {
431 // We keep the last known orientation. So if the user first orient
432 // the camera then point the camera to floor or sky, we still have
433 // the correct orientation.
434 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return;
Angus Kongb50b5cb2013-08-09 14:55:20 -0700435 int newOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800436
437 if (mOrientation != newOrientation) {
438 mOrientation = newOrientation;
Michael Kolb8872c232013-01-29 10:33:22 -0800439 }
440
441 // Show the toast after getting the first orientation changed.
442 if (mHandler.hasMessages(SHOW_TAP_TO_SNAPSHOT_TOAST)) {
443 mHandler.removeMessages(SHOW_TAP_TO_SNAPSHOT_TOAST);
444 showTapToSnapshotToast();
445 }
446 }
447
Michael Kolb8872c232013-01-29 10:33:22 -0800448 private void startPlayVideoActivity() {
449 Intent intent = new Intent(Intent.ACTION_VIEW);
450 intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
451 try {
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700452 mActivity
453 .startActivityForResult(intent, CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
Michael Kolb8872c232013-01-29 10:33:22 -0800454 } catch (ActivityNotFoundException ex) {
455 Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
456 }
457 }
458
ztenghui7b265a62013-09-09 14:58:44 -0700459 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800460 @OnClickAttr
461 public void onReviewPlayClicked(View v) {
462 startPlayVideoActivity();
463 }
464
ztenghui7b265a62013-09-09 14:58:44 -0700465 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800466 @OnClickAttr
467 public void onReviewDoneClicked(View v) {
Doris Liu69ef5ea2013-05-07 13:48:10 -0700468 mIsInReviewMode = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800469 doReturnToCaller(true);
470 }
471
ztenghui7b265a62013-09-09 14:58:44 -0700472 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800473 @OnClickAttr
474 public void onReviewCancelClicked(View v) {
ztenghuiaf3a1972013-09-17 16:51:15 -0700475 // TODO: It should be better to not even insert the URI at all before we
476 // confirm done in review, which means we need to handle temporary video
477 // files in a quite different way than we currently had.
ztenghui70bd0242013-10-14 11:15:44 -0700478 // Make sure we don't delete the Uri sent from the video capture intent.
479 if (mCurrentVideoUriFromMediaSaved) {
ztenghuidfe5b152013-10-08 17:07:15 -0700480 mContentResolver.delete(mCurrentVideoUri, null, null);
481 }
ztenghui638bf9a2013-10-09 10:52:33 -0700482 mIsInReviewMode = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800483 doReturnToCaller(false);
484 }
485
Doris Liu69ef5ea2013-05-07 13:48:10 -0700486 @Override
487 public boolean isInReviewMode() {
488 return mIsInReviewMode;
489 }
490
Michael Kolb8872c232013-01-29 10:33:22 -0800491 private void onStopVideoRecording() {
Michael Kolb8872c232013-01-29 10:33:22 -0800492 boolean recordFail = stopVideoRecording();
493 if (mIsVideoCaptureIntent) {
Doris Liu3973deb2013-08-21 13:42:22 -0700494 if (mQuickCapture) {
495 doReturnToCaller(!recordFail);
496 } else if (!recordFail) {
497 showCaptureResult();
Michael Kolb8872c232013-01-29 10:33:22 -0800498 }
499 } else if (!recordFail){
500 // Start capture animation.
501 if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
502 // The capture animation is disabled on ICS because we use SurfaceView
503 // for preview during recording. When the recording is done, we switch
504 // back to use SurfaceTexture for preview and we need to stop then start
505 // the preview. This will cause the preview flicker since the preview
506 // will not be continuous for a short period of time.
Sascha Haeberling4f91ab52013-05-21 11:26:13 -0700507
Sascha Haeberling37f36112013-08-06 14:31:52 -0700508 mUI.animateFlash();
Doris Liu3973deb2013-08-21 13:42:22 -0700509 mUI.animateCapture();
Michael Kolb8872c232013-01-29 10:33:22 -0800510 }
511 }
512 }
513
Doris Liu2a7f44c2013-08-12 15:18:53 -0700514 public void onVideoSaved() {
515 if (mIsVideoCaptureIntent) {
516 showCaptureResult();
517 }
518 }
519
Michael Kolb8872c232013-01-29 10:33:22 -0800520 public void onProtectiveCurtainClick(View v) {
521 // Consume clicks
522 }
523
524 @Override
525 public void onShutterButtonClick() {
Doris Liu6827ce22013-03-12 19:24:28 -0700526 if (mUI.collapseCameraControls() || mSwitchingCamera) return;
Michael Kolb8872c232013-01-29 10:33:22 -0800527
528 boolean stop = mMediaRecorderRecording;
529
530 if (stop) {
531 onStopVideoRecording();
532 } else {
533 startVideoRecording();
534 }
Doris Liu6827ce22013-03-12 19:24:28 -0700535 mUI.enableShutter(false);
Michael Kolb8872c232013-01-29 10:33:22 -0800536
537 // Keep the shutter button disabled when in video capture intent
538 // mode and recording is stopped. It'll be re-enabled when
539 // re-take button is clicked.
540 if (!(mIsVideoCaptureIntent && stop)) {
541 mHandler.sendEmptyMessageDelayed(
542 ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
543 }
544 }
545
546 @Override
547 public void onShutterButtonFocus(boolean pressed) {
Doris Liu61f2b082013-03-27 17:25:43 -0700548 mUI.setShutterPressed(pressed);
Michael Kolb8872c232013-01-29 10:33:22 -0800549 }
550
551 private void readVideoPreferences() {
552 // The preference stores values from ListPreference and is thus string type for all values.
553 // We need to convert it to int manually.
Doris Liu3f7e0042013-07-31 11:25:09 -0700554 String videoQuality = mPreferences.getString(CameraSettings.KEY_VIDEO_QUALITY,
555 null);
556 if (videoQuality == null) {
557 // check for highest quality before setting default value
558 videoQuality = CameraSettings.getSupportedHighestVideoQuality(mCameraId,
559 mActivity.getResources().getString(R.string.pref_video_quality_default));
560 mPreferences.edit().putString(CameraSettings.KEY_VIDEO_QUALITY, videoQuality);
561 }
Michael Kolb8872c232013-01-29 10:33:22 -0800562 int quality = Integer.valueOf(videoQuality);
563
564 // Set video quality.
565 Intent intent = mActivity.getIntent();
566 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
567 int extraVideoQuality =
568 intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
569 if (extraVideoQuality > 0) {
570 quality = CamcorderProfile.QUALITY_HIGH;
571 } else { // 0 is mms.
572 quality = CamcorderProfile.QUALITY_LOW;
573 }
574 }
575
576 // Set video duration limit. The limit is read from the preference,
577 // unless it is specified in the intent.
578 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
579 int seconds =
580 intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
581 mMaxVideoDurationInMs = 1000 * seconds;
582 } else {
583 mMaxVideoDurationInMs = CameraSettings.getMaxVideoDuration(mActivity);
584 }
585
Michael Kolb8872c232013-01-29 10:33:22 -0800586 // Read time lapse recording interval.
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700587 String frameIntervalStr = mPreferences.getString(
588 CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
589 mActivity.getString(R.string.pref_video_time_lapse_frame_interval_default));
590 mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
591 mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
Michael Kolb8872c232013-01-29 10:33:22 -0800592 // TODO: This should be checked instead directly +1000.
593 if (mCaptureTimeLapse) quality += 1000;
594 mProfile = CamcorderProfile.get(mCameraId, quality);
595 getDesiredPreviewSize();
Angus Kong395ee2d2013-07-15 12:42:41 -0700596 mPreferenceRead = true;
Michael Kolb8872c232013-01-29 10:33:22 -0800597 }
598
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700599 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
Michael Kolb8872c232013-01-29 10:33:22 -0800600 private void getDesiredPreviewSize() {
ztenghuief01a312013-10-14 15:25:16 -0700601 if (mCameraDevice == null) {
602 return;
603 }
Doris Liu6432cd62013-06-13 17:20:31 -0700604 mParameters = mCameraDevice.getParameters();
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700605 if (mParameters.getSupportedVideoSizes() == null) {
Michael Kolb8872c232013-01-29 10:33:22 -0800606 mDesiredPreviewWidth = mProfile.videoFrameWidth;
607 mDesiredPreviewHeight = mProfile.videoFrameHeight;
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700608 } else { // Driver supports separates outputs for preview and video.
609 List<Size> sizes = mParameters.getSupportedPreviewSizes();
610 Size preferred = mParameters.getPreferredPreviewSizeForVideo();
611 int product = preferred.width * preferred.height;
612 Iterator<Size> it = sizes.iterator();
613 // Remove the preview sizes that are not preferred.
614 while (it.hasNext()) {
615 Size size = it.next();
616 if (size.width * size.height > product) {
617 it.remove();
618 }
619 }
620 Size optimalSize = CameraUtil.getOptimalPreviewSize(mActivity, sizes,
621 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
622 mDesiredPreviewWidth = optimalSize.width;
623 mDesiredPreviewHeight = optimalSize.height;
Michael Kolb8872c232013-01-29 10:33:22 -0800624 }
Doris Liu6432cd62013-06-13 17:20:31 -0700625 mUI.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800626 Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
627 ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
628 }
629
630 private void resizeForPreviewAspectRatio() {
Doris Liu6827ce22013-03-12 19:24:28 -0700631 mUI.setAspectRatio(
Michael Kolb8872c232013-01-29 10:33:22 -0800632 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
633 }
634
635 @Override
636 public void installIntentFilter() {
637 // install an intent filter to receive SD card related events.
638 IntentFilter intentFilter =
639 new IntentFilter(Intent.ACTION_MEDIA_EJECT);
640 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
641 intentFilter.addDataScheme("file");
642 mReceiver = new MyBroadcastReceiver();
643 mActivity.registerReceiver(mReceiver, intentFilter);
644 }
645
646 @Override
647 public void onResumeBeforeSuper() {
648 mPaused = false;
649 }
650
651 @Override
652 public void onResumeAfterSuper() {
Doris Liu6827ce22013-03-12 19:24:28 -0700653 mUI.enableShutter(false);
Michael Kolb8872c232013-01-29 10:33:22 -0800654 mZoomValue = 0;
655
656 showVideoSnapshotUI(false);
657
Michael Kolb8872c232013-01-29 10:33:22 -0800658 if (!mPreviewing) {
Michael Kolb8872c232013-01-29 10:33:22 -0800659 openCamera();
ztenghuief01a312013-10-14 15:25:16 -0700660 if (mCameraDevice == null) {
Michael Kolb8872c232013-01-29 10:33:22 -0800661 return;
662 }
663 readVideoPreferences();
664 resizeForPreviewAspectRatio();
Angus Kong395ee2d2013-07-15 12:42:41 -0700665 startPreview();
Doris Liuc774ff92013-03-20 19:25:47 -0700666 } else {
667 // preview already started
668 mUI.enableShutter(true);
Michael Kolb8872c232013-01-29 10:33:22 -0800669 }
670
Doris Liu59390062013-10-10 17:20:56 -0700671 mUI.initDisplayChangeListener();
Michael Kolb8872c232013-01-29 10:33:22 -0800672 // Initializing it here after the preview is started.
Doris Liu6827ce22013-03-12 19:24:28 -0700673 mUI.initializeZoom(mParameters);
Michael Kolb8872c232013-01-29 10:33:22 -0800674
675 keepScreenOnAwhile();
676
Angus Kongce2b9492013-09-05 17:49:06 -0700677 mOrientationManager.resume();
Michael Kolb8872c232013-01-29 10:33:22 -0800678 // Initialize location service.
679 boolean recordLocation = RecordLocationPreference.get(mPreferences,
680 mContentResolver);
681 mLocationManager.recordLocation(recordLocation);
682
683 if (mPreviewing) {
684 mOnResumeTime = SystemClock.uptimeMillis();
685 mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
686 }
Michael Kolb8872c232013-01-29 10:33:22 -0800687
Bobby Georgescu0a7dd572013-03-12 22:45:17 -0700688 UsageStatistics.onContentViewChanged(
689 UsageStatistics.COMPONENT_CAMERA, "VideoModule");
Michael Kolb8872c232013-01-29 10:33:22 -0800690 }
691
692 private void setDisplayOrientation() {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700693 mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
694 mCameraDisplayOrientation = CameraUtil.getDisplayOrientation(mDisplayRotation, mCameraId);
Doris Liu6432cd62013-06-13 17:20:31 -0700695 // Change the camera display orientation
696 if (mCameraDevice != null) {
697 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800698 }
Doris Liu6432cd62013-06-13 17:20:31 -0700699 }
700
701 @Override
702 public void updateCameraOrientation() {
703 if (mMediaRecorderRecording) return;
Angus Kongb50b5cb2013-08-09 14:55:20 -0700704 if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
Doris Liu6432cd62013-06-13 17:20:31 -0700705 setDisplayOrientation();
706 }
Michael Kolb8872c232013-01-29 10:33:22 -0800707 }
708
Doris Liu6827ce22013-03-12 19:24:28 -0700709 @Override
710 public int onZoomChanged(int index) {
711 // Not useful to change zoom value when the activity is paused.
712 if (mPaused) return index;
713 mZoomValue = index;
Doris Liu6432cd62013-06-13 17:20:31 -0700714 if (mParameters == null || mCameraDevice == null) return index;
Doris Liu6827ce22013-03-12 19:24:28 -0700715 // Set zoom parameters asynchronously
716 mParameters.setZoom(mZoomValue);
Doris Liu6432cd62013-06-13 17:20:31 -0700717 mCameraDevice.setParameters(mParameters);
718 Parameters p = mCameraDevice.getParameters();
Doris Liu6827ce22013-03-12 19:24:28 -0700719 if (p != null) return p.getZoom();
720 return index;
721 }
Angus Kong395ee2d2013-07-15 12:42:41 -0700722
Michael Kolb8872c232013-01-29 10:33:22 -0800723 private void startPreview() {
724 Log.v(TAG, "startPreview");
725
Angus Kong395ee2d2013-07-15 12:42:41 -0700726 SurfaceTexture surfaceTexture = mUI.getSurfaceTexture();
ztenghuief01a312013-10-14 15:25:16 -0700727 if (!mPreferenceRead || surfaceTexture == null || mPaused == true ||
728 mCameraDevice == null) {
729 return;
730 }
Angus Kong395ee2d2013-07-15 12:42:41 -0700731
Doris Liu6432cd62013-06-13 17:20:31 -0700732 mCameraDevice.setErrorCallback(mErrorCallback);
Michael Kolb8872c232013-01-29 10:33:22 -0800733 if (mPreviewing == true) {
734 stopPreview();
Michael Kolb8872c232013-01-29 10:33:22 -0800735 }
736
Michael Kolb8872c232013-01-29 10:33:22 -0800737 setDisplayOrientation();
Doris Liu6432cd62013-06-13 17:20:31 -0700738 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800739 setCameraParameters();
740
741 try {
Doris Liu3973deb2013-08-21 13:42:22 -0700742 mCameraDevice.setPreviewTexture(surfaceTexture);
743 mCameraDevice.startPreview();
744 mPreviewing = true;
745 onPreviewStarted();
Michael Kolb8872c232013-01-29 10:33:22 -0800746 } catch (Throwable ex) {
747 closeCamera();
748 throw new RuntimeException("startPreview failed", ex);
Michael Kolb8872c232013-01-29 10:33:22 -0800749 }
Michael Kolbb1aeb392013-03-11 12:37:40 -0700750 }
751
752 private void onPreviewStarted() {
Doris Liu6827ce22013-03-12 19:24:28 -0700753 mUI.enableShutter(true);
Michael Kolb8872c232013-01-29 10:33:22 -0800754 }
755
Doris Liu6827ce22013-03-12 19:24:28 -0700756 @Override
757 public void stopPreview() {
Doris Liu6432cd62013-06-13 17:20:31 -0700758 if (!mPreviewing) return;
759 mCameraDevice.stopPreview();
Michael Kolb8872c232013-01-29 10:33:22 -0800760 mPreviewing = false;
761 }
762
Michael Kolb8872c232013-01-29 10:33:22 -0800763 private void closeCamera() {
Michael Kolb8872c232013-01-29 10:33:22 -0800764 Log.v(TAG, "closeCamera");
Doris Liu6432cd62013-06-13 17:20:31 -0700765 if (mCameraDevice == null) {
Michael Kolb8872c232013-01-29 10:33:22 -0800766 Log.d(TAG, "already stopped.");
767 return;
768 }
Doris Liu6432cd62013-06-13 17:20:31 -0700769 mCameraDevice.setZoomChangeListener(null);
770 mCameraDevice.setErrorCallback(null);
Angus Kong9ef99252013-07-18 18:04:19 -0700771 CameraHolder.instance().release();
Angus Kong395ee2d2013-07-15 12:42:41 -0700772 mCameraDevice = null;
Michael Kolb8872c232013-01-29 10:33:22 -0800773 mPreviewing = false;
774 mSnapshotInProgress = false;
775 }
776
777 private void releasePreviewResources() {
Doris Liu6432cd62013-06-13 17:20:31 -0700778 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
779 mUI.hideSurfaceView();
Michael Kolb8872c232013-01-29 10:33:22 -0800780 }
781 }
782
783 @Override
784 public void onPauseBeforeSuper() {
785 mPaused = true;
786
Doris Liu3a45c332013-10-15 19:10:28 -0700787 mUI.showPreviewCover();
Michael Kolb8872c232013-01-29 10:33:22 -0800788 if (mMediaRecorderRecording) {
789 // Camera will be released in onStopVideoRecording.
790 onStopVideoRecording();
791 } else {
792 closeCamera();
Doris Liu3973deb2013-08-21 13:42:22 -0700793 releaseMediaRecorder();
Michael Kolb8872c232013-01-29 10:33:22 -0800794 }
Doris Liu3973deb2013-08-21 13:42:22 -0700795
796 closeVideoFileDescriptor();
797
Michael Kolb8872c232013-01-29 10:33:22 -0800798
799 releasePreviewResources();
800
801 if (mReceiver != null) {
802 mActivity.unregisterReceiver(mReceiver);
803 mReceiver = null;
804 }
805 resetScreenOn();
806
807 if (mLocationManager != null) mLocationManager.recordLocation(false);
Angus Kongce2b9492013-09-05 17:49:06 -0700808 mOrientationManager.pause();
Michael Kolb8872c232013-01-29 10:33:22 -0800809
810 mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
811 mHandler.removeMessages(SWITCH_CAMERA);
812 mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION);
813 mPendingSwitchCameraId = -1;
814 mSwitchingCamera = false;
Angus Kong395ee2d2013-07-15 12:42:41 -0700815 mPreferenceRead = false;
Doris Liuc3679c02013-08-08 18:08:43 -0700816
817 mUI.collapseCameraControls();
Doris Liu59390062013-10-10 17:20:56 -0700818 mUI.removeDisplayChangeListener();
Michael Kolb8872c232013-01-29 10:33:22 -0800819 }
820
821 @Override
822 public void onPauseAfterSuper() {
823 }
824
825 @Override
826 public void onUserInteraction() {
827 if (!mMediaRecorderRecording && !mActivity.isFinishing()) {
828 keepScreenOnAwhile();
829 }
830 }
831
832 @Override
833 public boolean onBackPressed() {
834 if (mPaused) return true;
835 if (mMediaRecorderRecording) {
836 onStopVideoRecording();
837 return true;
Doris Liu6827ce22013-03-12 19:24:28 -0700838 } else if (mUI.hidePieRenderer()) {
Michael Kolb8872c232013-01-29 10:33:22 -0800839 return true;
840 } else {
Doris Liu6827ce22013-03-12 19:24:28 -0700841 return mUI.removeTopLevelPopup();
Michael Kolb8872c232013-01-29 10:33:22 -0800842 }
843 }
844
845 @Override
846 public boolean onKeyDown(int keyCode, KeyEvent event) {
847 // Do not handle any key if the activity is paused.
848 if (mPaused) {
849 return true;
850 }
851
852 switch (keyCode) {
853 case KeyEvent.KEYCODE_CAMERA:
854 if (event.getRepeatCount() == 0) {
Doris Liu6827ce22013-03-12 19:24:28 -0700855 mUI.clickShutter();
Michael Kolb8872c232013-01-29 10:33:22 -0800856 return true;
857 }
858 break;
859 case KeyEvent.KEYCODE_DPAD_CENTER:
860 if (event.getRepeatCount() == 0) {
Doris Liu6827ce22013-03-12 19:24:28 -0700861 mUI.clickShutter();
Michael Kolb8872c232013-01-29 10:33:22 -0800862 return true;
863 }
864 break;
865 case KeyEvent.KEYCODE_MENU:
866 if (mMediaRecorderRecording) return true;
867 break;
868 }
869 return false;
870 }
871
872 @Override
873 public boolean onKeyUp(int keyCode, KeyEvent event) {
874 switch (keyCode) {
875 case KeyEvent.KEYCODE_CAMERA:
Doris Liu6827ce22013-03-12 19:24:28 -0700876 mUI.pressShutter(false);
Michael Kolb8872c232013-01-29 10:33:22 -0800877 return true;
878 }
879 return false;
880 }
881
Doris Liu6827ce22013-03-12 19:24:28 -0700882 @Override
883 public boolean isVideoCaptureIntent() {
Michael Kolb8872c232013-01-29 10:33:22 -0800884 String action = mActivity.getIntent().getAction();
885 return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
886 }
887
888 private void doReturnToCaller(boolean valid) {
889 Intent resultIntent = new Intent();
890 int resultCode;
891 if (valid) {
892 resultCode = Activity.RESULT_OK;
893 resultIntent.setData(mCurrentVideoUri);
894 } else {
895 resultCode = Activity.RESULT_CANCELED;
896 }
897 mActivity.setResultEx(resultCode, resultIntent);
898 mActivity.finish();
899 }
900
901 private void cleanupEmptyFile() {
902 if (mVideoFilename != null) {
903 File f = new File(mVideoFilename);
904 if (f.length() == 0 && f.delete()) {
905 Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
906 mVideoFilename = null;
907 }
908 }
909 }
910
911 private void setupMediaRecorderPreviewDisplay() {
912 // Nothing to do here if using SurfaceTexture.
Doris Liu6432cd62013-06-13 17:20:31 -0700913 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
Michael Kolb8872c232013-01-29 10:33:22 -0800914 // We stop the preview here before unlocking the device because we
915 // need to change the SurfaceTexture to SurfaceView for preview.
916 stopPreview();
Angus Kong9ef99252013-07-18 18:04:19 -0700917 mCameraDevice.setPreviewDisplay(mUI.getSurfaceHolder());
Michael Kolb8872c232013-01-29 10:33:22 -0800918 // The orientation for SurfaceTexture is different from that for
919 // SurfaceView. For SurfaceTexture we don't need to consider the
920 // display rotation. Just consider the sensor's orientation and we
921 // will set the orientation correctly when showing the texture.
922 // Gallery will handle the orientation for the preview. For
923 // SurfaceView we will have to take everything into account so the
924 // display rotation is considered.
Doris Liu6432cd62013-06-13 17:20:31 -0700925 mCameraDevice.setDisplayOrientation(
Angus Kongb50b5cb2013-08-09 14:55:20 -0700926 CameraUtil.getDisplayOrientation(mDisplayRotation, mCameraId));
Angus Kong9ef99252013-07-18 18:04:19 -0700927 mCameraDevice.startPreview();
Michael Kolb8872c232013-01-29 10:33:22 -0800928 mPreviewing = true;
Doris Liu6827ce22013-03-12 19:24:28 -0700929 mMediaRecorder.setPreviewDisplay(mUI.getSurfaceHolder().getSurface());
Michael Kolb8872c232013-01-29 10:33:22 -0800930 }
931 }
932
933 // Prepares media recorder.
934 private void initializeRecorder() {
935 Log.v(TAG, "initializeRecorder");
936 // If the mCameraDevice is null, then this activity is going to finish
Doris Liu6432cd62013-06-13 17:20:31 -0700937 if (mCameraDevice == null) return;
Michael Kolb8872c232013-01-29 10:33:22 -0800938
Doris Liu6432cd62013-06-13 17:20:31 -0700939 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
Michael Kolb8872c232013-01-29 10:33:22 -0800940 // Set the SurfaceView to visible so the surface gets created.
941 // surfaceCreated() is called immediately when the visibility is
942 // changed to visible. Thus, mSurfaceViewReady should become true
943 // right after calling setVisibility().
Doris Liu6827ce22013-03-12 19:24:28 -0700944 mUI.showSurfaceView();
Michael Kolb8872c232013-01-29 10:33:22 -0800945 }
946
947 Intent intent = mActivity.getIntent();
948 Bundle myExtras = intent.getExtras();
949
950 long requestedSizeLimit = 0;
951 closeVideoFileDescriptor();
ztenghui70bd0242013-10-14 11:15:44 -0700952 mCurrentVideoUriFromMediaSaved = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800953 if (mIsVideoCaptureIntent && myExtras != null) {
954 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
955 if (saveUri != null) {
956 try {
957 mVideoFileDescriptor =
958 mContentResolver.openFileDescriptor(saveUri, "rw");
959 mCurrentVideoUri = saveUri;
960 } catch (java.io.FileNotFoundException ex) {
961 // invalid uri
962 Log.e(TAG, ex.toString());
963 }
964 }
965 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
966 }
967 mMediaRecorder = new MediaRecorder();
968
969 setupMediaRecorderPreviewDisplay();
970 // Unlock the camera object before passing it to media recorder.
Doris Liu6432cd62013-06-13 17:20:31 -0700971 mCameraDevice.unlock();
Doris Liu6432cd62013-06-13 17:20:31 -0700972 mMediaRecorder.setCamera(mCameraDevice.getCamera());
Michael Kolb8872c232013-01-29 10:33:22 -0800973 if (!mCaptureTimeLapse) {
974 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
975 }
976 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
977 mMediaRecorder.setProfile(mProfile);
978 mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
979 if (mCaptureTimeLapse) {
980 double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs;
981 setCaptureRate(mMediaRecorder, fps);
982 }
983
984 setRecordLocation();
985
986 // Set output file.
987 // Try Uri in the intent first. If it doesn't exist, use our own
988 // instead.
989 if (mVideoFileDescriptor != null) {
990 mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
991 } else {
992 generateVideoFilename(mProfile.fileFormat);
993 mMediaRecorder.setOutputFile(mVideoFilename);
994 }
995
996 // Set maximum file size.
Angus Kong2dcc0a92013-09-25 13:00:08 -0700997 long maxFileSize = mActivity.getStorageSpaceBytes() - Storage.LOW_STORAGE_THRESHOLD_BYTES;
Michael Kolb8872c232013-01-29 10:33:22 -0800998 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
999 maxFileSize = requestedSizeLimit;
1000 }
1001
1002 try {
1003 mMediaRecorder.setMaxFileSize(maxFileSize);
1004 } catch (RuntimeException exception) {
1005 // We are going to ignore failure of setMaxFileSize here, as
1006 // a) The composer selected may simply not support it, or
1007 // b) The underlying media framework may not handle 64-bit range
1008 // on the size restriction.
1009 }
1010
1011 // See android.hardware.Camera.Parameters.setRotation for
1012 // documentation.
1013 // Note that mOrientation here is the device orientation, which is the opposite of
1014 // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
1015 // which is the orientation the graphics need to rotate in order to render correctly.
1016 int rotation = 0;
1017 if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1018 CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1019 if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
1020 rotation = (info.orientation - mOrientation + 360) % 360;
1021 } else { // back-facing camera
1022 rotation = (info.orientation + mOrientation) % 360;
1023 }
1024 }
1025 mMediaRecorder.setOrientationHint(rotation);
1026
1027 try {
1028 mMediaRecorder.prepare();
1029 } catch (IOException e) {
1030 Log.e(TAG, "prepare failed for " + mVideoFilename, e);
1031 releaseMediaRecorder();
1032 throw new RuntimeException(e);
1033 }
1034
1035 mMediaRecorder.setOnErrorListener(this);
1036 mMediaRecorder.setOnInfoListener(this);
1037 }
1038
Michael Kolb8872c232013-01-29 10:33:22 -08001039 private static void setCaptureRate(MediaRecorder recorder, double fps) {
1040 recorder.setCaptureRate(fps);
1041 }
1042
Michael Kolb8872c232013-01-29 10:33:22 -08001043 private void setRecordLocation() {
Sascha Haeberling638e6f02013-09-18 14:28:51 -07001044 Location loc = mLocationManager.getCurrentLocation();
1045 if (loc != null) {
1046 mMediaRecorder.setLocation((float) loc.getLatitude(),
1047 (float) loc.getLongitude());
Michael Kolb8872c232013-01-29 10:33:22 -08001048 }
1049 }
1050
Michael Kolb8872c232013-01-29 10:33:22 -08001051 private void releaseMediaRecorder() {
1052 Log.v(TAG, "Releasing media recorder.");
1053 if (mMediaRecorder != null) {
1054 cleanupEmptyFile();
1055 mMediaRecorder.reset();
1056 mMediaRecorder.release();
1057 mMediaRecorder = null;
1058 }
1059 mVideoFilename = null;
1060 }
1061
Michael Kolb8872c232013-01-29 10:33:22 -08001062 private void generateVideoFilename(int outputFileFormat) {
1063 long dateTaken = System.currentTimeMillis();
1064 String title = createName(dateTaken);
1065 // Used when emailing.
1066 String filename = title + convertOutputFormatToFileExt(outputFileFormat);
1067 String mime = convertOutputFormatToMimeType(outputFileFormat);
1068 String path = Storage.DIRECTORY + '/' + filename;
1069 String tmpPath = path + ".tmp";
Ruben Brunk16007962013-04-19 15:27:57 -07001070 mCurrentVideoValues = new ContentValues(9);
Michael Kolb8872c232013-01-29 10:33:22 -08001071 mCurrentVideoValues.put(Video.Media.TITLE, title);
1072 mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
1073 mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
Ruben Brunk16007962013-04-19 15:27:57 -07001074 mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000);
Michael Kolb8872c232013-01-29 10:33:22 -08001075 mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
1076 mCurrentVideoValues.put(Video.Media.DATA, path);
1077 mCurrentVideoValues.put(Video.Media.RESOLUTION,
1078 Integer.toString(mProfile.videoFrameWidth) + "x" +
1079 Integer.toString(mProfile.videoFrameHeight));
1080 Location loc = mLocationManager.getCurrentLocation();
1081 if (loc != null) {
1082 mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
1083 mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
1084 }
Michael Kolb8872c232013-01-29 10:33:22 -08001085 mVideoFilename = tmpPath;
1086 Log.v(TAG, "New video filename: " + mVideoFilename);
1087 }
1088
Angus Kong83a99ae2013-04-17 15:37:07 -07001089 private void saveVideo() {
Michael Kolb8872c232013-01-29 10:33:22 -08001090 if (mVideoFileDescriptor == null) {
Michael Kolb8872c232013-01-29 10:33:22 -08001091 long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
1092 if (duration > 0) {
1093 if (mCaptureTimeLapse) {
1094 duration = getTimeLapseVideoLength(duration);
1095 }
Michael Kolb8872c232013-01-29 10:33:22 -08001096 } else {
1097 Log.w(TAG, "Video duration <= 0 : " + duration);
1098 }
Angus Kongfd4fc0e2013-11-07 15:38:09 -08001099 mActivity.getMediaSaver().addVideo(mCurrentVideoFilename,
Angus Kong83a99ae2013-04-17 15:37:07 -07001100 duration, mCurrentVideoValues,
1101 mOnVideoSavedListener, mContentResolver);
Michael Kolb8872c232013-01-29 10:33:22 -08001102 }
1103 mCurrentVideoValues = null;
Michael Kolb8872c232013-01-29 10:33:22 -08001104 }
1105
1106 private void deleteVideoFile(String fileName) {
1107 Log.v(TAG, "Deleting video " + fileName);
1108 File f = new File(fileName);
1109 if (!f.delete()) {
1110 Log.v(TAG, "Could not delete " + fileName);
1111 }
1112 }
1113
1114 private PreferenceGroup filterPreferenceScreenByIntent(
1115 PreferenceGroup screen) {
1116 Intent intent = mActivity.getIntent();
1117 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
1118 CameraSettings.removePreferenceFromScreen(screen,
1119 CameraSettings.KEY_VIDEO_QUALITY);
1120 }
1121
1122 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
1123 CameraSettings.removePreferenceFromScreen(screen,
1124 CameraSettings.KEY_VIDEO_QUALITY);
1125 }
1126 return screen;
1127 }
1128
1129 // from MediaRecorder.OnErrorListener
1130 @Override
1131 public void onError(MediaRecorder mr, int what, int extra) {
1132 Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
1133 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1134 // We may have run out of space on the sdcard.
1135 stopVideoRecording();
1136 mActivity.updateStorageSpaceAndHint();
1137 }
1138 }
1139
1140 // from MediaRecorder.OnInfoListener
1141 @Override
1142 public void onInfo(MediaRecorder mr, int what, int extra) {
1143 if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
1144 if (mMediaRecorderRecording) onStopVideoRecording();
1145 } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
1146 if (mMediaRecorderRecording) onStopVideoRecording();
1147
1148 // Show the toast.
1149 Toast.makeText(mActivity, R.string.video_reach_size_limit,
1150 Toast.LENGTH_LONG).show();
1151 }
1152 }
1153
1154 /*
1155 * Make sure we're not recording music playing in the background, ask the
1156 * MediaPlaybackService to pause playback.
1157 */
1158 private void pauseAudioPlayback() {
1159 // Shamelessly copied from MediaPlaybackService.java, which
1160 // should be public, but isn't.
1161 Intent i = new Intent("com.android.music.musicservicecommand");
1162 i.putExtra("command", "pause");
1163
1164 mActivity.sendBroadcast(i);
1165 }
1166
1167 // For testing.
1168 public boolean isRecording() {
1169 return mMediaRecorderRecording;
1170 }
1171
1172 private void startVideoRecording() {
1173 Log.v(TAG, "startVideoRecording");
Sascha Haeberling37f36112013-08-06 14:31:52 -07001174 mUI.cancelAnimations();
Doris Liu6432cd62013-06-13 17:20:31 -07001175 mUI.setSwipingEnabled(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001176
1177 mActivity.updateStorageSpaceAndHint();
Angus Kong2dcc0a92013-09-25 13:00:08 -07001178 if (mActivity.getStorageSpaceBytes() <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
Michael Kolb8872c232013-01-29 10:33:22 -08001179 Log.v(TAG, "Storage issue, ignore the start request");
1180 return;
1181 }
1182
Angus Kong9ef99252013-07-18 18:04:19 -07001183 //??
1184 //if (!mCameraDevice.waitDone()) return;
Michael Kolb8872c232013-01-29 10:33:22 -08001185 mCurrentVideoUri = null;
Doris Liu3973deb2013-08-21 13:42:22 -07001186
1187 initializeRecorder();
1188 if (mMediaRecorder == null) {
1189 Log.e(TAG, "Fail to initialize media recorder");
1190 return;
Michael Kolb8872c232013-01-29 10:33:22 -08001191 }
1192
1193 pauseAudioPlayback();
1194
Doris Liu3973deb2013-08-21 13:42:22 -07001195 try {
1196 mMediaRecorder.start(); // Recording is now started
1197 } catch (RuntimeException e) {
1198 Log.e(TAG, "Could not start media recorder. ", e);
1199 releaseMediaRecorder();
1200 // If start fails, frameworks will not lock the camera for us.
1201 mCameraDevice.lock();
1202 return;
Michael Kolb8872c232013-01-29 10:33:22 -08001203 }
1204
1205 // Make sure the video recording has started before announcing
1206 // this in accessibility.
Doris Liu6432cd62013-06-13 17:20:31 -07001207 AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
Michael Kolb8872c232013-01-29 10:33:22 -08001208 mActivity.getString(R.string.video_recording_started));
1209
Angus Kong104e0012013-04-03 16:56:18 -07001210 // The parameters might have been altered by MediaRecorder already.
1211 // We need to force mCameraDevice to refresh before getting it.
Doris Liu6432cd62013-06-13 17:20:31 -07001212 mCameraDevice.refreshParameters();
Michael Kolb8872c232013-01-29 10:33:22 -08001213 // The parameters may have been changed by MediaRecorder upon starting
1214 // recording. We need to alter the parameters if we support camcorder
1215 // zoom. To reduce latency when setting the parameters during zoom, we
1216 // update mParameters here once.
Sascha Haeberling638e6f02013-09-18 14:28:51 -07001217 mParameters = mCameraDevice.getParameters();
Michael Kolb8872c232013-01-29 10:33:22 -08001218
Doris Liu6827ce22013-03-12 19:24:28 -07001219 mUI.enableCameraControls(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001220
1221 mMediaRecorderRecording = true;
Doris Liu6432cd62013-06-13 17:20:31 -07001222 mOrientationManager.lockOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001223 mRecordingStartTime = SystemClock.uptimeMillis();
Doris Liufe6596c2013-10-08 11:03:37 -07001224 mUI.showRecordingUI(true);
Michael Kolb8872c232013-01-29 10:33:22 -08001225
1226 updateRecordingTime();
1227 keepScreenOn();
Bobby Georgescu301b6462013-04-01 15:33:17 -07001228 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1229 UsageStatistics.ACTION_CAPTURE_START, "Video");
Michael Kolb8872c232013-01-29 10:33:22 -08001230 }
1231
Sascha Haeberling37f36112013-08-06 14:31:52 -07001232 private Bitmap getVideoThumbnail() {
Michael Kolb8872c232013-01-29 10:33:22 -08001233 Bitmap bitmap = null;
1234 if (mVideoFileDescriptor != null) {
1235 bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
Doris Liu3c2fca32013-02-13 18:28:03 -08001236 mDesiredPreviewWidth);
Doris Liu2a7f44c2013-08-12 15:18:53 -07001237 } else if (mCurrentVideoUri != null) {
1238 try {
1239 mVideoFileDescriptor = mContentResolver.openFileDescriptor(mCurrentVideoUri, "r");
1240 bitmap = Thumbnail.createVideoThumbnailBitmap(
1241 mVideoFileDescriptor.getFileDescriptor(), mDesiredPreviewWidth);
1242 } catch (java.io.FileNotFoundException ex) {
1243 // invalid uri
1244 Log.e(TAG, ex.toString());
1245 }
Michael Kolb8872c232013-01-29 10:33:22 -08001246 }
Doris Liu3973deb2013-08-21 13:42:22 -07001247
Michael Kolb8872c232013-01-29 10:33:22 -08001248 if (bitmap != null) {
1249 // MetadataRetriever already rotates the thumbnail. We should rotate
1250 // it to match the UI orientation (and mirror if it is front-facing camera).
1251 CameraInfo[] info = CameraHolder.instance().getCameraInfo();
1252 boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT);
Angus Kongb50b5cb2013-08-09 14:55:20 -07001253 bitmap = CameraUtil.rotateAndMirror(bitmap, 0, mirror);
Sascha Haeberling37f36112013-08-06 14:31:52 -07001254 }
1255 return bitmap;
1256 }
1257
1258 private void showCaptureResult() {
1259 mIsInReviewMode = true;
1260 Bitmap bitmap = getVideoThumbnail();
1261 if (bitmap != null) {
Doris Liu6827ce22013-03-12 19:24:28 -07001262 mUI.showReviewImage(bitmap);
Michael Kolb8872c232013-01-29 10:33:22 -08001263 }
Doris Liu6827ce22013-03-12 19:24:28 -07001264 mUI.showReviewControls();
1265 mUI.enableCameraControls(false);
1266 mUI.showTimeLapseUI(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001267 }
1268
Michael Kolb8872c232013-01-29 10:33:22 -08001269 private boolean stopVideoRecording() {
1270 Log.v(TAG, "stopVideoRecording");
Doris Liu6432cd62013-06-13 17:20:31 -07001271 mUI.setSwipingEnabled(true);
Doris Liu2a7f44c2013-08-12 15:18:53 -07001272 if (!isVideoCaptureIntent()) {
1273 mUI.showSwitcher();
1274 }
Michael Kolb8872c232013-01-29 10:33:22 -08001275
1276 boolean fail = false;
1277 if (mMediaRecorderRecording) {
1278 boolean shouldAddToMediaStoreNow = false;
1279
1280 try {
Doris Liu3973deb2013-08-21 13:42:22 -07001281 mMediaRecorder.setOnErrorListener(null);
1282 mMediaRecorder.setOnInfoListener(null);
1283 mMediaRecorder.stop();
1284 shouldAddToMediaStoreNow = true;
Michael Kolb8872c232013-01-29 10:33:22 -08001285 mCurrentVideoFilename = mVideoFilename;
1286 Log.v(TAG, "stopVideoRecording: Setting current video filename: "
1287 + mCurrentVideoFilename);
Doris Liu6432cd62013-06-13 17:20:31 -07001288 AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
Michael Kolb8872c232013-01-29 10:33:22 -08001289 mActivity.getString(R.string.video_recording_stopped));
1290 } catch (RuntimeException e) {
1291 Log.e(TAG, "stop fail", e);
1292 if (mVideoFilename != null) deleteVideoFile(mVideoFilename);
1293 fail = true;
1294 }
1295 mMediaRecorderRecording = false;
Doris Liu6432cd62013-06-13 17:20:31 -07001296 mOrientationManager.unlockOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001297
1298 // If the activity is paused, this means activity is interrupted
1299 // during recording. Release the camera as soon as possible because
1300 // face unlock or other applications may need to use the camera.
Michael Kolb8872c232013-01-29 10:33:22 -08001301 if (mPaused) {
Doris Liu3973deb2013-08-21 13:42:22 -07001302 closeCamera();
Michael Kolb8872c232013-01-29 10:33:22 -08001303 }
1304
Doris Liufe6596c2013-10-08 11:03:37 -07001305 mUI.showRecordingUI(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001306 if (!mIsVideoCaptureIntent) {
Doris Liu6827ce22013-03-12 19:24:28 -07001307 mUI.enableCameraControls(true);
Michael Kolb8872c232013-01-29 10:33:22 -08001308 }
1309 // The orientation was fixed during video recording. Now make it
1310 // reflect the device orientation as video recording is stopped.
Doris Liu6827ce22013-03-12 19:24:28 -07001311 mUI.setOrientationIndicator(0, true);
Michael Kolb8872c232013-01-29 10:33:22 -08001312 keepScreenOnAwhile();
Doris Liu2a7f44c2013-08-12 15:18:53 -07001313 if (shouldAddToMediaStoreNow && !fail) {
1314 if (mVideoFileDescriptor == null) {
1315 saveVideo();
1316 } else if (mIsVideoCaptureIntent) {
1317 // if no file save is needed, we can show the post capture UI now
1318 showCaptureResult();
1319 }
Michael Kolb8872c232013-01-29 10:33:22 -08001320 }
1321 }
Doris Liu3973deb2013-08-21 13:42:22 -07001322 // release media recorder
1323 releaseMediaRecorder();
1324 if (!mPaused) {
1325 mCameraDevice.lock();
1326 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
1327 stopPreview();
1328 mUI.hideSurfaceView();
1329 // Switch back to use SurfaceTexture for preview.
1330 startPreview();
Michael Kolb8872c232013-01-29 10:33:22 -08001331 }
1332 }
1333 // Update the parameters here because the parameters might have been altered
1334 // by MediaRecorder.
Doris Liu6432cd62013-06-13 17:20:31 -07001335 if (!mPaused) mParameters = mCameraDevice.getParameters();
Bobby Georgescu301b6462013-04-01 15:33:17 -07001336 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1337 fail ? UsageStatistics.ACTION_CAPTURE_FAIL :
1338 UsageStatistics.ACTION_CAPTURE_DONE, "Video",
1339 SystemClock.uptimeMillis() - mRecordingStartTime);
Michael Kolb8872c232013-01-29 10:33:22 -08001340 return fail;
1341 }
1342
1343 private void resetScreenOn() {
1344 mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1345 mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1346 }
1347
1348 private void keepScreenOnAwhile() {
1349 mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1350 mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1351 mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1352 }
1353
1354 private void keepScreenOn() {
1355 mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1356 mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1357 }
1358
1359 private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
1360 long seconds = milliSeconds / 1000; // round down to compute seconds
1361 long minutes = seconds / 60;
1362 long hours = minutes / 60;
1363 long remainderMinutes = minutes - (hours * 60);
1364 long remainderSeconds = seconds - (minutes * 60);
1365
1366 StringBuilder timeStringBuilder = new StringBuilder();
1367
1368 // Hours
1369 if (hours > 0) {
1370 if (hours < 10) {
1371 timeStringBuilder.append('0');
1372 }
1373 timeStringBuilder.append(hours);
1374
1375 timeStringBuilder.append(':');
1376 }
1377
1378 // Minutes
1379 if (remainderMinutes < 10) {
1380 timeStringBuilder.append('0');
1381 }
1382 timeStringBuilder.append(remainderMinutes);
1383 timeStringBuilder.append(':');
1384
1385 // Seconds
1386 if (remainderSeconds < 10) {
1387 timeStringBuilder.append('0');
1388 }
1389 timeStringBuilder.append(remainderSeconds);
1390
1391 // Centi seconds
1392 if (displayCentiSeconds) {
1393 timeStringBuilder.append('.');
1394 long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
1395 if (remainderCentiSeconds < 10) {
1396 timeStringBuilder.append('0');
1397 }
1398 timeStringBuilder.append(remainderCentiSeconds);
1399 }
1400
1401 return timeStringBuilder.toString();
1402 }
1403
1404 private long getTimeLapseVideoLength(long deltaMs) {
1405 // For better approximation calculate fractional number of frames captured.
1406 // This will update the video time at a higher resolution.
1407 double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
1408 return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
1409 }
1410
1411 private void updateRecordingTime() {
1412 if (!mMediaRecorderRecording) {
1413 return;
1414 }
1415 long now = SystemClock.uptimeMillis();
1416 long delta = now - mRecordingStartTime;
1417
1418 // Starting a minute before reaching the max duration
1419 // limit, we'll countdown the remaining time instead.
1420 boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1421 && delta >= mMaxVideoDurationInMs - 60000);
1422
1423 long deltaAdjusted = delta;
1424 if (countdownRemainingTime) {
1425 deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
1426 }
1427 String text;
1428
1429 long targetNextUpdateDelay;
1430 if (!mCaptureTimeLapse) {
1431 text = millisecondToTimeString(deltaAdjusted, false);
1432 targetNextUpdateDelay = 1000;
1433 } else {
1434 // The length of time lapse video is different from the length
1435 // of the actual wall clock time elapsed. Display the video length
1436 // only in format hh:mm:ss.dd, where dd are the centi seconds.
1437 text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
1438 targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
1439 }
1440
Doris Liu6827ce22013-03-12 19:24:28 -07001441 mUI.setRecordingTime(text);
Michael Kolb8872c232013-01-29 10:33:22 -08001442
1443 if (mRecordingTimeCountsDown != countdownRemainingTime) {
1444 // Avoid setting the color on every update, do it only
1445 // when it needs changing.
1446 mRecordingTimeCountsDown = countdownRemainingTime;
1447
1448 int color = mActivity.getResources().getColor(countdownRemainingTime
1449 ? R.color.recording_time_remaining_text
1450 : R.color.recording_time_elapsed_text);
1451
Doris Liu6827ce22013-03-12 19:24:28 -07001452 mUI.setRecordingTimeTextColor(color);
Michael Kolb8872c232013-01-29 10:33:22 -08001453 }
1454
1455 long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
1456 mHandler.sendEmptyMessageDelayed(
1457 UPDATE_RECORD_TIME, actualNextUpdateDelay);
1458 }
1459
1460 private static boolean isSupported(String value, List<String> supported) {
1461 return supported == null ? false : supported.indexOf(value) >= 0;
1462 }
1463
1464 @SuppressWarnings("deprecation")
1465 private void setCameraParameters() {
1466 mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
Angus Kongb50b5cb2013-08-09 14:55:20 -07001467 int[] fpsRange = CameraUtil.getMaxPreviewFpsRange(mParameters);
Doris Liu6432cd62013-06-13 17:20:31 -07001468 if (fpsRange.length > 0) {
1469 mParameters.setPreviewFpsRange(
1470 fpsRange[Parameters.PREVIEW_FPS_MIN_INDEX],
1471 fpsRange[Parameters.PREVIEW_FPS_MAX_INDEX]);
1472 } else {
1473 mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
1474 }
Michael Kolb8872c232013-01-29 10:33:22 -08001475
ztenghui7b265a62013-09-09 14:58:44 -07001476 forceFlashOffIfSupported(!mUI.isVisible());
Michael Kolb8872c232013-01-29 10:33:22 -08001477
1478 // Set white balance parameter.
1479 String whiteBalance = mPreferences.getString(
1480 CameraSettings.KEY_WHITE_BALANCE,
1481 mActivity.getString(R.string.pref_camera_whitebalance_default));
1482 if (isSupported(whiteBalance,
1483 mParameters.getSupportedWhiteBalance())) {
1484 mParameters.setWhiteBalance(whiteBalance);
1485 } else {
1486 whiteBalance = mParameters.getWhiteBalance();
1487 if (whiteBalance == null) {
1488 whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1489 }
1490 }
1491
1492 // Set zoom.
1493 if (mParameters.isZoomSupported()) {
1494 mParameters.setZoom(mZoomValue);
1495 }
1496
1497 // Set continuous autofocus.
1498 List<String> supportedFocus = mParameters.getSupportedFocusModes();
1499 if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
1500 mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
1501 }
1502
Angus Kongb50b5cb2013-08-09 14:55:20 -07001503 mParameters.set(CameraUtil.RECORDING_HINT, CameraUtil.TRUE);
Michael Kolb8872c232013-01-29 10:33:22 -08001504
1505 // Enable video stabilization. Convenience methods not available in API
1506 // level <= 14
1507 String vstabSupported = mParameters.get("video-stabilization-supported");
1508 if ("true".equals(vstabSupported)) {
1509 mParameters.set("video-stabilization", "true");
1510 }
1511
1512 // Set picture size.
1513 // The logic here is different from the logic in still-mode camera.
1514 // There we determine the preview size based on the picture size, but
1515 // here we determine the picture size based on the preview size.
1516 List<Size> supported = mParameters.getSupportedPictureSizes();
Angus Kongb50b5cb2013-08-09 14:55:20 -07001517 Size optimalSize = CameraUtil.getOptimalVideoSnapshotPictureSize(supported,
Michael Kolb8872c232013-01-29 10:33:22 -08001518 (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
1519 Size original = mParameters.getPictureSize();
1520 if (!original.equals(optimalSize)) {
1521 mParameters.setPictureSize(optimalSize.width, optimalSize.height);
1522 }
1523 Log.v(TAG, "Video snapshot size is " + optimalSize.width + "x" +
1524 optimalSize.height);
1525
1526 // Set JPEG quality.
1527 int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1528 CameraProfile.QUALITY_HIGH);
1529 mParameters.setJpegQuality(jpegQuality);
1530
Doris Liu6432cd62013-06-13 17:20:31 -07001531 mCameraDevice.setParameters(mParameters);
Michael Kolb8872c232013-01-29 10:33:22 -08001532 // Keep preview size up to date.
Doris Liu6432cd62013-06-13 17:20:31 -07001533 mParameters = mCameraDevice.getParameters();
Sascha Haeberlingf8b877c2013-09-09 17:32:48 -07001534
1535 // Update UI based on the new parameters.
1536 mUI.updateOnScreenIndicators(mParameters, mPreferences);
Michael Kolb8872c232013-01-29 10:33:22 -08001537 }
1538
1539 @Override
1540 public void onActivityResult(int requestCode, int resultCode, Intent data) {
Doris Liu3973deb2013-08-21 13:42:22 -07001541 // Do nothing.
Michael Kolb8872c232013-01-29 10:33:22 -08001542 }
1543
Michael Kolb8872c232013-01-29 10:33:22 -08001544 @Override
1545 public void onConfigurationChanged(Configuration newConfig) {
Doris Liu6a0de792013-02-26 10:54:25 -08001546 Log.v(TAG, "onConfigurationChanged");
Michael Kolb8872c232013-01-29 10:33:22 -08001547 setDisplayOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001548 }
1549
1550 @Override
1551 public void onOverriddenPreferencesClicked() {
1552 }
1553
1554 @Override
1555 // TODO: Delete this after old camera code is removed
1556 public void onRestorePreferencesClicked() {
1557 }
1558
Michael Kolb8872c232013-01-29 10:33:22 -08001559 @Override
1560 public void onSharedPreferenceChanged() {
1561 // ignore the events after "onPause()" or preview has not started yet
Sascha Haeberlingf8b877c2013-09-09 17:32:48 -07001562 if (mPaused) {
1563 return;
1564 }
Michael Kolb8872c232013-01-29 10:33:22 -08001565 synchronized (mPreferences) {
1566 // If mCameraDevice is not ready then we can set the parameter in
1567 // startPreview().
Doris Liu6432cd62013-06-13 17:20:31 -07001568 if (mCameraDevice == null) return;
Michael Kolb8872c232013-01-29 10:33:22 -08001569
1570 boolean recordLocation = RecordLocationPreference.get(
1571 mPreferences, mContentResolver);
1572 mLocationManager.recordLocation(recordLocation);
1573
Michael Kolb8872c232013-01-29 10:33:22 -08001574 readVideoPreferences();
Doris Liu6827ce22013-03-12 19:24:28 -07001575 mUI.showTimeLapseUI(mCaptureTimeLapse);
Michael Kolb8872c232013-01-29 10:33:22 -08001576 // We need to restart the preview if preview size is changed.
1577 Size size = mParameters.getPreviewSize();
1578 if (size.width != mDesiredPreviewWidth
1579 || size.height != mDesiredPreviewHeight) {
Doris Liu3973deb2013-08-21 13:42:22 -07001580
1581 stopPreview();
Michael Kolb8872c232013-01-29 10:33:22 -08001582 resizeForPreviewAspectRatio();
1583 startPreview(); // Parameters will be set in startPreview().
1584 } else {
1585 setCameraParameters();
1586 }
Michael Kolb87880792013-04-30 15:38:49 -07001587 mUI.updateOnScreenIndicators(mParameters, mPreferences);
Michael Kolb8872c232013-01-29 10:33:22 -08001588 }
1589 }
1590
Doris Liu6827ce22013-03-12 19:24:28 -07001591 protected void setCameraId(int cameraId) {
1592 ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID);
1593 pref.setValue("" + cameraId);
Michael Kolb8872c232013-01-29 10:33:22 -08001594 }
1595
1596 private void switchCamera() {
Sascha Haeberlingf8b877c2013-09-09 17:32:48 -07001597 if (mPaused) {
1598 return;
1599 }
Michael Kolb8872c232013-01-29 10:33:22 -08001600
1601 Log.d(TAG, "Start to switch camera.");
1602 mCameraId = mPendingSwitchCameraId;
1603 mPendingSwitchCameraId = -1;
Doris Liu6827ce22013-03-12 19:24:28 -07001604 setCameraId(mCameraId);
Michael Kolb8872c232013-01-29 10:33:22 -08001605
1606 closeCamera();
Doris Liu6827ce22013-03-12 19:24:28 -07001607 mUI.collapseCameraControls();
Michael Kolb8872c232013-01-29 10:33:22 -08001608 // Restart the camera and initialize the UI. From onCreate.
1609 mPreferences.setLocalId(mActivity, mCameraId);
1610 CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
1611 openCamera();
1612 readVideoPreferences();
1613 startPreview();
1614 initializeVideoSnapshot();
1615 resizeForPreviewAspectRatio();
1616 initializeVideoControl();
1617
1618 // From onResume
Doris Liu6432cd62013-06-13 17:20:31 -07001619 mZoomValue = 0;
Doris Liu6827ce22013-03-12 19:24:28 -07001620 mUI.initializeZoom(mParameters);
1621 mUI.setOrientationIndicator(0, false);
Michael Kolb8872c232013-01-29 10:33:22 -08001622
Doris Liu6432cd62013-06-13 17:20:31 -07001623 // Start switch camera animation. Post a message because
1624 // onFrameAvailable from the old camera may already exist.
1625 mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
Michael Kolb87880792013-04-30 15:38:49 -07001626 mUI.updateOnScreenIndicators(mParameters, mPreferences);
Michael Kolb8872c232013-01-29 10:33:22 -08001627 }
1628
1629 // Preview texture has been copied. Now camera can be released and the
1630 // animation can be started.
1631 @Override
1632 public void onPreviewTextureCopied() {
1633 mHandler.sendEmptyMessage(SWITCH_CAMERA);
1634 }
1635
1636 @Override
1637 public void onCaptureTextureCopied() {
1638 }
1639
Michael Kolb8872c232013-01-29 10:33:22 -08001640 private void initializeVideoSnapshot() {
Doris Liu537718c2013-02-20 16:29:44 -08001641 if (mParameters == null) return;
Angus Kongb50b5cb2013-08-09 14:55:20 -07001642 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
Michael Kolb8872c232013-01-29 10:33:22 -08001643 // Show the tap to focus toast if this is the first start.
1644 if (mPreferences.getBoolean(
1645 CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, true)) {
1646 // Delay the toast for one second to wait for orientation.
1647 mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_SNAPSHOT_TOAST, 1000);
1648 }
Michael Kolb8872c232013-01-29 10:33:22 -08001649 }
1650 }
1651
1652 void showVideoSnapshotUI(boolean enabled) {
Doris Liu537718c2013-02-20 16:29:44 -08001653 if (mParameters == null) return;
Angus Kongb50b5cb2013-08-09 14:55:20 -07001654 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
Doris Liu6432cd62013-06-13 17:20:31 -07001655 if (enabled) {
Sascha Haeberling37f36112013-08-06 14:31:52 -07001656 mUI.animateFlash();
Doris Liu3973deb2013-08-21 13:42:22 -07001657 mUI.animateCapture();
Michael Kolb8872c232013-01-29 10:33:22 -08001658 } else {
Doris Liu6827ce22013-03-12 19:24:28 -07001659 mUI.showPreviewBorder(enabled);
Michael Kolb8872c232013-01-29 10:33:22 -08001660 }
Doris Liu6827ce22013-03-12 19:24:28 -07001661 mUI.enableShutter(!enabled);
Michael Kolb8872c232013-01-29 10:33:22 -08001662 }
1663 }
1664
ztenghui7b265a62013-09-09 14:58:44 -07001665 private void forceFlashOffIfSupported(boolean forceOff) {
1666 String flashMode;
1667 if (!forceOff) {
1668 flashMode = mPreferences.getString(
1669 CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
1670 mActivity.getString(R.string.pref_camera_video_flashmode_default));
1671 } else {
1672 flashMode = Parameters.FLASH_MODE_OFF;
1673 }
1674 List<String> supportedFlash = mParameters.getSupportedFlashModes();
1675 if (isSupported(flashMode, supportedFlash)) {
1676 mParameters.setFlashMode(flashMode);
1677 } else {
1678 flashMode = mParameters.getFlashMode();
1679 if (flashMode == null) {
1680 flashMode = mActivity.getString(
1681 R.string.pref_camera_flashmode_no_flash);
Michael Kolb8872c232013-01-29 10:33:22 -08001682 }
Michael Kolb8872c232013-01-29 10:33:22 -08001683 }
1684 }
1685
ztenghui7b265a62013-09-09 14:58:44 -07001686 /**
1687 * Used to update the flash mode. Video mode can turn on the flash as torch
1688 * mode, which we would like to turn on and off when we switching in and
1689 * out to the preview.
1690 *
1691 * @param forceOff whether we want to force the flash off.
1692 */
1693 private void forceFlashOff(boolean forceOff) {
1694 if (!mPreviewing || mParameters.getFlashMode() == null) {
1695 return;
1696 }
1697 forceFlashOffIfSupported(forceOff);
1698 mCameraDevice.setParameters(mParameters);
ztenghui285a5be2013-10-09 15:59:47 -07001699 mUI.updateOnScreenIndicators(mParameters, mPreferences);
ztenghui7b265a62013-09-09 14:58:44 -07001700 }
1701
Michael Kolb8872c232013-01-29 10:33:22 -08001702 @Override
ztenghui7b265a62013-09-09 14:58:44 -07001703 public void onPreviewFocusChanged(boolean previewFocused) {
1704 mUI.onPreviewFocusChanged(previewFocused);
1705 forceFlashOff(!previewFocused);
Michael Kolb8872c232013-01-29 10:33:22 -08001706 }
1707
Erin Dahlgren3044d8c2013-10-10 18:23:45 -07001708 @Override
1709 public boolean arePreviewControlsVisible() {
1710 return mUI.arePreviewControlsVisible();
1711 }
1712
Angus Kong9ef99252013-07-18 18:04:19 -07001713 private final class JpegPictureCallback implements CameraPictureCallback {
Michael Kolb8872c232013-01-29 10:33:22 -08001714 Location mLocation;
1715
1716 public JpegPictureCallback(Location loc) {
1717 mLocation = loc;
1718 }
1719
1720 @Override
Angus Kong9ef99252013-07-18 18:04:19 -07001721 public void onPictureTaken(byte [] jpegData, CameraProxy camera) {
Michael Kolb8872c232013-01-29 10:33:22 -08001722 Log.v(TAG, "onPictureTaken");
1723 mSnapshotInProgress = false;
1724 showVideoSnapshotUI(false);
1725 storeImage(jpegData, mLocation);
1726 }
1727 }
1728
1729 private void storeImage(final byte[] data, Location loc) {
1730 long dateTaken = System.currentTimeMillis();
Angus Kongb50b5cb2013-08-09 14:55:20 -07001731 String title = CameraUtil.createJpegName(dateTaken);
Angus Kong0d00a892013-03-26 11:40:40 -07001732 ExifInterface exif = Exif.getExif(data);
1733 int orientation = Exif.getOrientation(exif);
Doris Liu6df2d962013-08-20 16:31:29 -07001734
Angus Kongfd4fc0e2013-11-07 15:38:09 -08001735 mActivity.getMediaSaver().addImage(
Doris Liu6df2d962013-08-20 16:31:29 -07001736 data, title, dateTaken, loc, orientation,
Angus Kong83a99ae2013-04-17 15:37:07 -07001737 exif, mOnPhotoSavedListener, mContentResolver);
Michael Kolb8872c232013-01-29 10:33:22 -08001738 }
1739
Michael Kolb8872c232013-01-29 10:33:22 -08001740 private String convertOutputFormatToMimeType(int outputFileFormat) {
1741 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1742 return "video/mp4";
1743 }
1744 return "video/3gpp";
1745 }
1746
1747 private String convertOutputFormatToFileExt(int outputFileFormat) {
1748 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1749 return ".mp4";
1750 }
1751 return ".3gp";
1752 }
1753
1754 private void closeVideoFileDescriptor() {
1755 if (mVideoFileDescriptor != null) {
1756 try {
1757 mVideoFileDescriptor.close();
1758 } catch (IOException e) {
1759 Log.e(TAG, "Fail to close fd", e);
1760 }
1761 mVideoFileDescriptor = null;
1762 }
1763 }
1764
1765 private void showTapToSnapshotToast() {
1766 new RotateTextToast(mActivity, R.string.video_snapshot_hint, 0)
1767 .show();
1768 // Clear the preference.
1769 Editor editor = mPreferences.edit();
1770 editor.putBoolean(CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, false);
1771 editor.apply();
1772 }
1773
Michael Kolb8872c232013-01-29 10:33:22 -08001774 @Override
1775 public boolean updateStorageHintOnResume() {
1776 return true;
1777 }
1778
1779 // required by OnPreferenceChangedListener
1780 @Override
1781 public void onCameraPickerClicked(int cameraId) {
1782 if (mPaused || mPendingSwitchCameraId != -1) return;
1783
1784 mPendingSwitchCameraId = cameraId;
Doris Liu6432cd62013-06-13 17:20:31 -07001785 Log.d(TAG, "Start to copy texture.");
1786 // We need to keep a preview frame for the animation before
1787 // releasing the camera. This will trigger onPreviewTextureCopied.
1788 // TODO: ((CameraScreenNail) mActivity.mCameraScreenNail).copyTexture();
1789 // Disable all camera controls.
1790 mSwitchingCamera = true;
Sascha Haeberling0d63b892013-08-19 11:29:04 -07001791 switchCamera();
Michael Kolb8872c232013-01-29 10:33:22 -08001792
Doris Liu6a0de792013-02-26 10:54:25 -08001793 }
1794
1795 @Override
Michael Kolb8872c232013-01-29 10:33:22 -08001796 public void onShowSwitcherPopup() {
Doris Liu6827ce22013-03-12 19:24:28 -07001797 mUI.onShowSwitcherPopup();
Michael Kolb8872c232013-01-29 10:33:22 -08001798 }
Angus Kong86d36312013-01-31 18:22:44 -08001799
1800 @Override
Angus Kongfd4fc0e2013-11-07 15:38:09 -08001801 public void onMediaSaverAvailable(MediaSaver s) {
Angus Kong86d36312013-01-31 18:22:44 -08001802 // do nothing.
1803 }
Angus Kong395ee2d2013-07-15 12:42:41 -07001804
1805 @Override
1806 public void onPreviewUIReady() {
1807 startPreview();
1808 }
1809
1810 @Override
1811 public void onPreviewUIDestroyed() {
1812 stopPreview();
1813 }
Michael Kolb8872c232013-01-29 10:33:22 -08001814}