blob: ecf21936874ad9481697fc0c33b323e94c903d84 [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;
Marco Nelissen20694b22013-10-29 15:27:24 -070036import android.media.AudioManager;
Michael Kolb8872c232013-01-29 10:33:22 -080037import android.media.CamcorderProfile;
38import android.media.CameraProfile;
39import android.media.MediaRecorder;
40import android.net.Uri;
41import android.os.Build;
42import android.os.Bundle;
43import android.os.Handler;
44import android.os.Message;
45import android.os.ParcelFileDescriptor;
46import android.os.SystemClock;
47import android.provider.MediaStore;
Ruben Brunk16007962013-04-19 15:27:57 -070048import android.provider.MediaStore.MediaColumns;
Michael Kolb8872c232013-01-29 10:33:22 -080049import android.provider.MediaStore.Video;
50import android.util.Log;
Michael Kolb8872c232013-01-29 10:33:22 -080051import android.view.KeyEvent;
Michael Kolb8872c232013-01-29 10:33:22 -080052import android.view.OrientationEventListener;
Michael Kolb8872c232013-01-29 10:33:22 -080053import android.view.View;
Michael Kolb8872c232013-01-29 10:33:22 -080054import android.widget.Toast;
55
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080056import com.android.camera.app.AppController;
Angus Kong20fad242013-11-11 18:23:46 -080057import com.android.camera.app.CameraManager.CameraPictureCallback;
58import com.android.camera.app.CameraManager.CameraProxy;
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
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080076public class VideoModule extends CameraModule
77 implements ModuleController,
78 VideoController,
79 CameraPreference.OnPreferenceChangedListener,
80 ShutterButton.OnShutterButtonListener,
81 MediaRecorder.OnErrorListener,
82 MediaRecorder.OnInfoListener {
Michael Kolb8872c232013-01-29 10:33:22 -080083
84 private static final String TAG = "CAM_VideoModule";
85
Angus Kong13e87c42013-11-25 10:02:47 -080086 // Messages defined for the UI thread handler.
87 private static final int MSG_CHECK_DISPLAY_ROTATION = 4;
88 private static final int MSG_UPDATE_RECORD_TIME = 5;
89 private static final int MSG_ENABLE_SHUTTER_BUTTON = 6;
90 private static final int MSG_SHOW_TAP_TO_SNAPSHOT_TOAST = 7;
91 private static final int MSG_SWITCH_CAMERA = 8;
92 private static final int MSG_SWITCH_CAMERA_START_ANIMATION = 9;
Michael Kolb8872c232013-01-29 10:33:22 -080093
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;
164
Michael Kolb8872c232013-01-29 10:33:22 -0800165 private int mPendingSwitchCameraId;
Michael Kolb8872c232013-01-29 10:33:22 -0800166 private final Handler mHandler = new MainHandler();
Doris Liu6827ce22013-03-12 19:24:28 -0700167 private VideoUI mUI;
Doris Liu6432cd62013-06-13 17:20:31 -0700168 private CameraProxy mCameraDevice;
169
Michael Kolb8872c232013-01-29 10:33:22 -0800170 // The degrees of the device rotated clockwise from its natural orientation.
171 private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
172
173 private int mZoomValue; // The current zoom value.
Doris Liu6827ce22013-03-12 19:24:28 -0700174
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800175 private final MediaSaver.OnMediaSavedListener mOnVideoSavedListener =
176 new MediaSaver.OnMediaSavedListener() {
Angus Kong83a99ae2013-04-17 15:37:07 -0700177 @Override
178 public void onMediaSaved(Uri uri) {
179 if (uri != null) {
Doris Liu2a7f44c2013-08-12 15:18:53 -0700180 mCurrentVideoUri = uri;
ztenghui70bd0242013-10-14 11:15:44 -0700181 mCurrentVideoUriFromMediaSaved = true;
Doris Liu2a7f44c2013-08-12 15:18:53 -0700182 onVideoSaved();
Sascha Haeberling37f36112013-08-06 14:31:52 -0700183 mActivity.notifyNewMedia(uri);
Angus Kong83a99ae2013-04-17 15:37:07 -0700184 }
185 }
186 };
187
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800188 private final MediaSaver.OnMediaSavedListener mOnPhotoSavedListener =
189 new MediaSaver.OnMediaSavedListener() {
Angus Kong86d36312013-01-31 18:22:44 -0800190 @Override
191 public void onMediaSaved(Uri uri) {
192 if (uri != null) {
Sascha Haeberling37f36112013-08-06 14:31:52 -0700193 mActivity.notifyNewMedia(uri);
Angus Kong86d36312013-01-31 18:22:44 -0800194 }
195 }
196 };
197
Michael Kolb8872c232013-01-29 10:33:22 -0800198 private void openCamera() {
Angus Kong4f795b82013-09-16 14:25:35 -0700199 if (mCameraDevice == null) {
200 mCameraDevice = CameraUtil.openCamera(
201 mActivity, mCameraId, mHandler,
202 mActivity.getCameraOpenErrorCallback());
Michael Kolb8872c232013-01-29 10:33:22 -0800203 }
Angus Kong4f795b82013-09-16 14:25:35 -0700204 if (mCameraDevice == null) {
205 // Error.
206 return;
207 }
208 mParameters = mCameraDevice.getParameters();
Michael Kolb8872c232013-01-29 10:33:22 -0800209 }
210
211 // This Handler is used to post message back onto the main thread of the
212 // application
213 private class MainHandler extends Handler {
214 @Override
215 public void handleMessage(Message msg) {
216 switch (msg.what) {
217
Angus Kong13e87c42013-11-25 10:02:47 -0800218 case MSG_ENABLE_SHUTTER_BUTTON:
Doris Liu6827ce22013-03-12 19:24:28 -0700219 mUI.enableShutter(true);
Michael Kolb8872c232013-01-29 10:33:22 -0800220 break;
221
Angus Kong13e87c42013-11-25 10:02:47 -0800222 case MSG_UPDATE_RECORD_TIME: {
Michael Kolb8872c232013-01-29 10:33:22 -0800223 updateRecordingTime();
224 break;
225 }
226
Angus Kong13e87c42013-11-25 10:02:47 -0800227 case MSG_CHECK_DISPLAY_ROTATION: {
Michael Kolb8872c232013-01-29 10:33:22 -0800228 // Restart the preview if display rotation has changed.
229 // Sometimes this happens when the device is held upside
230 // down and camera app is opened. Rotation animation will
231 // take some time and the rotation value we have got may be
232 // wrong. Framework does not have a callback for this now.
Angus Kongb50b5cb2013-08-09 14:55:20 -0700233 if ((CameraUtil.getDisplayRotation(mActivity) != mDisplayRotation)
Michael Kolb8872c232013-01-29 10:33:22 -0800234 && !mMediaRecorderRecording && !mSwitchingCamera) {
235 startPreview();
236 }
237 if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
Angus Kong13e87c42013-11-25 10:02:47 -0800238 mHandler.sendEmptyMessageDelayed(MSG_CHECK_DISPLAY_ROTATION, 100);
Michael Kolb8872c232013-01-29 10:33:22 -0800239 }
240 break;
241 }
242
Angus Kong13e87c42013-11-25 10:02:47 -0800243 case MSG_SHOW_TAP_TO_SNAPSHOT_TOAST: {
Michael Kolb8872c232013-01-29 10:33:22 -0800244 showTapToSnapshotToast();
245 break;
246 }
247
Angus Kong13e87c42013-11-25 10:02:47 -0800248 case MSG_SWITCH_CAMERA: {
Michael Kolb8872c232013-01-29 10:33:22 -0800249 switchCamera();
250 break;
251 }
252
Angus Kong13e87c42013-11-25 10:02:47 -0800253 case MSG_SWITCH_CAMERA_START_ANIMATION: {
Doris Liu6432cd62013-06-13 17:20:31 -0700254 //TODO:
255 //((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
Michael Kolb8872c232013-01-29 10:33:22 -0800256
257 // Enable all camera controls.
258 mSwitchingCamera = false;
259 break;
260 }
261
Michael Kolb8872c232013-01-29 10:33:22 -0800262 default:
263 Log.v(TAG, "Unhandled message: " + msg.what);
264 break;
265 }
266 }
267 }
268
269 private BroadcastReceiver mReceiver = null;
270
271 private class MyBroadcastReceiver extends BroadcastReceiver {
272 @Override
273 public void onReceive(Context context, Intent intent) {
274 String action = intent.getAction();
275 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
276 stopVideoRecording();
277 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
278 Toast.makeText(mActivity,
279 mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
280 }
281 }
282 }
283
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800284 /**
285 * Construct a new video module.
286 */
Angus Kongc4e66562013-11-22 23:03:21 -0800287 public VideoModule(AppController app) {
288 super(app);
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800289 }
290
Michael Kolb8872c232013-01-29 10:33:22 -0800291 private String createName(long dateTaken) {
292 Date date = new Date(dateTaken);
293 SimpleDateFormat dateFormat = new SimpleDateFormat(
294 mActivity.getString(R.string.video_file_name_format));
295
296 return dateFormat.format(date);
297 }
298
299 private int getPreferredCameraId(ComboPreferences preferences) {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700300 int intentCameraId = CameraUtil.getCameraFacingIntentExtras(mActivity);
Michael Kolb8872c232013-01-29 10:33:22 -0800301 if (intentCameraId != -1) {
302 // Testing purpose. Launch a specific camera through the intent
303 // extras.
304 return intentCameraId;
305 } else {
306 return CameraSettings.readPreferredCameraId(preferences);
307 }
308 }
309
310 private void initializeSurfaceView() {
Doris Liu6827ce22013-03-12 19:24:28 -0700311 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { // API level < 16
Doris Liu6432cd62013-06-13 17:20:31 -0700312 mUI.initializeSurfaceView();
Michael Kolb8872c232013-01-29 10:33:22 -0800313 }
314 }
315
Angus Kong13e87c42013-11-25 10:02:47 -0800316
Michael Kolb8872c232013-01-29 10:33:22 -0800317 @Override
Angus Kong13e87c42013-11-25 10:02:47 -0800318 public void init(AppController app, boolean isSecureCamera, boolean isCaptureIntent) {
319 mActivity = (CameraActivity) app.getAndroidContext();
320 mUI = new VideoUI(mActivity, this, app.getModuleLayoutRoot());
Michael Kolb8872c232013-01-29 10:33:22 -0800321 mPreferences = new ComboPreferences(mActivity);
322 CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
323 mCameraId = getPreferredCameraId(mPreferences);
324
325 mPreferences.setLocalId(mActivity, mCameraId);
326 CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
327
Michael Kolb8872c232013-01-29 10:33:22 -0800328 /*
329 * To reduce startup time, we start the preview in another thread.
330 * We make sure the preview is started at the end of onCreate.
331 */
Angus Kong20fad242013-11-11 18:23:46 -0800332 requestCamera(mCameraId);
Michael Kolb8872c232013-01-29 10:33:22 -0800333
334 mContentResolver = mActivity.getContentResolver();
335
Michael Kolb8872c232013-01-29 10:33:22 -0800336 // Surface texture is from camera screen nail and startPreview needs it.
337 // This must be done before startPreview.
338 mIsVideoCaptureIntent = isVideoCaptureIntent();
Michael Kolb8872c232013-01-29 10:33:22 -0800339 initializeSurfaceView();
340
Doris Liu6827ce22013-03-12 19:24:28 -0700341 mUI.setPrefChangedListener(this);
Michael Kolb8872c232013-01-29 10:33:22 -0800342
Michael Kolb8872c232013-01-29 10:33:22 -0800343 mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
Erin Dahlgren21c21a62013-11-19 16:37:38 -0800344 mLocationManager = mActivity.getLocationManager();
Michael Kolb8872c232013-01-29 10:33:22 -0800345
Doris Liu6827ce22013-03-12 19:24:28 -0700346 mUI.setOrientationIndicator(0, false);
Michael Kolb8872c232013-01-29 10:33:22 -0800347 setDisplayOrientation();
348
Doris Liu6827ce22013-03-12 19:24:28 -0700349 mUI.showTimeLapseUI(mCaptureTimeLapse);
Michael Kolb8872c232013-01-29 10:33:22 -0800350 mPendingSwitchCameraId = -1;
Doris Liu6827ce22013-03-12 19:24:28 -0700351 }
352
353 // SingleTapListener
354 // Preview area is touched. Take a picture.
355 @Override
356 public void onSingleTapUp(View view, int x, int y) {
Doris Liu38605742013-08-13 15:01:52 -0700357 takeASnapshot();
358 }
359
360 private void takeASnapshot() {
Sascha Haeberlinga514b142013-08-13 16:10:01 -0700361 // Only take snapshots if video snapshot is supported by device
Doris Liu38605742013-08-13 15:01:52 -0700362 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
Doris Liu3973deb2013-08-21 13:42:22 -0700363 if (!mMediaRecorderRecording || mPaused || mSnapshotInProgress) {
Sascha Haeberlinga514b142013-08-13 16:10:01 -0700364 return;
365 }
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800366 MediaSaver s = getServices().getMediaSaver();
Sascha Haeberlinga514b142013-08-13 16:10:01 -0700367 if (s == null || s.isQueueFull()) {
Doris Liu38605742013-08-13 15:01:52 -0700368 return;
369 }
370
371 // Set rotation and gps data.
Angus Kong20fad242013-11-11 18:23:46 -0800372 int rotation = CameraUtil.getJpegRotation(mActivity, mCameraId, mOrientation);
Doris Liu38605742013-08-13 15:01:52 -0700373 mParameters.setRotation(rotation);
374 Location loc = mLocationManager.getCurrentLocation();
375 CameraUtil.setGpsParameters(mParameters, loc);
376 mCameraDevice.setParameters(mParameters);
377
378 Log.v(TAG, "Video snapshot start");
379 mCameraDevice.takePicture(mHandler,
380 null, null, null, new JpegPictureCallback(loc));
381 showVideoSnapshotUI(true);
382 mSnapshotInProgress = true;
383 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
384 UsageStatistics.ACTION_CAPTURE_DONE, "VideoSnapshot");
Doris Liu6827ce22013-03-12 19:24:28 -0700385 }
Michael Kolb8872c232013-01-29 10:33:22 -0800386 }
387
Michael Kolb8872c232013-01-29 10:33:22 -0800388 private void loadCameraPreferences() {
389 CameraSettings settings = new CameraSettings(mActivity, mParameters,
Angus Kong20fad242013-11-11 18:23:46 -0800390 mCameraId, mActivity.getCameraProvider().getCameraInfo());
Michael Kolb8872c232013-01-29 10:33:22 -0800391 // Remove the video quality preference setting when the quality is given in the intent.
392 mPreferenceGroup = filterPreferenceScreenByIntent(
393 settings.getPreferenceGroup(R.xml.video_preferences));
394 }
395
Michael Kolb8872c232013-01-29 10:33:22 -0800396 private void initializeVideoControl() {
397 loadCameraPreferences();
Doris Liu6827ce22013-03-12 19:24:28 -0700398 mUI.initializePopup(mPreferenceGroup);
Michael Kolb8872c232013-01-29 10:33:22 -0800399 }
400
Michael Kolb8872c232013-01-29 10:33:22 -0800401 @Override
402 public void onOrientationChanged(int orientation) {
403 // We keep the last known orientation. So if the user first orient
404 // the camera then point the camera to floor or sky, we still have
405 // the correct orientation.
406 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return;
Angus Kongb50b5cb2013-08-09 14:55:20 -0700407 int newOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800408
409 if (mOrientation != newOrientation) {
410 mOrientation = newOrientation;
Michael Kolb8872c232013-01-29 10:33:22 -0800411 }
412
413 // Show the toast after getting the first orientation changed.
Angus Kong13e87c42013-11-25 10:02:47 -0800414 if (mHandler.hasMessages(MSG_SHOW_TAP_TO_SNAPSHOT_TOAST)) {
415 mHandler.removeMessages(MSG_SHOW_TAP_TO_SNAPSHOT_TOAST);
Michael Kolb8872c232013-01-29 10:33:22 -0800416 showTapToSnapshotToast();
417 }
418 }
419
Angus Kong20fad242013-11-11 18:23:46 -0800420 @Override
421 public void onCameraAvailable(CameraProxy cameraProxy) {
422 mCameraDevice = cameraProxy;
423 readVideoPreferences();
424 resizeForPreviewAspectRatio();
425 startPreview();
426 initializeVideoSnapshot();
427 initializeVideoControl();
428 mUI.initializeZoom(mParameters);
429 }
430
Michael Kolb8872c232013-01-29 10:33:22 -0800431 private void startPlayVideoActivity() {
432 Intent intent = new Intent(Intent.ACTION_VIEW);
433 intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
434 try {
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700435 mActivity
436 .startActivityForResult(intent, CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
Michael Kolb8872c232013-01-29 10:33:22 -0800437 } catch (ActivityNotFoundException ex) {
438 Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
439 }
440 }
441
ztenghui7b265a62013-09-09 14:58:44 -0700442 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800443 @OnClickAttr
444 public void onReviewPlayClicked(View v) {
445 startPlayVideoActivity();
446 }
447
ztenghui7b265a62013-09-09 14:58:44 -0700448 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800449 @OnClickAttr
450 public void onReviewDoneClicked(View v) {
Doris Liu69ef5ea2013-05-07 13:48:10 -0700451 mIsInReviewMode = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800452 doReturnToCaller(true);
453 }
454
ztenghui7b265a62013-09-09 14:58:44 -0700455 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800456 @OnClickAttr
457 public void onReviewCancelClicked(View v) {
ztenghuiaf3a1972013-09-17 16:51:15 -0700458 // TODO: It should be better to not even insert the URI at all before we
459 // confirm done in review, which means we need to handle temporary video
460 // files in a quite different way than we currently had.
ztenghui70bd0242013-10-14 11:15:44 -0700461 // Make sure we don't delete the Uri sent from the video capture intent.
462 if (mCurrentVideoUriFromMediaSaved) {
ztenghuidfe5b152013-10-08 17:07:15 -0700463 mContentResolver.delete(mCurrentVideoUri, null, null);
464 }
ztenghui638bf9a2013-10-09 10:52:33 -0700465 mIsInReviewMode = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800466 doReturnToCaller(false);
467 }
468
Doris Liu69ef5ea2013-05-07 13:48:10 -0700469 @Override
470 public boolean isInReviewMode() {
471 return mIsInReviewMode;
472 }
473
Michael Kolb8872c232013-01-29 10:33:22 -0800474 private void onStopVideoRecording() {
Michael Kolb8872c232013-01-29 10:33:22 -0800475 boolean recordFail = stopVideoRecording();
476 if (mIsVideoCaptureIntent) {
Doris Liu3973deb2013-08-21 13:42:22 -0700477 if (mQuickCapture) {
478 doReturnToCaller(!recordFail);
479 } else if (!recordFail) {
480 showCaptureResult();
Michael Kolb8872c232013-01-29 10:33:22 -0800481 }
482 } else if (!recordFail){
483 // Start capture animation.
484 if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
485 // The capture animation is disabled on ICS because we use SurfaceView
486 // for preview during recording. When the recording is done, we switch
487 // back to use SurfaceTexture for preview and we need to stop then start
488 // the preview. This will cause the preview flicker since the preview
489 // will not be continuous for a short period of time.
Sascha Haeberling4f91ab52013-05-21 11:26:13 -0700490
Sascha Haeberling37f36112013-08-06 14:31:52 -0700491 mUI.animateFlash();
Doris Liu3973deb2013-08-21 13:42:22 -0700492 mUI.animateCapture();
Michael Kolb8872c232013-01-29 10:33:22 -0800493 }
494 }
495 }
496
Doris Liu2a7f44c2013-08-12 15:18:53 -0700497 public void onVideoSaved() {
498 if (mIsVideoCaptureIntent) {
499 showCaptureResult();
500 }
501 }
502
Michael Kolb8872c232013-01-29 10:33:22 -0800503 public void onProtectiveCurtainClick(View v) {
504 // Consume clicks
505 }
506
507 @Override
508 public void onShutterButtonClick() {
Doris Liu6827ce22013-03-12 19:24:28 -0700509 if (mUI.collapseCameraControls() || mSwitchingCamera) return;
Michael Kolb8872c232013-01-29 10:33:22 -0800510
511 boolean stop = mMediaRecorderRecording;
512
513 if (stop) {
514 onStopVideoRecording();
515 } else {
516 startVideoRecording();
517 }
Doris Liu6827ce22013-03-12 19:24:28 -0700518 mUI.enableShutter(false);
Michael Kolb8872c232013-01-29 10:33:22 -0800519
520 // Keep the shutter button disabled when in video capture intent
521 // mode and recording is stopped. It'll be re-enabled when
522 // re-take button is clicked.
523 if (!(mIsVideoCaptureIntent && stop)) {
Angus Kong13e87c42013-11-25 10:02:47 -0800524 mHandler.sendEmptyMessageDelayed(MSG_ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
Michael Kolb8872c232013-01-29 10:33:22 -0800525 }
526 }
527
528 @Override
529 public void onShutterButtonFocus(boolean pressed) {
Doris Liuf55f3c42013-11-20 00:24:46 -0800530 // TODO: Remove this when old camera controls are removed from the UI.
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() {
Angus Kong13e87c42013-11-25 10:02:47 -0800613 mUI.setAspectRatio((double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800614 }
615
Angus Kong13e87c42013-11-25 10:02:47 -0800616 private void installIntentFilter() {
Michael Kolb8872c232013-01-29 10:33:22 -0800617 // install an intent filter to receive SD card related events.
618 IntentFilter intentFilter =
619 new IntentFilter(Intent.ACTION_MEDIA_EJECT);
620 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
621 intentFilter.addDataScheme("file");
622 mReceiver = new MyBroadcastReceiver();
623 mActivity.registerReceiver(mReceiver, intentFilter);
624 }
625
Michael Kolb8872c232013-01-29 10:33:22 -0800626 private void setDisplayOrientation() {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700627 mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
628 mCameraDisplayOrientation = CameraUtil.getDisplayOrientation(mDisplayRotation, mCameraId);
Doris Liu6432cd62013-06-13 17:20:31 -0700629 // Change the camera display orientation
630 if (mCameraDevice != null) {
631 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800632 }
Doris Liu6432cd62013-06-13 17:20:31 -0700633 }
634
635 @Override
636 public void updateCameraOrientation() {
637 if (mMediaRecorderRecording) return;
Angus Kongb50b5cb2013-08-09 14:55:20 -0700638 if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
Doris Liu6432cd62013-06-13 17:20:31 -0700639 setDisplayOrientation();
640 }
Michael Kolb8872c232013-01-29 10:33:22 -0800641 }
642
Doris Liu6827ce22013-03-12 19:24:28 -0700643 @Override
644 public int onZoomChanged(int index) {
645 // Not useful to change zoom value when the activity is paused.
646 if (mPaused) return index;
647 mZoomValue = index;
Doris Liu6432cd62013-06-13 17:20:31 -0700648 if (mParameters == null || mCameraDevice == null) return index;
Doris Liu6827ce22013-03-12 19:24:28 -0700649 // Set zoom parameters asynchronously
650 mParameters.setZoom(mZoomValue);
Doris Liu6432cd62013-06-13 17:20:31 -0700651 mCameraDevice.setParameters(mParameters);
652 Parameters p = mCameraDevice.getParameters();
Doris Liu6827ce22013-03-12 19:24:28 -0700653 if (p != null) return p.getZoom();
654 return index;
655 }
Angus Kong395ee2d2013-07-15 12:42:41 -0700656
Michael Kolb8872c232013-01-29 10:33:22 -0800657 private void startPreview() {
658 Log.v(TAG, "startPreview");
659
Angus Kong395ee2d2013-07-15 12:42:41 -0700660 SurfaceTexture surfaceTexture = mUI.getSurfaceTexture();
ztenghuief01a312013-10-14 15:25:16 -0700661 if (!mPreferenceRead || surfaceTexture == null || mPaused == true ||
662 mCameraDevice == null) {
663 return;
664 }
Angus Kong395ee2d2013-07-15 12:42:41 -0700665
Doris Liu6432cd62013-06-13 17:20:31 -0700666 mCameraDevice.setErrorCallback(mErrorCallback);
Michael Kolb8872c232013-01-29 10:33:22 -0800667 if (mPreviewing == true) {
668 stopPreview();
Michael Kolb8872c232013-01-29 10:33:22 -0800669 }
670
Michael Kolb8872c232013-01-29 10:33:22 -0800671 setDisplayOrientation();
Doris Liu6432cd62013-06-13 17:20:31 -0700672 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800673 setCameraParameters();
674
675 try {
Doris Liu3973deb2013-08-21 13:42:22 -0700676 mCameraDevice.setPreviewTexture(surfaceTexture);
677 mCameraDevice.startPreview();
678 mPreviewing = true;
679 onPreviewStarted();
Michael Kolb8872c232013-01-29 10:33:22 -0800680 } catch (Throwable ex) {
681 closeCamera();
682 throw new RuntimeException("startPreview failed", ex);
Michael Kolb8872c232013-01-29 10:33:22 -0800683 }
Michael Kolbb1aeb392013-03-11 12:37:40 -0700684 }
685
686 private void onPreviewStarted() {
Doris Liu6827ce22013-03-12 19:24:28 -0700687 mUI.enableShutter(true);
Michael Kolb8872c232013-01-29 10:33:22 -0800688 }
689
Doris Liu6827ce22013-03-12 19:24:28 -0700690 @Override
691 public void stopPreview() {
Doris Liu6432cd62013-06-13 17:20:31 -0700692 if (!mPreviewing) return;
693 mCameraDevice.stopPreview();
Michael Kolb8872c232013-01-29 10:33:22 -0800694 mPreviewing = false;
695 }
696
Michael Kolb8872c232013-01-29 10:33:22 -0800697 private void closeCamera() {
Michael Kolb8872c232013-01-29 10:33:22 -0800698 Log.v(TAG, "closeCamera");
Doris Liu6432cd62013-06-13 17:20:31 -0700699 if (mCameraDevice == null) {
Michael Kolb8872c232013-01-29 10:33:22 -0800700 Log.d(TAG, "already stopped.");
701 return;
702 }
Doris Liu6432cd62013-06-13 17:20:31 -0700703 mCameraDevice.setZoomChangeListener(null);
704 mCameraDevice.setErrorCallback(null);
Angus Kong20fad242013-11-11 18:23:46 -0800705 mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
Angus Kong395ee2d2013-07-15 12:42:41 -0700706 mCameraDevice = null;
Michael Kolb8872c232013-01-29 10:33:22 -0800707 mPreviewing = false;
708 mSnapshotInProgress = false;
709 }
710
711 private void releasePreviewResources() {
Doris Liu6432cd62013-06-13 17:20:31 -0700712 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
713 mUI.hideSurfaceView();
Michael Kolb8872c232013-01-29 10:33:22 -0800714 }
715 }
716
717 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800718 public boolean onBackPressed() {
719 if (mPaused) return true;
720 if (mMediaRecorderRecording) {
721 onStopVideoRecording();
722 return true;
Doris Liu6827ce22013-03-12 19:24:28 -0700723 } else if (mUI.hidePieRenderer()) {
Michael Kolb8872c232013-01-29 10:33:22 -0800724 return true;
725 } else {
Doris Liu6827ce22013-03-12 19:24:28 -0700726 return mUI.removeTopLevelPopup();
Michael Kolb8872c232013-01-29 10:33:22 -0800727 }
728 }
729
730 @Override
731 public boolean onKeyDown(int keyCode, KeyEvent event) {
732 // Do not handle any key if the activity is paused.
733 if (mPaused) {
734 return true;
735 }
736
737 switch (keyCode) {
738 case KeyEvent.KEYCODE_CAMERA:
739 if (event.getRepeatCount() == 0) {
Doris Liu6827ce22013-03-12 19:24:28 -0700740 mUI.clickShutter();
Michael Kolb8872c232013-01-29 10:33:22 -0800741 return true;
742 }
743 break;
744 case KeyEvent.KEYCODE_DPAD_CENTER:
745 if (event.getRepeatCount() == 0) {
Doris Liu6827ce22013-03-12 19:24:28 -0700746 mUI.clickShutter();
Michael Kolb8872c232013-01-29 10:33:22 -0800747 return true;
748 }
749 break;
750 case KeyEvent.KEYCODE_MENU:
751 if (mMediaRecorderRecording) return true;
752 break;
753 }
754 return false;
755 }
756
757 @Override
758 public boolean onKeyUp(int keyCode, KeyEvent event) {
759 switch (keyCode) {
760 case KeyEvent.KEYCODE_CAMERA:
Doris Liu6827ce22013-03-12 19:24:28 -0700761 mUI.pressShutter(false);
Michael Kolb8872c232013-01-29 10:33:22 -0800762 return true;
763 }
764 return false;
765 }
766
Doris Liu6827ce22013-03-12 19:24:28 -0700767 @Override
768 public boolean isVideoCaptureIntent() {
Michael Kolb8872c232013-01-29 10:33:22 -0800769 String action = mActivity.getIntent().getAction();
770 return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
771 }
772
773 private void doReturnToCaller(boolean valid) {
774 Intent resultIntent = new Intent();
775 int resultCode;
776 if (valid) {
777 resultCode = Activity.RESULT_OK;
778 resultIntent.setData(mCurrentVideoUri);
779 } else {
780 resultCode = Activity.RESULT_CANCELED;
781 }
782 mActivity.setResultEx(resultCode, resultIntent);
783 mActivity.finish();
784 }
785
786 private void cleanupEmptyFile() {
787 if (mVideoFilename != null) {
788 File f = new File(mVideoFilename);
789 if (f.length() == 0 && f.delete()) {
790 Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
791 mVideoFilename = null;
792 }
793 }
794 }
795
796 private void setupMediaRecorderPreviewDisplay() {
797 // Nothing to do here if using SurfaceTexture.
Doris Liu6432cd62013-06-13 17:20:31 -0700798 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
Michael Kolb8872c232013-01-29 10:33:22 -0800799 // We stop the preview here before unlocking the device because we
800 // need to change the SurfaceTexture to SurfaceView for preview.
801 stopPreview();
Angus Kong9ef99252013-07-18 18:04:19 -0700802 mCameraDevice.setPreviewDisplay(mUI.getSurfaceHolder());
Michael Kolb8872c232013-01-29 10:33:22 -0800803 // The orientation for SurfaceTexture is different from that for
804 // SurfaceView. For SurfaceTexture we don't need to consider the
805 // display rotation. Just consider the sensor's orientation and we
806 // will set the orientation correctly when showing the texture.
807 // Gallery will handle the orientation for the preview. For
808 // SurfaceView we will have to take everything into account so the
809 // display rotation is considered.
Doris Liu6432cd62013-06-13 17:20:31 -0700810 mCameraDevice.setDisplayOrientation(
Angus Kongb50b5cb2013-08-09 14:55:20 -0700811 CameraUtil.getDisplayOrientation(mDisplayRotation, mCameraId));
Angus Kong9ef99252013-07-18 18:04:19 -0700812 mCameraDevice.startPreview();
Michael Kolb8872c232013-01-29 10:33:22 -0800813 mPreviewing = true;
Doris Liu6827ce22013-03-12 19:24:28 -0700814 mMediaRecorder.setPreviewDisplay(mUI.getSurfaceHolder().getSurface());
Michael Kolb8872c232013-01-29 10:33:22 -0800815 }
816 }
817
818 // Prepares media recorder.
819 private void initializeRecorder() {
820 Log.v(TAG, "initializeRecorder");
821 // If the mCameraDevice is null, then this activity is going to finish
Doris Liu6432cd62013-06-13 17:20:31 -0700822 if (mCameraDevice == null) return;
Michael Kolb8872c232013-01-29 10:33:22 -0800823
Doris Liu6432cd62013-06-13 17:20:31 -0700824 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
Michael Kolb8872c232013-01-29 10:33:22 -0800825 // Set the SurfaceView to visible so the surface gets created.
826 // surfaceCreated() is called immediately when the visibility is
827 // changed to visible. Thus, mSurfaceViewReady should become true
828 // right after calling setVisibility().
Doris Liu6827ce22013-03-12 19:24:28 -0700829 mUI.showSurfaceView();
Michael Kolb8872c232013-01-29 10:33:22 -0800830 }
831
832 Intent intent = mActivity.getIntent();
833 Bundle myExtras = intent.getExtras();
834
835 long requestedSizeLimit = 0;
836 closeVideoFileDescriptor();
ztenghui70bd0242013-10-14 11:15:44 -0700837 mCurrentVideoUriFromMediaSaved = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800838 if (mIsVideoCaptureIntent && myExtras != null) {
839 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
840 if (saveUri != null) {
841 try {
842 mVideoFileDescriptor =
843 mContentResolver.openFileDescriptor(saveUri, "rw");
844 mCurrentVideoUri = saveUri;
845 } catch (java.io.FileNotFoundException ex) {
846 // invalid uri
847 Log.e(TAG, ex.toString());
848 }
849 }
850 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
851 }
852 mMediaRecorder = new MediaRecorder();
853
854 setupMediaRecorderPreviewDisplay();
855 // Unlock the camera object before passing it to media recorder.
Doris Liu6432cd62013-06-13 17:20:31 -0700856 mCameraDevice.unlock();
Doris Liu6432cd62013-06-13 17:20:31 -0700857 mMediaRecorder.setCamera(mCameraDevice.getCamera());
Michael Kolb8872c232013-01-29 10:33:22 -0800858 if (!mCaptureTimeLapse) {
859 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
860 }
861 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
862 mMediaRecorder.setProfile(mProfile);
863 mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
864 if (mCaptureTimeLapse) {
865 double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs;
866 setCaptureRate(mMediaRecorder, fps);
867 }
868
869 setRecordLocation();
870
871 // Set output file.
872 // Try Uri in the intent first. If it doesn't exist, use our own
873 // instead.
874 if (mVideoFileDescriptor != null) {
875 mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
876 } else {
877 generateVideoFilename(mProfile.fileFormat);
878 mMediaRecorder.setOutputFile(mVideoFilename);
879 }
880
881 // Set maximum file size.
Angus Kong2dcc0a92013-09-25 13:00:08 -0700882 long maxFileSize = mActivity.getStorageSpaceBytes() - Storage.LOW_STORAGE_THRESHOLD_BYTES;
Michael Kolb8872c232013-01-29 10:33:22 -0800883 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
884 maxFileSize = requestedSizeLimit;
885 }
886
887 try {
888 mMediaRecorder.setMaxFileSize(maxFileSize);
889 } catch (RuntimeException exception) {
890 // We are going to ignore failure of setMaxFileSize here, as
891 // a) The composer selected may simply not support it, or
892 // b) The underlying media framework may not handle 64-bit range
893 // on the size restriction.
894 }
895
896 // See android.hardware.Camera.Parameters.setRotation for
897 // documentation.
898 // Note that mOrientation here is the device orientation, which is the opposite of
899 // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
900 // which is the orientation the graphics need to rotate in order to render correctly.
901 int rotation = 0;
902 if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
Angus Kong20fad242013-11-11 18:23:46 -0800903 CameraInfo info = mActivity.getCameraProvider().getCameraInfo()[mCameraId];
Michael Kolb8872c232013-01-29 10:33:22 -0800904 if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
905 rotation = (info.orientation - mOrientation + 360) % 360;
906 } else { // back-facing camera
907 rotation = (info.orientation + mOrientation) % 360;
908 }
909 }
910 mMediaRecorder.setOrientationHint(rotation);
911
912 try {
913 mMediaRecorder.prepare();
914 } catch (IOException e) {
915 Log.e(TAG, "prepare failed for " + mVideoFilename, e);
916 releaseMediaRecorder();
917 throw new RuntimeException(e);
918 }
919
920 mMediaRecorder.setOnErrorListener(this);
921 mMediaRecorder.setOnInfoListener(this);
922 }
923
Michael Kolb8872c232013-01-29 10:33:22 -0800924 private static void setCaptureRate(MediaRecorder recorder, double fps) {
925 recorder.setCaptureRate(fps);
926 }
927
Michael Kolb8872c232013-01-29 10:33:22 -0800928 private void setRecordLocation() {
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700929 Location loc = mLocationManager.getCurrentLocation();
930 if (loc != null) {
931 mMediaRecorder.setLocation((float) loc.getLatitude(),
932 (float) loc.getLongitude());
Michael Kolb8872c232013-01-29 10:33:22 -0800933 }
934 }
935
Michael Kolb8872c232013-01-29 10:33:22 -0800936 private void releaseMediaRecorder() {
937 Log.v(TAG, "Releasing media recorder.");
938 if (mMediaRecorder != null) {
939 cleanupEmptyFile();
940 mMediaRecorder.reset();
941 mMediaRecorder.release();
942 mMediaRecorder = null;
943 }
944 mVideoFilename = null;
945 }
946
Michael Kolb8872c232013-01-29 10:33:22 -0800947 private void generateVideoFilename(int outputFileFormat) {
948 long dateTaken = System.currentTimeMillis();
949 String title = createName(dateTaken);
950 // Used when emailing.
951 String filename = title + convertOutputFormatToFileExt(outputFileFormat);
952 String mime = convertOutputFormatToMimeType(outputFileFormat);
953 String path = Storage.DIRECTORY + '/' + filename;
954 String tmpPath = path + ".tmp";
Ruben Brunk16007962013-04-19 15:27:57 -0700955 mCurrentVideoValues = new ContentValues(9);
Michael Kolb8872c232013-01-29 10:33:22 -0800956 mCurrentVideoValues.put(Video.Media.TITLE, title);
957 mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
958 mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
Ruben Brunk16007962013-04-19 15:27:57 -0700959 mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000);
Michael Kolb8872c232013-01-29 10:33:22 -0800960 mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
961 mCurrentVideoValues.put(Video.Media.DATA, path);
962 mCurrentVideoValues.put(Video.Media.RESOLUTION,
963 Integer.toString(mProfile.videoFrameWidth) + "x" +
964 Integer.toString(mProfile.videoFrameHeight));
965 Location loc = mLocationManager.getCurrentLocation();
966 if (loc != null) {
967 mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
968 mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
969 }
Michael Kolb8872c232013-01-29 10:33:22 -0800970 mVideoFilename = tmpPath;
971 Log.v(TAG, "New video filename: " + mVideoFilename);
972 }
973
Angus Kong83a99ae2013-04-17 15:37:07 -0700974 private void saveVideo() {
Michael Kolb8872c232013-01-29 10:33:22 -0800975 if (mVideoFileDescriptor == null) {
Michael Kolb8872c232013-01-29 10:33:22 -0800976 long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
977 if (duration > 0) {
978 if (mCaptureTimeLapse) {
979 duration = getTimeLapseVideoLength(duration);
980 }
Michael Kolb8872c232013-01-29 10:33:22 -0800981 } else {
982 Log.w(TAG, "Video duration <= 0 : " + duration);
983 }
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800984 getServices().getMediaSaver().addVideo(mCurrentVideoFilename,
Angus Kong83a99ae2013-04-17 15:37:07 -0700985 duration, mCurrentVideoValues,
986 mOnVideoSavedListener, mContentResolver);
Michael Kolb8872c232013-01-29 10:33:22 -0800987 }
988 mCurrentVideoValues = null;
Michael Kolb8872c232013-01-29 10:33:22 -0800989 }
990
991 private void deleteVideoFile(String fileName) {
992 Log.v(TAG, "Deleting video " + fileName);
993 File f = new File(fileName);
994 if (!f.delete()) {
995 Log.v(TAG, "Could not delete " + fileName);
996 }
997 }
998
999 private PreferenceGroup filterPreferenceScreenByIntent(
1000 PreferenceGroup screen) {
1001 Intent intent = mActivity.getIntent();
1002 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
Angus Kong20fad242013-11-11 18:23:46 -08001003 CameraSettings.removePreferenceFromScreen(screen, CameraSettings.KEY_VIDEO_QUALITY);
Michael Kolb8872c232013-01-29 10:33:22 -08001004 }
1005
1006 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
1007 CameraSettings.removePreferenceFromScreen(screen,
1008 CameraSettings.KEY_VIDEO_QUALITY);
1009 }
1010 return screen;
1011 }
1012
1013 // from MediaRecorder.OnErrorListener
1014 @Override
1015 public void onError(MediaRecorder mr, int what, int extra) {
1016 Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
1017 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1018 // We may have run out of space on the sdcard.
1019 stopVideoRecording();
1020 mActivity.updateStorageSpaceAndHint();
1021 }
1022 }
1023
1024 // from MediaRecorder.OnInfoListener
1025 @Override
1026 public void onInfo(MediaRecorder mr, int what, int extra) {
1027 if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
1028 if (mMediaRecorderRecording) onStopVideoRecording();
1029 } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
1030 if (mMediaRecorderRecording) onStopVideoRecording();
1031
1032 // Show the toast.
1033 Toast.makeText(mActivity, R.string.video_reach_size_limit,
1034 Toast.LENGTH_LONG).show();
1035 }
1036 }
1037
1038 /*
1039 * Make sure we're not recording music playing in the background, ask the
1040 * MediaPlaybackService to pause playback.
1041 */
1042 private void pauseAudioPlayback() {
Marco Nelissen20694b22013-10-29 15:27:24 -07001043 AudioManager am = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);
1044 am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
Michael Kolb8872c232013-01-29 10:33:22 -08001045 }
1046
1047 // For testing.
1048 public boolean isRecording() {
1049 return mMediaRecorderRecording;
1050 }
1051
1052 private void startVideoRecording() {
1053 Log.v(TAG, "startVideoRecording");
Sascha Haeberling37f36112013-08-06 14:31:52 -07001054 mUI.cancelAnimations();
Doris Liu6432cd62013-06-13 17:20:31 -07001055 mUI.setSwipingEnabled(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001056
1057 mActivity.updateStorageSpaceAndHint();
Angus Kong2dcc0a92013-09-25 13:00:08 -07001058 if (mActivity.getStorageSpaceBytes() <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
Michael Kolb8872c232013-01-29 10:33:22 -08001059 Log.v(TAG, "Storage issue, ignore the start request");
1060 return;
1061 }
1062
Angus Kong9ef99252013-07-18 18:04:19 -07001063 //??
1064 //if (!mCameraDevice.waitDone()) return;
Michael Kolb8872c232013-01-29 10:33:22 -08001065 mCurrentVideoUri = null;
Doris Liu3973deb2013-08-21 13:42:22 -07001066
1067 initializeRecorder();
1068 if (mMediaRecorder == null) {
1069 Log.e(TAG, "Fail to initialize media recorder");
1070 return;
Michael Kolb8872c232013-01-29 10:33:22 -08001071 }
1072
1073 pauseAudioPlayback();
1074
Doris Liu3973deb2013-08-21 13:42:22 -07001075 try {
1076 mMediaRecorder.start(); // Recording is now started
1077 } catch (RuntimeException e) {
1078 Log.e(TAG, "Could not start media recorder. ", e);
1079 releaseMediaRecorder();
1080 // If start fails, frameworks will not lock the camera for us.
1081 mCameraDevice.lock();
1082 return;
Michael Kolb8872c232013-01-29 10:33:22 -08001083 }
1084
1085 // Make sure the video recording has started before announcing
1086 // this in accessibility.
Doris Liu6432cd62013-06-13 17:20:31 -07001087 AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
Michael Kolb8872c232013-01-29 10:33:22 -08001088 mActivity.getString(R.string.video_recording_started));
1089
Angus Kong104e0012013-04-03 16:56:18 -07001090 // The parameters might have been altered by MediaRecorder already.
1091 // We need to force mCameraDevice to refresh before getting it.
Doris Liu6432cd62013-06-13 17:20:31 -07001092 mCameraDevice.refreshParameters();
Michael Kolb8872c232013-01-29 10:33:22 -08001093 // The parameters may have been changed by MediaRecorder upon starting
1094 // recording. We need to alter the parameters if we support camcorder
1095 // zoom. To reduce latency when setting the parameters during zoom, we
1096 // update mParameters here once.
Sascha Haeberling638e6f02013-09-18 14:28:51 -07001097 mParameters = mCameraDevice.getParameters();
Michael Kolb8872c232013-01-29 10:33:22 -08001098
Doris Liu6827ce22013-03-12 19:24:28 -07001099 mUI.enableCameraControls(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001100
1101 mMediaRecorderRecording = true;
Angus Kong9f1db522013-11-09 16:25:59 -08001102 mActivity.lockOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001103 mRecordingStartTime = SystemClock.uptimeMillis();
Doris Liufe6596c2013-10-08 11:03:37 -07001104 mUI.showRecordingUI(true);
Michael Kolb8872c232013-01-29 10:33:22 -08001105
1106 updateRecordingTime();
Angus Kong13e87c42013-11-25 10:02:47 -08001107 mActivity.enableKeepScreenOn(true);
Bobby Georgescu301b6462013-04-01 15:33:17 -07001108 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1109 UsageStatistics.ACTION_CAPTURE_START, "Video");
Michael Kolb8872c232013-01-29 10:33:22 -08001110 }
1111
Sascha Haeberling37f36112013-08-06 14:31:52 -07001112 private Bitmap getVideoThumbnail() {
Michael Kolb8872c232013-01-29 10:33:22 -08001113 Bitmap bitmap = null;
1114 if (mVideoFileDescriptor != null) {
1115 bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
Doris Liu3c2fca32013-02-13 18:28:03 -08001116 mDesiredPreviewWidth);
Doris Liu2a7f44c2013-08-12 15:18:53 -07001117 } else if (mCurrentVideoUri != null) {
1118 try {
1119 mVideoFileDescriptor = mContentResolver.openFileDescriptor(mCurrentVideoUri, "r");
1120 bitmap = Thumbnail.createVideoThumbnailBitmap(
1121 mVideoFileDescriptor.getFileDescriptor(), mDesiredPreviewWidth);
1122 } catch (java.io.FileNotFoundException ex) {
1123 // invalid uri
1124 Log.e(TAG, ex.toString());
1125 }
Michael Kolb8872c232013-01-29 10:33:22 -08001126 }
Doris Liu3973deb2013-08-21 13:42:22 -07001127
Michael Kolb8872c232013-01-29 10:33:22 -08001128 if (bitmap != null) {
1129 // MetadataRetriever already rotates the thumbnail. We should rotate
1130 // it to match the UI orientation (and mirror if it is front-facing camera).
Angus Kong20fad242013-11-11 18:23:46 -08001131 CameraInfo[] info = mActivity.getCameraProvider().getCameraInfo();
Michael Kolb8872c232013-01-29 10:33:22 -08001132 boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT);
Angus Kongb50b5cb2013-08-09 14:55:20 -07001133 bitmap = CameraUtil.rotateAndMirror(bitmap, 0, mirror);
Sascha Haeberling37f36112013-08-06 14:31:52 -07001134 }
1135 return bitmap;
1136 }
1137
1138 private void showCaptureResult() {
1139 mIsInReviewMode = true;
1140 Bitmap bitmap = getVideoThumbnail();
1141 if (bitmap != null) {
Doris Liu6827ce22013-03-12 19:24:28 -07001142 mUI.showReviewImage(bitmap);
Michael Kolb8872c232013-01-29 10:33:22 -08001143 }
Doris Liu6827ce22013-03-12 19:24:28 -07001144 mUI.showReviewControls();
1145 mUI.enableCameraControls(false);
1146 mUI.showTimeLapseUI(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001147 }
1148
Michael Kolb8872c232013-01-29 10:33:22 -08001149 private boolean stopVideoRecording() {
1150 Log.v(TAG, "stopVideoRecording");
Doris Liu6432cd62013-06-13 17:20:31 -07001151 mUI.setSwipingEnabled(true);
Michael Kolb8872c232013-01-29 10:33:22 -08001152
1153 boolean fail = false;
1154 if (mMediaRecorderRecording) {
1155 boolean shouldAddToMediaStoreNow = false;
1156
1157 try {
Doris Liu3973deb2013-08-21 13:42:22 -07001158 mMediaRecorder.setOnErrorListener(null);
1159 mMediaRecorder.setOnInfoListener(null);
1160 mMediaRecorder.stop();
1161 shouldAddToMediaStoreNow = true;
Michael Kolb8872c232013-01-29 10:33:22 -08001162 mCurrentVideoFilename = mVideoFilename;
1163 Log.v(TAG, "stopVideoRecording: Setting current video filename: "
1164 + mCurrentVideoFilename);
Doris Liu6432cd62013-06-13 17:20:31 -07001165 AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
Angus Kong13e87c42013-11-25 10:02:47 -08001166 mActivity.getAndroidContext().getString(R.string
1167 .video_recording_stopped));
Michael Kolb8872c232013-01-29 10:33:22 -08001168 } catch (RuntimeException e) {
1169 Log.e(TAG, "stop fail", e);
1170 if (mVideoFilename != null) deleteVideoFile(mVideoFilename);
1171 fail = true;
1172 }
1173 mMediaRecorderRecording = false;
Angus Kong9f1db522013-11-09 16:25:59 -08001174 mActivity.unlockOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001175
1176 // If the activity is paused, this means activity is interrupted
1177 // during recording. Release the camera as soon as possible because
1178 // face unlock or other applications may need to use the camera.
Michael Kolb8872c232013-01-29 10:33:22 -08001179 if (mPaused) {
Doris Liu3973deb2013-08-21 13:42:22 -07001180 closeCamera();
Michael Kolb8872c232013-01-29 10:33:22 -08001181 }
1182
Doris Liufe6596c2013-10-08 11:03:37 -07001183 mUI.showRecordingUI(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001184 if (!mIsVideoCaptureIntent) {
Doris Liu6827ce22013-03-12 19:24:28 -07001185 mUI.enableCameraControls(true);
Michael Kolb8872c232013-01-29 10:33:22 -08001186 }
1187 // The orientation was fixed during video recording. Now make it
1188 // reflect the device orientation as video recording is stopped.
Doris Liu6827ce22013-03-12 19:24:28 -07001189 mUI.setOrientationIndicator(0, true);
Angus Kong13e87c42013-11-25 10:02:47 -08001190 mActivity.enableKeepScreenOn(false);
Doris Liu2a7f44c2013-08-12 15:18:53 -07001191 if (shouldAddToMediaStoreNow && !fail) {
1192 if (mVideoFileDescriptor == null) {
1193 saveVideo();
1194 } else if (mIsVideoCaptureIntent) {
1195 // if no file save is needed, we can show the post capture UI now
1196 showCaptureResult();
1197 }
Michael Kolb8872c232013-01-29 10:33:22 -08001198 }
1199 }
Doris Liu3973deb2013-08-21 13:42:22 -07001200 // release media recorder
1201 releaseMediaRecorder();
1202 if (!mPaused) {
1203 mCameraDevice.lock();
1204 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
1205 stopPreview();
1206 mUI.hideSurfaceView();
1207 // Switch back to use SurfaceTexture for preview.
1208 startPreview();
Michael Kolb8872c232013-01-29 10:33:22 -08001209 }
1210 }
1211 // Update the parameters here because the parameters might have been altered
1212 // by MediaRecorder.
Doris Liu6432cd62013-06-13 17:20:31 -07001213 if (!mPaused) mParameters = mCameraDevice.getParameters();
Bobby Georgescu301b6462013-04-01 15:33:17 -07001214 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
Angus Kong13e87c42013-11-25 10:02:47 -08001215 fail ? UsageStatistics.ACTION_CAPTURE_FAIL : UsageStatistics.ACTION_CAPTURE_DONE,
1216 "Video", SystemClock.uptimeMillis() - mRecordingStartTime);
Michael Kolb8872c232013-01-29 10:33:22 -08001217 return fail;
1218 }
1219
Michael Kolb8872c232013-01-29 10:33:22 -08001220 private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
1221 long seconds = milliSeconds / 1000; // round down to compute seconds
1222 long minutes = seconds / 60;
1223 long hours = minutes / 60;
1224 long remainderMinutes = minutes - (hours * 60);
1225 long remainderSeconds = seconds - (minutes * 60);
1226
1227 StringBuilder timeStringBuilder = new StringBuilder();
1228
1229 // Hours
1230 if (hours > 0) {
1231 if (hours < 10) {
1232 timeStringBuilder.append('0');
1233 }
1234 timeStringBuilder.append(hours);
1235
1236 timeStringBuilder.append(':');
1237 }
1238
1239 // Minutes
1240 if (remainderMinutes < 10) {
1241 timeStringBuilder.append('0');
1242 }
1243 timeStringBuilder.append(remainderMinutes);
1244 timeStringBuilder.append(':');
1245
1246 // Seconds
1247 if (remainderSeconds < 10) {
1248 timeStringBuilder.append('0');
1249 }
1250 timeStringBuilder.append(remainderSeconds);
1251
1252 // Centi seconds
1253 if (displayCentiSeconds) {
1254 timeStringBuilder.append('.');
1255 long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
1256 if (remainderCentiSeconds < 10) {
1257 timeStringBuilder.append('0');
1258 }
1259 timeStringBuilder.append(remainderCentiSeconds);
1260 }
1261
1262 return timeStringBuilder.toString();
1263 }
1264
1265 private long getTimeLapseVideoLength(long deltaMs) {
1266 // For better approximation calculate fractional number of frames captured.
1267 // This will update the video time at a higher resolution.
1268 double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
1269 return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
1270 }
1271
1272 private void updateRecordingTime() {
1273 if (!mMediaRecorderRecording) {
1274 return;
1275 }
1276 long now = SystemClock.uptimeMillis();
1277 long delta = now - mRecordingStartTime;
1278
1279 // Starting a minute before reaching the max duration
1280 // limit, we'll countdown the remaining time instead.
1281 boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1282 && delta >= mMaxVideoDurationInMs - 60000);
1283
1284 long deltaAdjusted = delta;
1285 if (countdownRemainingTime) {
1286 deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
1287 }
1288 String text;
1289
1290 long targetNextUpdateDelay;
1291 if (!mCaptureTimeLapse) {
1292 text = millisecondToTimeString(deltaAdjusted, false);
1293 targetNextUpdateDelay = 1000;
1294 } else {
1295 // The length of time lapse video is different from the length
1296 // of the actual wall clock time elapsed. Display the video length
1297 // only in format hh:mm:ss.dd, where dd are the centi seconds.
1298 text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
1299 targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
1300 }
1301
Doris Liu6827ce22013-03-12 19:24:28 -07001302 mUI.setRecordingTime(text);
Michael Kolb8872c232013-01-29 10:33:22 -08001303
1304 if (mRecordingTimeCountsDown != countdownRemainingTime) {
1305 // Avoid setting the color on every update, do it only
1306 // when it needs changing.
1307 mRecordingTimeCountsDown = countdownRemainingTime;
1308
1309 int color = mActivity.getResources().getColor(countdownRemainingTime
1310 ? R.color.recording_time_remaining_text
1311 : R.color.recording_time_elapsed_text);
1312
Doris Liu6827ce22013-03-12 19:24:28 -07001313 mUI.setRecordingTimeTextColor(color);
Michael Kolb8872c232013-01-29 10:33:22 -08001314 }
1315
1316 long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
Angus Kong13e87c42013-11-25 10:02:47 -08001317 mHandler.sendEmptyMessageDelayed(MSG_UPDATE_RECORD_TIME, actualNextUpdateDelay);
Michael Kolb8872c232013-01-29 10:33:22 -08001318 }
1319
1320 private static boolean isSupported(String value, List<String> supported) {
1321 return supported == null ? false : supported.indexOf(value) >= 0;
1322 }
1323
1324 @SuppressWarnings("deprecation")
1325 private void setCameraParameters() {
1326 mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
Angus Kongb50b5cb2013-08-09 14:55:20 -07001327 int[] fpsRange = CameraUtil.getMaxPreviewFpsRange(mParameters);
Doris Liu6432cd62013-06-13 17:20:31 -07001328 if (fpsRange.length > 0) {
1329 mParameters.setPreviewFpsRange(
1330 fpsRange[Parameters.PREVIEW_FPS_MIN_INDEX],
1331 fpsRange[Parameters.PREVIEW_FPS_MAX_INDEX]);
1332 } else {
1333 mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
1334 }
Michael Kolb8872c232013-01-29 10:33:22 -08001335
ztenghui7b265a62013-09-09 14:58:44 -07001336 forceFlashOffIfSupported(!mUI.isVisible());
Michael Kolb8872c232013-01-29 10:33:22 -08001337
1338 // Set white balance parameter.
1339 String whiteBalance = mPreferences.getString(
1340 CameraSettings.KEY_WHITE_BALANCE,
1341 mActivity.getString(R.string.pref_camera_whitebalance_default));
1342 if (isSupported(whiteBalance,
1343 mParameters.getSupportedWhiteBalance())) {
1344 mParameters.setWhiteBalance(whiteBalance);
1345 } else {
1346 whiteBalance = mParameters.getWhiteBalance();
1347 if (whiteBalance == null) {
1348 whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1349 }
1350 }
1351
1352 // Set zoom.
1353 if (mParameters.isZoomSupported()) {
1354 mParameters.setZoom(mZoomValue);
1355 }
1356
1357 // Set continuous autofocus.
1358 List<String> supportedFocus = mParameters.getSupportedFocusModes();
1359 if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
1360 mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
1361 }
1362
Angus Kongb50b5cb2013-08-09 14:55:20 -07001363 mParameters.set(CameraUtil.RECORDING_HINT, CameraUtil.TRUE);
Michael Kolb8872c232013-01-29 10:33:22 -08001364
1365 // Enable video stabilization. Convenience methods not available in API
1366 // level <= 14
1367 String vstabSupported = mParameters.get("video-stabilization-supported");
1368 if ("true".equals(vstabSupported)) {
1369 mParameters.set("video-stabilization", "true");
1370 }
1371
1372 // Set picture size.
1373 // The logic here is different from the logic in still-mode camera.
1374 // There we determine the preview size based on the picture size, but
1375 // here we determine the picture size based on the preview size.
1376 List<Size> supported = mParameters.getSupportedPictureSizes();
Angus Kongb50b5cb2013-08-09 14:55:20 -07001377 Size optimalSize = CameraUtil.getOptimalVideoSnapshotPictureSize(supported,
Michael Kolb8872c232013-01-29 10:33:22 -08001378 (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
1379 Size original = mParameters.getPictureSize();
1380 if (!original.equals(optimalSize)) {
1381 mParameters.setPictureSize(optimalSize.width, optimalSize.height);
1382 }
1383 Log.v(TAG, "Video snapshot size is " + optimalSize.width + "x" +
1384 optimalSize.height);
1385
1386 // Set JPEG quality.
1387 int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1388 CameraProfile.QUALITY_HIGH);
1389 mParameters.setJpegQuality(jpegQuality);
1390
Doris Liu6432cd62013-06-13 17:20:31 -07001391 mCameraDevice.setParameters(mParameters);
Michael Kolb8872c232013-01-29 10:33:22 -08001392 // Keep preview size up to date.
Doris Liu6432cd62013-06-13 17:20:31 -07001393 mParameters = mCameraDevice.getParameters();
Sascha Haeberlingf8b877c2013-09-09 17:32:48 -07001394
1395 // Update UI based on the new parameters.
1396 mUI.updateOnScreenIndicators(mParameters, mPreferences);
Michael Kolb8872c232013-01-29 10:33:22 -08001397 }
1398
1399 @Override
Angus Kong20fad242013-11-11 18:23:46 -08001400 public void resume() {
Angus Kongc4e66562013-11-22 23:03:21 -08001401 mPaused = false;
Angus Kong13e87c42013-11-25 10:02:47 -08001402 installIntentFilter();
Angus Kongc4e66562013-11-22 23:03:21 -08001403 mUI.enableShutter(false);
1404 mZoomValue = 0;
1405
1406 showVideoSnapshotUI(false);
1407
1408 if (!mPreviewing) {
1409 requestCamera(mCameraId);
1410 } else {
1411 // preview already started
1412 mUI.enableShutter(true);
1413 }
1414
1415 mUI.initDisplayChangeListener();
1416
Angus Kongc4e66562013-11-22 23:03:21 -08001417 // Initialize location service.
1418 boolean recordLocation = RecordLocationPreference.get(mPreferences,
1419 mContentResolver);
1420 mLocationManager.recordLocation(recordLocation);
1421
1422 if (mPreviewing) {
1423 mOnResumeTime = SystemClock.uptimeMillis();
Angus Kong13e87c42013-11-25 10:02:47 -08001424 mHandler.sendEmptyMessageDelayed(MSG_CHECK_DISPLAY_ROTATION, 100);
Angus Kongc4e66562013-11-22 23:03:21 -08001425 }
1426
1427 UsageStatistics.onContentViewChanged(
1428 UsageStatistics.COMPONENT_CAMERA, "VideoModule");
Angus Kong20fad242013-11-11 18:23:46 -08001429 }
1430
1431 @Override
1432 public void pause() {
Angus Kongc4e66562013-11-22 23:03:21 -08001433 mPaused = true;
1434
1435 mUI.showPreviewCover();
1436 if (mMediaRecorderRecording) {
1437 // Camera will be released in onStopVideoRecording.
1438 onStopVideoRecording();
1439 } else {
1440 stopPreview();
1441 closeCamera();
1442 releaseMediaRecorder();
1443 }
1444
1445 closeVideoFileDescriptor();
1446
1447
1448 releasePreviewResources();
1449
1450 if (mReceiver != null) {
1451 mActivity.unregisterReceiver(mReceiver);
1452 mReceiver = null;
1453 }
Angus Kongc4e66562013-11-22 23:03:21 -08001454
1455 if (mLocationManager != null) mLocationManager.recordLocation(false);
1456
Angus Kong13e87c42013-11-25 10:02:47 -08001457 mHandler.removeMessages(MSG_CHECK_DISPLAY_ROTATION);
1458 mHandler.removeMessages(MSG_SWITCH_CAMERA);
1459 mHandler.removeMessages(MSG_SWITCH_CAMERA_START_ANIMATION);
Angus Kongc4e66562013-11-22 23:03:21 -08001460 mPendingSwitchCameraId = -1;
1461 mSwitchingCamera = false;
1462 mPreferenceRead = false;
1463
1464 mUI.collapseCameraControls();
1465 mUI.removeDisplayChangeListener();
Angus Kong20fad242013-11-11 18:23:46 -08001466 }
1467
1468 @Override
1469 public void destroy() {
1470
1471 }
1472
1473 @Override
1474 public void onPreviewSizeChanged(int width, int height) {
1475 // TODO: implement this
1476 }
1477
1478 @Override
Michael Kolb8872c232013-01-29 10:33:22 -08001479 public void onConfigurationChanged(Configuration newConfig) {
Doris Liu6a0de792013-02-26 10:54:25 -08001480 Log.v(TAG, "onConfigurationChanged");
Michael Kolb8872c232013-01-29 10:33:22 -08001481 setDisplayOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001482 }
1483
1484 @Override
1485 public void onOverriddenPreferencesClicked() {
1486 }
1487
1488 @Override
1489 // TODO: Delete this after old camera code is removed
1490 public void onRestorePreferencesClicked() {
1491 }
1492
Michael Kolb8872c232013-01-29 10:33:22 -08001493 @Override
1494 public void onSharedPreferenceChanged() {
1495 // ignore the events after "onPause()" or preview has not started yet
Sascha Haeberlingf8b877c2013-09-09 17:32:48 -07001496 if (mPaused) {
1497 return;
1498 }
Michael Kolb8872c232013-01-29 10:33:22 -08001499 synchronized (mPreferences) {
1500 // If mCameraDevice is not ready then we can set the parameter in
1501 // startPreview().
Doris Liu6432cd62013-06-13 17:20:31 -07001502 if (mCameraDevice == null) return;
Michael Kolb8872c232013-01-29 10:33:22 -08001503
1504 boolean recordLocation = RecordLocationPreference.get(
1505 mPreferences, mContentResolver);
1506 mLocationManager.recordLocation(recordLocation);
1507
Michael Kolb8872c232013-01-29 10:33:22 -08001508 readVideoPreferences();
Doris Liu6827ce22013-03-12 19:24:28 -07001509 mUI.showTimeLapseUI(mCaptureTimeLapse);
Michael Kolb8872c232013-01-29 10:33:22 -08001510 // We need to restart the preview if preview size is changed.
1511 Size size = mParameters.getPreviewSize();
1512 if (size.width != mDesiredPreviewWidth
1513 || size.height != mDesiredPreviewHeight) {
Doris Liu3973deb2013-08-21 13:42:22 -07001514
1515 stopPreview();
Michael Kolb8872c232013-01-29 10:33:22 -08001516 resizeForPreviewAspectRatio();
1517 startPreview(); // Parameters will be set in startPreview().
1518 } else {
1519 setCameraParameters();
1520 }
Michael Kolb87880792013-04-30 15:38:49 -07001521 mUI.updateOnScreenIndicators(mParameters, mPreferences);
Michael Kolb8872c232013-01-29 10:33:22 -08001522 }
1523 }
1524
Doris Liu6827ce22013-03-12 19:24:28 -07001525 protected void setCameraId(int cameraId) {
1526 ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID);
1527 pref.setValue("" + cameraId);
Michael Kolb8872c232013-01-29 10:33:22 -08001528 }
1529
1530 private void switchCamera() {
Sascha Haeberlingf8b877c2013-09-09 17:32:48 -07001531 if (mPaused) {
1532 return;
1533 }
Michael Kolb8872c232013-01-29 10:33:22 -08001534
1535 Log.d(TAG, "Start to switch camera.");
1536 mCameraId = mPendingSwitchCameraId;
1537 mPendingSwitchCameraId = -1;
Doris Liu6827ce22013-03-12 19:24:28 -07001538 setCameraId(mCameraId);
Michael Kolb8872c232013-01-29 10:33:22 -08001539
1540 closeCamera();
Angus Kong20fad242013-11-11 18:23:46 -08001541 requestCamera(mCameraId);
Doris Liu6827ce22013-03-12 19:24:28 -07001542 mUI.collapseCameraControls();
Michael Kolb8872c232013-01-29 10:33:22 -08001543 // Restart the camera and initialize the UI. From onCreate.
1544 mPreferences.setLocalId(mActivity, mCameraId);
1545 CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
Michael Kolb8872c232013-01-29 10:33:22 -08001546
1547 // From onResume
Doris Liu6432cd62013-06-13 17:20:31 -07001548 mZoomValue = 0;
Doris Liu6827ce22013-03-12 19:24:28 -07001549 mUI.setOrientationIndicator(0, false);
Michael Kolb8872c232013-01-29 10:33:22 -08001550
Doris Liu6432cd62013-06-13 17:20:31 -07001551 // Start switch camera animation. Post a message because
1552 // onFrameAvailable from the old camera may already exist.
Angus Kong13e87c42013-11-25 10:02:47 -08001553 mHandler.sendEmptyMessage(MSG_SWITCH_CAMERA_START_ANIMATION);
Michael Kolb87880792013-04-30 15:38:49 -07001554 mUI.updateOnScreenIndicators(mParameters, mPreferences);
Michael Kolb8872c232013-01-29 10:33:22 -08001555 }
1556
Michael Kolb8872c232013-01-29 10:33:22 -08001557 private void initializeVideoSnapshot() {
Doris Liu537718c2013-02-20 16:29:44 -08001558 if (mParameters == null) return;
Angus Kongb50b5cb2013-08-09 14:55:20 -07001559 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
Michael Kolb8872c232013-01-29 10:33:22 -08001560 // Show the tap to focus toast if this is the first start.
1561 if (mPreferences.getBoolean(
1562 CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, true)) {
1563 // Delay the toast for one second to wait for orientation.
Angus Kong13e87c42013-11-25 10:02:47 -08001564 mHandler.sendEmptyMessageDelayed(MSG_SHOW_TAP_TO_SNAPSHOT_TOAST, 1000);
Michael Kolb8872c232013-01-29 10:33:22 -08001565 }
Michael Kolb8872c232013-01-29 10:33:22 -08001566 }
1567 }
1568
1569 void showVideoSnapshotUI(boolean enabled) {
Doris Liu537718c2013-02-20 16:29:44 -08001570 if (mParameters == null) return;
Angus Kongb50b5cb2013-08-09 14:55:20 -07001571 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
Doris Liu6432cd62013-06-13 17:20:31 -07001572 if (enabled) {
Sascha Haeberling37f36112013-08-06 14:31:52 -07001573 mUI.animateFlash();
Doris Liu3973deb2013-08-21 13:42:22 -07001574 mUI.animateCapture();
Michael Kolb8872c232013-01-29 10:33:22 -08001575 } else {
Doris Liu6827ce22013-03-12 19:24:28 -07001576 mUI.showPreviewBorder(enabled);
Michael Kolb8872c232013-01-29 10:33:22 -08001577 }
Doris Liu6827ce22013-03-12 19:24:28 -07001578 mUI.enableShutter(!enabled);
Michael Kolb8872c232013-01-29 10:33:22 -08001579 }
1580 }
1581
ztenghui7b265a62013-09-09 14:58:44 -07001582 private void forceFlashOffIfSupported(boolean forceOff) {
1583 String flashMode;
1584 if (!forceOff) {
1585 flashMode = mPreferences.getString(
1586 CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
1587 mActivity.getString(R.string.pref_camera_video_flashmode_default));
1588 } else {
1589 flashMode = Parameters.FLASH_MODE_OFF;
1590 }
1591 List<String> supportedFlash = mParameters.getSupportedFlashModes();
1592 if (isSupported(flashMode, supportedFlash)) {
1593 mParameters.setFlashMode(flashMode);
1594 } else {
1595 flashMode = mParameters.getFlashMode();
1596 if (flashMode == null) {
1597 flashMode = mActivity.getString(
1598 R.string.pref_camera_flashmode_no_flash);
Michael Kolb8872c232013-01-29 10:33:22 -08001599 }
Michael Kolb8872c232013-01-29 10:33:22 -08001600 }
1601 }
1602
ztenghui7b265a62013-09-09 14:58:44 -07001603 /**
1604 * Used to update the flash mode. Video mode can turn on the flash as torch
1605 * mode, which we would like to turn on and off when we switching in and
1606 * out to the preview.
1607 *
1608 * @param forceOff whether we want to force the flash off.
1609 */
1610 private void forceFlashOff(boolean forceOff) {
1611 if (!mPreviewing || mParameters.getFlashMode() == null) {
1612 return;
1613 }
1614 forceFlashOffIfSupported(forceOff);
1615 mCameraDevice.setParameters(mParameters);
ztenghui285a5be2013-10-09 15:59:47 -07001616 mUI.updateOnScreenIndicators(mParameters, mPreferences);
ztenghui7b265a62013-09-09 14:58:44 -07001617 }
1618
Michael Kolb8872c232013-01-29 10:33:22 -08001619 @Override
ztenghui7b265a62013-09-09 14:58:44 -07001620 public void onPreviewFocusChanged(boolean previewFocused) {
1621 mUI.onPreviewFocusChanged(previewFocused);
1622 forceFlashOff(!previewFocused);
Michael Kolb8872c232013-01-29 10:33:22 -08001623 }
1624
Erin Dahlgren3044d8c2013-10-10 18:23:45 -07001625 @Override
1626 public boolean arePreviewControlsVisible() {
1627 return mUI.arePreviewControlsVisible();
1628 }
1629
Angus Kong9ef99252013-07-18 18:04:19 -07001630 private final class JpegPictureCallback implements CameraPictureCallback {
Michael Kolb8872c232013-01-29 10:33:22 -08001631 Location mLocation;
1632
1633 public JpegPictureCallback(Location loc) {
1634 mLocation = loc;
1635 }
1636
1637 @Override
Angus Kong9ef99252013-07-18 18:04:19 -07001638 public void onPictureTaken(byte [] jpegData, CameraProxy camera) {
Michael Kolb8872c232013-01-29 10:33:22 -08001639 Log.v(TAG, "onPictureTaken");
1640 mSnapshotInProgress = false;
1641 showVideoSnapshotUI(false);
1642 storeImage(jpegData, mLocation);
1643 }
1644 }
1645
1646 private void storeImage(final byte[] data, Location loc) {
1647 long dateTaken = System.currentTimeMillis();
Angus Kongb50b5cb2013-08-09 14:55:20 -07001648 String title = CameraUtil.createJpegName(dateTaken);
Angus Kong0d00a892013-03-26 11:40:40 -07001649 ExifInterface exif = Exif.getExif(data);
1650 int orientation = Exif.getOrientation(exif);
Doris Liu6df2d962013-08-20 16:31:29 -07001651
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08001652 getServices().getMediaSaver().addImage(
Doris Liu6df2d962013-08-20 16:31:29 -07001653 data, title, dateTaken, loc, orientation,
Angus Kong83a99ae2013-04-17 15:37:07 -07001654 exif, mOnPhotoSavedListener, mContentResolver);
Michael Kolb8872c232013-01-29 10:33:22 -08001655 }
1656
Michael Kolb8872c232013-01-29 10:33:22 -08001657 private String convertOutputFormatToMimeType(int outputFileFormat) {
1658 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1659 return "video/mp4";
1660 }
1661 return "video/3gpp";
1662 }
1663
1664 private String convertOutputFormatToFileExt(int outputFileFormat) {
1665 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1666 return ".mp4";
1667 }
1668 return ".3gp";
1669 }
1670
1671 private void closeVideoFileDescriptor() {
1672 if (mVideoFileDescriptor != null) {
1673 try {
1674 mVideoFileDescriptor.close();
1675 } catch (IOException e) {
1676 Log.e(TAG, "Fail to close fd", e);
1677 }
1678 mVideoFileDescriptor = null;
1679 }
1680 }
1681
1682 private void showTapToSnapshotToast() {
1683 new RotateTextToast(mActivity, R.string.video_snapshot_hint, 0)
1684 .show();
1685 // Clear the preference.
1686 Editor editor = mPreferences.edit();
1687 editor.putBoolean(CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, false);
1688 editor.apply();
1689 }
1690
Michael Kolb8872c232013-01-29 10:33:22 -08001691 // required by OnPreferenceChangedListener
1692 @Override
1693 public void onCameraPickerClicked(int cameraId) {
1694 if (mPaused || mPendingSwitchCameraId != -1) return;
1695
1696 mPendingSwitchCameraId = cameraId;
Doris Liu6432cd62013-06-13 17:20:31 -07001697 Log.d(TAG, "Start to copy texture.");
Angus Kong20fad242013-11-11 18:23:46 -08001698
Doris Liu6432cd62013-06-13 17:20:31 -07001699 // Disable all camera controls.
1700 mSwitchingCamera = true;
Sascha Haeberling0d63b892013-08-19 11:29:04 -07001701 switchCamera();
Michael Kolb8872c232013-01-29 10:33:22 -08001702
Doris Liu6a0de792013-02-26 10:54:25 -08001703 }
1704
1705 @Override
Angus Kongfd4fc0e2013-11-07 15:38:09 -08001706 public void onMediaSaverAvailable(MediaSaver s) {
Angus Kong86d36312013-01-31 18:22:44 -08001707 // do nothing.
1708 }
Angus Kong395ee2d2013-07-15 12:42:41 -07001709
1710 @Override
1711 public void onPreviewUIReady() {
1712 startPreview();
1713 }
1714
1715 @Override
1716 public void onPreviewUIDestroyed() {
1717 stopPreview();
1718 }
Angus Kong20fad242013-11-11 18:23:46 -08001719
1720 private void requestCamera(int id) {
1721 mActivity.getCameraProvider().requestCamera(id);
1722 }
Michael Kolb8872c232013-01-29 10:33:22 -08001723}