blob: 0a6faba6a405ba6c052b67f29ac4d35a083915fb [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 Kongb50b5cb2013-08-09 14:55:20 -070058import com.android.camera.app.OrientationManager;
59import com.android.camera.util.ApiHelper;
60import com.android.camera.util.AccessibilityUtils;
Michael Kolb8872c232013-01-29 10:33:22 -080061import com.android.camera.ui.PopupManager;
Michael Kolb8872c232013-01-29 10:33:22 -080062import com.android.camera.ui.RotateTextToast;
Angus Kongb50b5cb2013-08-09 14:55:20 -070063import com.android.camera.util.CameraUtil;
Sascha Haeberling8e963a52013-08-06 11:43:02 -070064import com.android.camera.util.UsageStatistics;
65import com.android.camera2.R;
Angus Kong0d00a892013-03-26 11:40:40 -070066import com.android.gallery3d.exif.ExifInterface;
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,
80 MediaRecorder.OnInfoListener,
Doris Liu6827ce22013-03-12 19:24:28 -070081 EffectsRecorder.EffectsListener {
Michael Kolb8872c232013-01-29 10:33:22 -080082
83 private static final String TAG = "CAM_VideoModule";
84
85 // We number the request code from 1000 to avoid collision with Gallery.
86 private static final int REQUEST_EFFECT_BACKDROPPER = 1000;
87
88 private static final int CHECK_DISPLAY_ROTATION = 3;
89 private static final int CLEAR_SCREEN_DELAY = 4;
90 private static final int UPDATE_RECORD_TIME = 5;
91 private static final int ENABLE_SHUTTER_BUTTON = 6;
92 private static final int SHOW_TAP_TO_SNAPSHOT_TOAST = 7;
93 private static final int SWITCH_CAMERA = 8;
94 private static final int SWITCH_CAMERA_START_ANIMATION = 9;
Michael Kolb8872c232013-01-29 10:33:22 -080095
96 private static final int SCREEN_DELAY = 2 * 60 * 1000;
97
98 private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
99
100 /**
101 * An unpublished intent flag requesting to start recording straight away
102 * and return as soon as recording is stopped.
103 * TODO: consider publishing by moving into MediaStore.
104 */
105 private static final String EXTRA_QUICK_CAPTURE =
106 "android.intent.extra.quickCapture";
107
Michael Kolb8872c232013-01-29 10:33:22 -0800108 // module fields
109 private CameraActivity mActivity;
Michael Kolb8872c232013-01-29 10:33:22 -0800110 private boolean mPaused;
111 private int mCameraId;
112 private Parameters mParameters;
113
Doris Liu6432cd62013-06-13 17:20:31 -0700114 private boolean mIsInReviewMode;
Michael Kolb8872c232013-01-29 10:33:22 -0800115 private boolean mSnapshotInProgress = false;
116
117 private static final String EFFECT_BG_FROM_GALLERY = "gallery";
118
119 private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
120
121 private ComboPreferences mPreferences;
122 private PreferenceGroup mPreferenceGroup;
Angus Kong395ee2d2013-07-15 12:42:41 -0700123 // Preference must be read before starting preview. We check this before starting
124 // preview.
125 private boolean mPreferenceRead;
Michael Kolb8872c232013-01-29 10:33:22 -0800126
Michael Kolb8872c232013-01-29 10:33:22 -0800127 private boolean mIsVideoCaptureIntent;
128 private boolean mQuickCapture;
129
130 private MediaRecorder mMediaRecorder;
131 private EffectsRecorder mEffectsRecorder;
132 private boolean mEffectsDisplayResult;
133
134 private int mEffectType = EffectsRecorder.EFFECT_NONE;
135 private Object mEffectParameter = null;
136 private String mEffectUriFromGallery = null;
137 private String mPrefVideoEffectDefault;
138 private boolean mResetEffect = true;
139
140 private boolean mSwitchingCamera;
141 private boolean mMediaRecorderRecording = false;
142 private long mRecordingStartTime;
143 private boolean mRecordingTimeCountsDown = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800144 private long mOnResumeTime;
145 // The video file that the hardware camera is about to record into
146 // (or is recording into.)
147 private String mVideoFilename;
148 private ParcelFileDescriptor mVideoFileDescriptor;
149
150 // The video file that has already been recorded, and that is being
151 // examined by the user.
152 private String mCurrentVideoFilename;
153 private Uri mCurrentVideoUri;
154 private ContentValues mCurrentVideoValues;
155
156 private CamcorderProfile mProfile;
157
158 // The video duration limit. 0 menas no limit.
159 private int mMaxVideoDurationInMs;
160
161 // Time Lapse parameters.
162 private boolean mCaptureTimeLapse = false;
163 // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
164 private int mTimeBetweenTimeLapseFrameCaptureMs = 0;
Michael Kolb8872c232013-01-29 10:33:22 -0800165
166 boolean mPreviewing = false; // True if preview is started.
167 // The display rotation in degrees. This is only valid when mPreviewing is
168 // true.
169 private int mDisplayRotation;
170 private int mCameraDisplayOrientation;
171
Doris Liu6827ce22013-03-12 19:24:28 -0700172 private int mDesiredPreviewWidth;
173 private int mDesiredPreviewHeight;
Michael Kolb8872c232013-01-29 10:33:22 -0800174 private ContentResolver mContentResolver;
175
176 private LocationManager mLocationManager;
Doris Liu6432cd62013-06-13 17:20:31 -0700177 private OrientationManager mOrientationManager;
Michael Kolb8872c232013-01-29 10:33:22 -0800178
Michael Kolb8872c232013-01-29 10:33:22 -0800179 private int mPendingSwitchCameraId;
Doris Liu6432cd62013-06-13 17:20:31 -0700180 private boolean mOpenCameraFail;
181 private boolean mCameraDisabled;
Michael Kolb8872c232013-01-29 10:33:22 -0800182 private final Handler mHandler = new MainHandler();
Doris Liu6827ce22013-03-12 19:24:28 -0700183 private VideoUI mUI;
Doris Liu6432cd62013-06-13 17:20:31 -0700184 private CameraProxy mCameraDevice;
185
Michael Kolb8872c232013-01-29 10:33:22 -0800186 // The degrees of the device rotated clockwise from its natural orientation.
187 private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
188
189 private int mZoomValue; // The current zoom value.
Doris Liu6827ce22013-03-12 19:24:28 -0700190
Michael Kolb8872c232013-01-29 10:33:22 -0800191 private boolean mRestoreFlash; // This is used to check if we need to restore the flash
192 // status when going back from gallery.
193
Angus Kong83a99ae2013-04-17 15:37:07 -0700194 private final MediaSaveService.OnMediaSavedListener mOnVideoSavedListener =
195 new MediaSaveService.OnMediaSavedListener() {
196 @Override
197 public void onMediaSaved(Uri uri) {
198 if (uri != null) {
Doris Liu2a7f44c2013-08-12 15:18:53 -0700199 mCurrentVideoUri = uri;
200 onVideoSaved();
Sascha Haeberling37f36112013-08-06 14:31:52 -0700201 mActivity.notifyNewMedia(uri);
Angus Kong83a99ae2013-04-17 15:37:07 -0700202 }
203 }
204 };
205
206 private final MediaSaveService.OnMediaSavedListener mOnPhotoSavedListener =
Angus Kong86d36312013-01-31 18:22:44 -0800207 new MediaSaveService.OnMediaSavedListener() {
208 @Override
209 public void onMediaSaved(Uri uri) {
210 if (uri != null) {
Sascha Haeberling37f36112013-08-06 14:31:52 -0700211 mActivity.notifyNewMedia(uri);
Angus Kong86d36312013-01-31 18:22:44 -0800212 }
213 }
214 };
215
216
Michael Kolb8872c232013-01-29 10:33:22 -0800217 protected class CameraOpenThread extends Thread {
218 @Override
219 public void run() {
220 openCamera();
221 }
222 }
223
224 private void openCamera() {
225 try {
Angus Kong9ef99252013-07-18 18:04:19 -0700226 if (mCameraDevice == null) {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700227 mCameraDevice = CameraUtil.openCamera(mActivity, mCameraId);
Michael Kolbb7e9fe82013-03-12 10:42:56 -0700228 }
Doris Liu6432cd62013-06-13 17:20:31 -0700229 mParameters = mCameraDevice.getParameters();
Michael Kolb8872c232013-01-29 10:33:22 -0800230 } catch (CameraHardwareException e) {
Doris Liu6432cd62013-06-13 17:20:31 -0700231 mOpenCameraFail = true;
Michael Kolb8872c232013-01-29 10:33:22 -0800232 } catch (CameraDisabledException e) {
Doris Liu6432cd62013-06-13 17:20:31 -0700233 mCameraDisabled = true;
Michael Kolb8872c232013-01-29 10:33:22 -0800234 }
235 }
236
237 // This Handler is used to post message back onto the main thread of the
238 // application
239 private class MainHandler extends Handler {
240 @Override
241 public void handleMessage(Message msg) {
242 switch (msg.what) {
243
244 case ENABLE_SHUTTER_BUTTON:
Doris Liu6827ce22013-03-12 19:24:28 -0700245 mUI.enableShutter(true);
Michael Kolb8872c232013-01-29 10:33:22 -0800246 break;
247
248 case CLEAR_SCREEN_DELAY: {
249 mActivity.getWindow().clearFlags(
250 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
251 break;
252 }
253
254 case UPDATE_RECORD_TIME: {
255 updateRecordingTime();
256 break;
257 }
258
259 case CHECK_DISPLAY_ROTATION: {
260 // Restart the preview if display rotation has changed.
261 // Sometimes this happens when the device is held upside
262 // down and camera app is opened. Rotation animation will
263 // take some time and the rotation value we have got may be
264 // wrong. Framework does not have a callback for this now.
Angus Kongb50b5cb2013-08-09 14:55:20 -0700265 if ((CameraUtil.getDisplayRotation(mActivity) != mDisplayRotation)
Michael Kolb8872c232013-01-29 10:33:22 -0800266 && !mMediaRecorderRecording && !mSwitchingCamera) {
267 startPreview();
268 }
269 if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
270 mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
271 }
272 break;
273 }
274
275 case SHOW_TAP_TO_SNAPSHOT_TOAST: {
276 showTapToSnapshotToast();
277 break;
278 }
279
280 case SWITCH_CAMERA: {
281 switchCamera();
282 break;
283 }
284
285 case SWITCH_CAMERA_START_ANIMATION: {
Doris Liu6432cd62013-06-13 17:20:31 -0700286 //TODO:
287 //((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
Michael Kolb8872c232013-01-29 10:33:22 -0800288
289 // Enable all camera controls.
290 mSwitchingCamera = false;
291 break;
292 }
293
Michael Kolb8872c232013-01-29 10:33:22 -0800294 default:
295 Log.v(TAG, "Unhandled message: " + msg.what);
296 break;
297 }
298 }
299 }
300
301 private BroadcastReceiver mReceiver = null;
302
303 private class MyBroadcastReceiver extends BroadcastReceiver {
304 @Override
305 public void onReceive(Context context, Intent intent) {
306 String action = intent.getAction();
307 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
308 stopVideoRecording();
309 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
310 Toast.makeText(mActivity,
311 mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
312 }
313 }
314 }
315
316 private String createName(long dateTaken) {
317 Date date = new Date(dateTaken);
318 SimpleDateFormat dateFormat = new SimpleDateFormat(
319 mActivity.getString(R.string.video_file_name_format));
320
321 return dateFormat.format(date);
322 }
323
324 private int getPreferredCameraId(ComboPreferences preferences) {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700325 int intentCameraId = CameraUtil.getCameraFacingIntentExtras(mActivity);
Michael Kolb8872c232013-01-29 10:33:22 -0800326 if (intentCameraId != -1) {
327 // Testing purpose. Launch a specific camera through the intent
328 // extras.
329 return intentCameraId;
330 } else {
331 return CameraSettings.readPreferredCameraId(preferences);
332 }
333 }
334
335 private void initializeSurfaceView() {
Doris Liu6827ce22013-03-12 19:24:28 -0700336 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { // API level < 16
Doris Liu6432cd62013-06-13 17:20:31 -0700337 mUI.initializeSurfaceView();
Michael Kolb8872c232013-01-29 10:33:22 -0800338 }
339 }
340
341 @Override
Doris Liu6432cd62013-06-13 17:20:31 -0700342 public void init(CameraActivity activity, View root) {
Michael Kolb8872c232013-01-29 10:33:22 -0800343 mActivity = activity;
Doris Liu6827ce22013-03-12 19:24:28 -0700344 mUI = new VideoUI(activity, this, root);
Michael Kolb8872c232013-01-29 10:33:22 -0800345 mPreferences = new ComboPreferences(mActivity);
346 CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
347 mCameraId = getPreferredCameraId(mPreferences);
348
349 mPreferences.setLocalId(mActivity, mCameraId);
350 CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
351
Michael Kolb8872c232013-01-29 10:33:22 -0800352 mPrefVideoEffectDefault = mActivity.getString(R.string.pref_video_effect_default);
353 resetEffect();
Doris Liu6432cd62013-06-13 17:20:31 -0700354 mOrientationManager = new OrientationManager(mActivity);
Michael Kolb8872c232013-01-29 10:33:22 -0800355
356 /*
357 * To reduce startup time, we start the preview in another thread.
358 * We make sure the preview is started at the end of onCreate.
359 */
360 CameraOpenThread cameraOpenThread = new CameraOpenThread();
361 cameraOpenThread.start();
362
363 mContentResolver = mActivity.getContentResolver();
364
Michael Kolb8872c232013-01-29 10:33:22 -0800365 // Surface texture is from camera screen nail and startPreview needs it.
366 // This must be done before startPreview.
367 mIsVideoCaptureIntent = isVideoCaptureIntent();
Michael Kolb8872c232013-01-29 10:33:22 -0800368 initializeSurfaceView();
369
370 // Make sure camera device is opened.
371 try {
372 cameraOpenThread.join();
Doris Liu6432cd62013-06-13 17:20:31 -0700373 if (mOpenCameraFail) {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700374 CameraUtil.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
Michael Kolb8872c232013-01-29 10:33:22 -0800375 return;
Doris Liu6432cd62013-06-13 17:20:31 -0700376 } else if (mCameraDisabled) {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700377 CameraUtil.showErrorAndFinish(mActivity, R.string.camera_disabled);
Michael Kolb8872c232013-01-29 10:33:22 -0800378 return;
379 }
380 } catch (InterruptedException ex) {
381 // ignore
382 }
383
384 readVideoPreferences();
Doris Liu6827ce22013-03-12 19:24:28 -0700385 mUI.setPrefChangedListener(this);
Michael Kolb8872c232013-01-29 10:33:22 -0800386
Michael Kolb8872c232013-01-29 10:33:22 -0800387 mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
388 mLocationManager = new LocationManager(mActivity, null);
389
Doris Liu6827ce22013-03-12 19:24:28 -0700390 mUI.setOrientationIndicator(0, false);
Michael Kolb8872c232013-01-29 10:33:22 -0800391 setDisplayOrientation();
392
Doris Liu6827ce22013-03-12 19:24:28 -0700393 mUI.showTimeLapseUI(mCaptureTimeLapse);
Michael Kolb8872c232013-01-29 10:33:22 -0800394 initializeVideoSnapshot();
395 resizeForPreviewAspectRatio();
396
397 initializeVideoControl();
398 mPendingSwitchCameraId = -1;
Michael Kolb87880792013-04-30 15:38:49 -0700399 mUI.updateOnScreenIndicators(mParameters, mPreferences);
Doris Liu6827ce22013-03-12 19:24:28 -0700400
401 // Disable the shutter button if effects are ON since it might take
402 // a little more time for the effects preview to be ready. We do not
403 // want to allow recording before that happens. The shutter button
404 // will be enabled when we get the message from effectsrecorder that
405 // the preview is running. This becomes critical when the camera is
406 // swapped.
407 if (effectsActive()) {
408 mUI.enableShutter(false);
409 }
410 }
411
412 // SingleTapListener
413 // Preview area is touched. Take a picture.
414 @Override
415 public void onSingleTapUp(View view, int x, int y) {
416 if (mMediaRecorderRecording && effectsActive()) {
417 new RotateTextToast(mActivity, R.string.disable_video_snapshot_hint,
418 mOrientation).show();
419 return;
420 }
Doris Liu38605742013-08-13 15:01:52 -0700421 takeASnapshot();
422 }
423
424 private void takeASnapshot() {
Sascha Haeberlinga514b142013-08-13 16:10:01 -0700425 // Only take snapshots if video snapshot is supported by device
Doris Liu38605742013-08-13 15:01:52 -0700426 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
Sascha Haeberlinga514b142013-08-13 16:10:01 -0700427 if (!mMediaRecorderRecording || mPaused || mSnapshotInProgress || effectsActive()) {
428 return;
429 }
Doris Liu38605742013-08-13 15:01:52 -0700430 MediaSaveService s = mActivity.getMediaSaveService();
Sascha Haeberlinga514b142013-08-13 16:10:01 -0700431 if (s == null || s.isQueueFull()) {
Doris Liu38605742013-08-13 15:01:52 -0700432 return;
433 }
434
435 // Set rotation and gps data.
436 int rotation = CameraUtil.getJpegRotation(mCameraId, mOrientation);
437 mParameters.setRotation(rotation);
438 Location loc = mLocationManager.getCurrentLocation();
439 CameraUtil.setGpsParameters(mParameters, loc);
440 mCameraDevice.setParameters(mParameters);
441
442 Log.v(TAG, "Video snapshot start");
443 mCameraDevice.takePicture(mHandler,
444 null, null, null, new JpegPictureCallback(loc));
445 showVideoSnapshotUI(true);
446 mSnapshotInProgress = true;
447 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
448 UsageStatistics.ACTION_CAPTURE_DONE, "VideoSnapshot");
Doris Liu6827ce22013-03-12 19:24:28 -0700449 }
Michael Kolb8872c232013-01-29 10:33:22 -0800450 }
451
452 @Override
453 public void onStop() {}
454
455 private void loadCameraPreferences() {
456 CameraSettings settings = new CameraSettings(mActivity, mParameters,
457 mCameraId, CameraHolder.instance().getCameraInfo());
458 // Remove the video quality preference setting when the quality is given in the intent.
459 mPreferenceGroup = filterPreferenceScreenByIntent(
460 settings.getPreferenceGroup(R.xml.video_preferences));
461 }
462
Michael Kolb8872c232013-01-29 10:33:22 -0800463 private void initializeVideoControl() {
464 loadCameraPreferences();
Doris Liu6827ce22013-03-12 19:24:28 -0700465 mUI.initializePopup(mPreferenceGroup);
Michael Kolb8872c232013-01-29 10:33:22 -0800466 if (effectsActive()) {
Doris Liu6827ce22013-03-12 19:24:28 -0700467 mUI.overrideSettings(
Michael Kolb8872c232013-01-29 10:33:22 -0800468 CameraSettings.KEY_VIDEO_QUALITY,
Doris Liu3f7e0042013-07-31 11:25:09 -0700469 Integer.toString(CamcorderProfile.QUALITY_480P));
Michael Kolb8872c232013-01-29 10:33:22 -0800470 }
471 }
472
Michael Kolb8872c232013-01-29 10:33:22 -0800473 @Override
474 public void onOrientationChanged(int orientation) {
475 // We keep the last known orientation. So if the user first orient
476 // the camera then point the camera to floor or sky, we still have
477 // the correct orientation.
478 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return;
Angus Kongb50b5cb2013-08-09 14:55:20 -0700479 int newOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800480
481 if (mOrientation != newOrientation) {
482 mOrientation = newOrientation;
483 // The input of effects recorder is affected by
484 // android.hardware.Camera.setDisplayOrientation. Its value only
485 // compensates the camera orientation (no Display.getRotation).
486 // So the orientation hint here should only consider sensor
487 // orientation.
488 if (effectsActive()) {
489 mEffectsRecorder.setOrientationHint(mOrientation);
490 }
491 }
492
493 // Show the toast after getting the first orientation changed.
494 if (mHandler.hasMessages(SHOW_TAP_TO_SNAPSHOT_TOAST)) {
495 mHandler.removeMessages(SHOW_TAP_TO_SNAPSHOT_TOAST);
496 showTapToSnapshotToast();
497 }
498 }
499
Michael Kolb8872c232013-01-29 10:33:22 -0800500 private void startPlayVideoActivity() {
501 Intent intent = new Intent(Intent.ACTION_VIEW);
502 intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
503 try {
504 mActivity.startActivity(intent);
505 } catch (ActivityNotFoundException ex) {
506 Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
507 }
508 }
509
510 @OnClickAttr
511 public void onReviewPlayClicked(View v) {
512 startPlayVideoActivity();
513 }
514
515 @OnClickAttr
516 public void onReviewDoneClicked(View v) {
Doris Liu69ef5ea2013-05-07 13:48:10 -0700517 mIsInReviewMode = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800518 doReturnToCaller(true);
519 }
520
521 @OnClickAttr
522 public void onReviewCancelClicked(View v) {
Doris Liu69ef5ea2013-05-07 13:48:10 -0700523 mIsInReviewMode = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800524 stopVideoRecording();
525 doReturnToCaller(false);
526 }
527
Doris Liu69ef5ea2013-05-07 13:48:10 -0700528 @Override
529 public boolean isInReviewMode() {
530 return mIsInReviewMode;
531 }
532
Michael Kolb8872c232013-01-29 10:33:22 -0800533 private void onStopVideoRecording() {
534 mEffectsDisplayResult = true;
535 boolean recordFail = stopVideoRecording();
536 if (mIsVideoCaptureIntent) {
537 if (!effectsActive()) {
538 if (mQuickCapture) {
539 doReturnToCaller(!recordFail);
540 } else if (!recordFail) {
Doris Liu3c2fca32013-02-13 18:28:03 -0800541 showCaptureResult();
Michael Kolb8872c232013-01-29 10:33:22 -0800542 }
543 }
544 } else if (!recordFail){
545 // Start capture animation.
546 if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
547 // The capture animation is disabled on ICS because we use SurfaceView
548 // for preview during recording. When the recording is done, we switch
549 // back to use SurfaceTexture for preview and we need to stop then start
550 // the preview. This will cause the preview flicker since the preview
551 // will not be continuous for a short period of time.
Sascha Haeberling4f91ab52013-05-21 11:26:13 -0700552
Sascha Haeberling37f36112013-08-06 14:31:52 -0700553 mUI.animateFlash();
554 Bitmap bitmap = getVideoThumbnail();
555 if (bitmap != null) {
556 mUI.animateCapture(bitmap);
557 }
Michael Kolb8872c232013-01-29 10:33:22 -0800558 }
559 }
560 }
561
Doris Liu2a7f44c2013-08-12 15:18:53 -0700562 public void onVideoSaved() {
563 if (mIsVideoCaptureIntent) {
564 showCaptureResult();
565 }
566 }
567
Michael Kolb8872c232013-01-29 10:33:22 -0800568 public void onProtectiveCurtainClick(View v) {
569 // Consume clicks
570 }
571
572 @Override
573 public void onShutterButtonClick() {
Doris Liu6827ce22013-03-12 19:24:28 -0700574 if (mUI.collapseCameraControls() || mSwitchingCamera) return;
Michael Kolb8872c232013-01-29 10:33:22 -0800575
576 boolean stop = mMediaRecorderRecording;
577
578 if (stop) {
579 onStopVideoRecording();
580 } else {
581 startVideoRecording();
582 }
Doris Liu6827ce22013-03-12 19:24:28 -0700583 mUI.enableShutter(false);
Michael Kolb8872c232013-01-29 10:33:22 -0800584
585 // Keep the shutter button disabled when in video capture intent
586 // mode and recording is stopped. It'll be re-enabled when
587 // re-take button is clicked.
588 if (!(mIsVideoCaptureIntent && stop)) {
589 mHandler.sendEmptyMessageDelayed(
590 ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
591 }
592 }
593
594 @Override
595 public void onShutterButtonFocus(boolean pressed) {
Doris Liu61f2b082013-03-27 17:25:43 -0700596 mUI.setShutterPressed(pressed);
Michael Kolb8872c232013-01-29 10:33:22 -0800597 }
598
599 private void readVideoPreferences() {
600 // The preference stores values from ListPreference and is thus string type for all values.
601 // We need to convert it to int manually.
Doris Liu3f7e0042013-07-31 11:25:09 -0700602 String videoQuality = mPreferences.getString(CameraSettings.KEY_VIDEO_QUALITY,
603 null);
604 if (videoQuality == null) {
605 // check for highest quality before setting default value
606 videoQuality = CameraSettings.getSupportedHighestVideoQuality(mCameraId,
607 mActivity.getResources().getString(R.string.pref_video_quality_default));
608 mPreferences.edit().putString(CameraSettings.KEY_VIDEO_QUALITY, videoQuality);
609 }
Michael Kolb8872c232013-01-29 10:33:22 -0800610 int quality = Integer.valueOf(videoQuality);
611
612 // Set video quality.
613 Intent intent = mActivity.getIntent();
614 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
615 int extraVideoQuality =
616 intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
617 if (extraVideoQuality > 0) {
618 quality = CamcorderProfile.QUALITY_HIGH;
619 } else { // 0 is mms.
620 quality = CamcorderProfile.QUALITY_LOW;
621 }
622 }
623
624 // Set video duration limit. The limit is read from the preference,
625 // unless it is specified in the intent.
626 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
627 int seconds =
628 intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
629 mMaxVideoDurationInMs = 1000 * seconds;
630 } else {
631 mMaxVideoDurationInMs = CameraSettings.getMaxVideoDuration(mActivity);
632 }
633
634 // Set effect
635 mEffectType = CameraSettings.readEffectType(mPreferences);
636 if (mEffectType != EffectsRecorder.EFFECT_NONE) {
637 mEffectParameter = CameraSettings.readEffectParameter(mPreferences);
638 // Set quality to be no higher than 480p.
639 CamcorderProfile profile = CamcorderProfile.get(mCameraId, quality);
640 if (profile.videoFrameHeight > 480) {
Doris Liu3f7e0042013-07-31 11:25:09 -0700641 quality = CamcorderProfile.QUALITY_480P;
Michael Kolb8872c232013-01-29 10:33:22 -0800642 }
643 } else {
644 mEffectParameter = null;
645 }
646 // Read time lapse recording interval.
647 if (ApiHelper.HAS_TIME_LAPSE_RECORDING) {
648 String frameIntervalStr = mPreferences.getString(
649 CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
650 mActivity.getString(R.string.pref_video_time_lapse_frame_interval_default));
651 mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
652 mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
653 }
654 // TODO: This should be checked instead directly +1000.
655 if (mCaptureTimeLapse) quality += 1000;
656 mProfile = CamcorderProfile.get(mCameraId, quality);
657 getDesiredPreviewSize();
Angus Kong395ee2d2013-07-15 12:42:41 -0700658 mPreferenceRead = true;
Michael Kolb8872c232013-01-29 10:33:22 -0800659 }
660
661 private void writeDefaultEffectToPrefs() {
662 ComboPreferences.Editor editor = mPreferences.edit();
663 editor.putString(CameraSettings.KEY_VIDEO_EFFECT,
664 mActivity.getString(R.string.pref_video_effect_default));
665 editor.apply();
666 }
667
668 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
669 private void getDesiredPreviewSize() {
Doris Liu6432cd62013-06-13 17:20:31 -0700670 mParameters = mCameraDevice.getParameters();
Michael Kolb8872c232013-01-29 10:33:22 -0800671 if (ApiHelper.HAS_GET_SUPPORTED_VIDEO_SIZE) {
672 if (mParameters.getSupportedVideoSizes() == null || effectsActive()) {
673 mDesiredPreviewWidth = mProfile.videoFrameWidth;
674 mDesiredPreviewHeight = mProfile.videoFrameHeight;
675 } else { // Driver supports separates outputs for preview and video.
676 List<Size> sizes = mParameters.getSupportedPreviewSizes();
677 Size preferred = mParameters.getPreferredPreviewSizeForVideo();
678 int product = preferred.width * preferred.height;
679 Iterator<Size> it = sizes.iterator();
680 // Remove the preview sizes that are not preferred.
681 while (it.hasNext()) {
682 Size size = it.next();
683 if (size.width * size.height > product) {
684 it.remove();
685 }
686 }
Angus Kongb50b5cb2013-08-09 14:55:20 -0700687 Size optimalSize = CameraUtil.getOptimalPreviewSize(mActivity, sizes,
Michael Kolb8872c232013-01-29 10:33:22 -0800688 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
689 mDesiredPreviewWidth = optimalSize.width;
690 mDesiredPreviewHeight = optimalSize.height;
691 }
692 } else {
693 mDesiredPreviewWidth = mProfile.videoFrameWidth;
694 mDesiredPreviewHeight = mProfile.videoFrameHeight;
695 }
Doris Liu6432cd62013-06-13 17:20:31 -0700696 mUI.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800697 Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
698 ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
699 }
700
701 private void resizeForPreviewAspectRatio() {
Doris Liu6827ce22013-03-12 19:24:28 -0700702 mUI.setAspectRatio(
Michael Kolb8872c232013-01-29 10:33:22 -0800703 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
704 }
705
706 @Override
707 public void installIntentFilter() {
708 // install an intent filter to receive SD card related events.
709 IntentFilter intentFilter =
710 new IntentFilter(Intent.ACTION_MEDIA_EJECT);
711 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
712 intentFilter.addDataScheme("file");
713 mReceiver = new MyBroadcastReceiver();
714 mActivity.registerReceiver(mReceiver, intentFilter);
715 }
716
717 @Override
718 public void onResumeBeforeSuper() {
719 mPaused = false;
720 }
721
722 @Override
723 public void onResumeAfterSuper() {
Doris Liu6432cd62013-06-13 17:20:31 -0700724 if (mOpenCameraFail || mCameraDisabled)
Michael Kolb8872c232013-01-29 10:33:22 -0800725 return;
Doris Liu6827ce22013-03-12 19:24:28 -0700726 mUI.enableShutter(false);
Michael Kolb8872c232013-01-29 10:33:22 -0800727 mZoomValue = 0;
728
729 showVideoSnapshotUI(false);
730
Michael Kolb8872c232013-01-29 10:33:22 -0800731 if (!mPreviewing) {
Doris Liu6827ce22013-03-12 19:24:28 -0700732 resetEffect();
Michael Kolb8872c232013-01-29 10:33:22 -0800733 openCamera();
Doris Liu6432cd62013-06-13 17:20:31 -0700734 if (mOpenCameraFail) {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700735 CameraUtil.showErrorAndFinish(mActivity,
Michael Kolb8872c232013-01-29 10:33:22 -0800736 R.string.cannot_connect_camera);
737 return;
Doris Liu6432cd62013-06-13 17:20:31 -0700738 } else if (mCameraDisabled) {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700739 CameraUtil.showErrorAndFinish(mActivity, R.string.camera_disabled);
Michael Kolb8872c232013-01-29 10:33:22 -0800740 return;
741 }
742 readVideoPreferences();
743 resizeForPreviewAspectRatio();
Angus Kong395ee2d2013-07-15 12:42:41 -0700744 startPreview();
Doris Liuc774ff92013-03-20 19:25:47 -0700745 } else {
746 // preview already started
747 mUI.enableShutter(true);
Michael Kolb8872c232013-01-29 10:33:22 -0800748 }
749
750 // Initializing it here after the preview is started.
Doris Liu6827ce22013-03-12 19:24:28 -0700751 mUI.initializeZoom(mParameters);
Michael Kolb8872c232013-01-29 10:33:22 -0800752
753 keepScreenOnAwhile();
754
755 // Initialize location service.
756 boolean recordLocation = RecordLocationPreference.get(mPreferences,
757 mContentResolver);
758 mLocationManager.recordLocation(recordLocation);
759
760 if (mPreviewing) {
761 mOnResumeTime = SystemClock.uptimeMillis();
762 mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
763 }
764 // Dismiss open menu if exists.
765 PopupManager.getInstance(mActivity).notifyShowPopup(null);
766
Bobby Georgescu0a7dd572013-03-12 22:45:17 -0700767 UsageStatistics.onContentViewChanged(
768 UsageStatistics.COMPONENT_CAMERA, "VideoModule");
Michael Kolb8872c232013-01-29 10:33:22 -0800769 }
770
771 private void setDisplayOrientation() {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700772 mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
773 mCameraDisplayOrientation = CameraUtil.getDisplayOrientation(mDisplayRotation, mCameraId);
Doris Liu6432cd62013-06-13 17:20:31 -0700774 // Change the camera display orientation
775 if (mCameraDevice != null) {
776 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800777 }
Doris Liu6432cd62013-06-13 17:20:31 -0700778 }
779
780 @Override
781 public void updateCameraOrientation() {
782 if (mMediaRecorderRecording) return;
Angus Kongb50b5cb2013-08-09 14:55:20 -0700783 if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
Doris Liu6432cd62013-06-13 17:20:31 -0700784 setDisplayOrientation();
785 }
Michael Kolb8872c232013-01-29 10:33:22 -0800786 }
787
Doris Liu6827ce22013-03-12 19:24:28 -0700788 @Override
789 public int onZoomChanged(int index) {
790 // Not useful to change zoom value when the activity is paused.
791 if (mPaused) return index;
792 mZoomValue = index;
Doris Liu6432cd62013-06-13 17:20:31 -0700793 if (mParameters == null || mCameraDevice == null) return index;
Doris Liu6827ce22013-03-12 19:24:28 -0700794 // Set zoom parameters asynchronously
795 mParameters.setZoom(mZoomValue);
Doris Liu6432cd62013-06-13 17:20:31 -0700796 mCameraDevice.setParameters(mParameters);
797 Parameters p = mCameraDevice.getParameters();
Doris Liu6827ce22013-03-12 19:24:28 -0700798 if (p != null) return p.getZoom();
799 return index;
800 }
Angus Kong395ee2d2013-07-15 12:42:41 -0700801
Michael Kolb8872c232013-01-29 10:33:22 -0800802 private void startPreview() {
803 Log.v(TAG, "startPreview");
804
Angus Kong395ee2d2013-07-15 12:42:41 -0700805 SurfaceTexture surfaceTexture = mUI.getSurfaceTexture();
806 if (!mPreferenceRead || surfaceTexture == null || mPaused == true) return;
807
Doris Liu6432cd62013-06-13 17:20:31 -0700808 mCameraDevice.setErrorCallback(mErrorCallback);
Michael Kolb8872c232013-01-29 10:33:22 -0800809 if (mPreviewing == true) {
810 stopPreview();
811 if (effectsActive() && mEffectsRecorder != null) {
812 mEffectsRecorder.release();
813 mEffectsRecorder = null;
814 }
815 }
816
Michael Kolb8872c232013-01-29 10:33:22 -0800817 setDisplayOrientation();
Doris Liu6432cd62013-06-13 17:20:31 -0700818 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800819 setCameraParameters();
820
821 try {
822 if (!effectsActive()) {
Angus Kong9ef99252013-07-18 18:04:19 -0700823 mCameraDevice.setPreviewTexture(surfaceTexture);
824 mCameraDevice.startPreview();
Michael Kolbb1aeb392013-03-11 12:37:40 -0700825 mPreviewing = true;
826 onPreviewStarted();
Michael Kolb8872c232013-01-29 10:33:22 -0800827 } else {
828 initializeEffectsPreview();
829 mEffectsRecorder.startPreview();
Michael Kolbb1aeb392013-03-11 12:37:40 -0700830 mPreviewing = true;
831 onPreviewStarted();
Michael Kolb8872c232013-01-29 10:33:22 -0800832 }
833 } catch (Throwable ex) {
834 closeCamera();
835 throw new RuntimeException("startPreview failed", ex);
836 } finally {
Angus Kong395ee2d2013-07-15 12:42:41 -0700837 if (mOpenCameraFail) {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700838 CameraUtil.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
Angus Kong395ee2d2013-07-15 12:42:41 -0700839 } else if (mCameraDisabled) {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700840 CameraUtil.showErrorAndFinish(mActivity, R.string.camera_disabled);
Angus Kong395ee2d2013-07-15 12:42:41 -0700841 }
Michael Kolb8872c232013-01-29 10:33:22 -0800842 }
Michael Kolbb1aeb392013-03-11 12:37:40 -0700843 }
844
845 private void onPreviewStarted() {
Doris Liu6827ce22013-03-12 19:24:28 -0700846 mUI.enableShutter(true);
Michael Kolb8872c232013-01-29 10:33:22 -0800847 }
848
Doris Liu6827ce22013-03-12 19:24:28 -0700849 @Override
850 public void stopPreview() {
Doris Liu6432cd62013-06-13 17:20:31 -0700851 if (!mPreviewing) return;
852 mCameraDevice.stopPreview();
Michael Kolb8872c232013-01-29 10:33:22 -0800853 mPreviewing = false;
854 }
855
856 // Closing the effects out. Will shut down the effects graph.
857 private void closeEffects() {
858 Log.v(TAG, "Closing effects");
859 mEffectType = EffectsRecorder.EFFECT_NONE;
860 if (mEffectsRecorder == null) {
861 Log.d(TAG, "Effects are already closed. Nothing to do");
862 return;
863 }
864 // This call can handle the case where the camera is already released
865 // after the recording has been stopped.
866 mEffectsRecorder.release();
867 mEffectsRecorder = null;
868 }
869
870 // By default, we want to close the effects as well with the camera.
871 private void closeCamera() {
872 closeCamera(true);
873 }
874
875 // In certain cases, when the effects are active, we may want to shutdown
876 // only the camera related parts, and handle closing the effects in the
877 // effectsUpdate callback.
878 // For example, in onPause, we want to make the camera available to
879 // outside world immediately, however, want to wait till the effects
880 // callback to shut down the effects. In such a case, we just disconnect
881 // the effects from the camera by calling disconnectCamera. That way
882 // the effects can handle that when shutting down.
883 //
884 // @param closeEffectsAlso - indicates whether we want to close the
885 // effects also along with the camera.
886 private void closeCamera(boolean closeEffectsAlso) {
887 Log.v(TAG, "closeCamera");
Doris Liu6432cd62013-06-13 17:20:31 -0700888 if (mCameraDevice == null) {
Michael Kolb8872c232013-01-29 10:33:22 -0800889 Log.d(TAG, "already stopped.");
890 return;
891 }
892
893 if (mEffectsRecorder != null) {
894 // Disconnect the camera from effects so that camera is ready to
895 // be released to the outside world.
896 mEffectsRecorder.disconnectCamera();
897 }
898 if (closeEffectsAlso) closeEffects();
Doris Liu6432cd62013-06-13 17:20:31 -0700899 mCameraDevice.setZoomChangeListener(null);
900 mCameraDevice.setErrorCallback(null);
Angus Kong9ef99252013-07-18 18:04:19 -0700901 CameraHolder.instance().release();
Angus Kong395ee2d2013-07-15 12:42:41 -0700902 mCameraDevice = null;
Michael Kolb8872c232013-01-29 10:33:22 -0800903 mPreviewing = false;
904 mSnapshotInProgress = false;
905 }
906
907 private void releasePreviewResources() {
Doris Liu6432cd62013-06-13 17:20:31 -0700908 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
909 mUI.hideSurfaceView();
Michael Kolb8872c232013-01-29 10:33:22 -0800910 }
911 }
912
913 @Override
914 public void onPauseBeforeSuper() {
915 mPaused = true;
916
917 if (mMediaRecorderRecording) {
918 // Camera will be released in onStopVideoRecording.
919 onStopVideoRecording();
920 } else {
921 closeCamera();
922 if (!effectsActive()) releaseMediaRecorder();
923 }
924 if (effectsActive()) {
925 // If the effects are active, make sure we tell the graph that the
926 // surfacetexture is not valid anymore. Disconnect the graph from
927 // the display. This should be done before releasing the surface
928 // texture.
929 mEffectsRecorder.disconnectDisplay();
930 } else {
931 // Close the file descriptor and clear the video namer only if the
932 // effects are not active. If effects are active, we need to wait
933 // till we get the callback from the Effects that the graph is done
934 // recording. That also needs a change in the stopVideoRecording()
935 // call to not call closeCamera if the effects are active, because
936 // that will close down the effects are well, thus making this if
937 // condition invalid.
938 closeVideoFileDescriptor();
Michael Kolb8872c232013-01-29 10:33:22 -0800939 }
940
941 releasePreviewResources();
942
943 if (mReceiver != null) {
944 mActivity.unregisterReceiver(mReceiver);
945 mReceiver = null;
946 }
947 resetScreenOn();
948
949 if (mLocationManager != null) mLocationManager.recordLocation(false);
950
951 mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
952 mHandler.removeMessages(SWITCH_CAMERA);
953 mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION);
954 mPendingSwitchCameraId = -1;
955 mSwitchingCamera = false;
Angus Kong395ee2d2013-07-15 12:42:41 -0700956 mPreferenceRead = false;
Doris Liuc3679c02013-08-08 18:08:43 -0700957
958 mUI.collapseCameraControls();
Michael Kolb8872c232013-01-29 10:33:22 -0800959 }
960
961 @Override
962 public void onPauseAfterSuper() {
963 }
964
965 @Override
966 public void onUserInteraction() {
967 if (!mMediaRecorderRecording && !mActivity.isFinishing()) {
968 keepScreenOnAwhile();
969 }
970 }
971
972 @Override
973 public boolean onBackPressed() {
974 if (mPaused) return true;
975 if (mMediaRecorderRecording) {
976 onStopVideoRecording();
977 return true;
Doris Liu6827ce22013-03-12 19:24:28 -0700978 } else if (mUI.hidePieRenderer()) {
Michael Kolb8872c232013-01-29 10:33:22 -0800979 return true;
980 } else {
Doris Liu6827ce22013-03-12 19:24:28 -0700981 return mUI.removeTopLevelPopup();
Michael Kolb8872c232013-01-29 10:33:22 -0800982 }
983 }
984
985 @Override
986 public boolean onKeyDown(int keyCode, KeyEvent event) {
987 // Do not handle any key if the activity is paused.
988 if (mPaused) {
989 return true;
990 }
991
992 switch (keyCode) {
993 case KeyEvent.KEYCODE_CAMERA:
994 if (event.getRepeatCount() == 0) {
Doris Liu6827ce22013-03-12 19:24:28 -0700995 mUI.clickShutter();
Michael Kolb8872c232013-01-29 10:33:22 -0800996 return true;
997 }
998 break;
999 case KeyEvent.KEYCODE_DPAD_CENTER:
1000 if (event.getRepeatCount() == 0) {
Doris Liu6827ce22013-03-12 19:24:28 -07001001 mUI.clickShutter();
Michael Kolb8872c232013-01-29 10:33:22 -08001002 return true;
1003 }
1004 break;
1005 case KeyEvent.KEYCODE_MENU:
1006 if (mMediaRecorderRecording) return true;
1007 break;
1008 }
1009 return false;
1010 }
1011
1012 @Override
1013 public boolean onKeyUp(int keyCode, KeyEvent event) {
1014 switch (keyCode) {
1015 case KeyEvent.KEYCODE_CAMERA:
Doris Liu6827ce22013-03-12 19:24:28 -07001016 mUI.pressShutter(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001017 return true;
1018 }
1019 return false;
1020 }
1021
Doris Liu6827ce22013-03-12 19:24:28 -07001022 @Override
1023 public boolean isVideoCaptureIntent() {
Michael Kolb8872c232013-01-29 10:33:22 -08001024 String action = mActivity.getIntent().getAction();
1025 return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
1026 }
1027
1028 private void doReturnToCaller(boolean valid) {
1029 Intent resultIntent = new Intent();
1030 int resultCode;
1031 if (valid) {
1032 resultCode = Activity.RESULT_OK;
1033 resultIntent.setData(mCurrentVideoUri);
1034 } else {
1035 resultCode = Activity.RESULT_CANCELED;
1036 }
1037 mActivity.setResultEx(resultCode, resultIntent);
1038 mActivity.finish();
1039 }
1040
1041 private void cleanupEmptyFile() {
1042 if (mVideoFilename != null) {
1043 File f = new File(mVideoFilename);
1044 if (f.length() == 0 && f.delete()) {
1045 Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
1046 mVideoFilename = null;
1047 }
1048 }
1049 }
1050
1051 private void setupMediaRecorderPreviewDisplay() {
1052 // Nothing to do here if using SurfaceTexture.
Doris Liu6432cd62013-06-13 17:20:31 -07001053 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
Michael Kolb8872c232013-01-29 10:33:22 -08001054 // We stop the preview here before unlocking the device because we
1055 // need to change the SurfaceTexture to SurfaceView for preview.
1056 stopPreview();
Angus Kong9ef99252013-07-18 18:04:19 -07001057 mCameraDevice.setPreviewDisplay(mUI.getSurfaceHolder());
Michael Kolb8872c232013-01-29 10:33:22 -08001058 // The orientation for SurfaceTexture is different from that for
1059 // SurfaceView. For SurfaceTexture we don't need to consider the
1060 // display rotation. Just consider the sensor's orientation and we
1061 // will set the orientation correctly when showing the texture.
1062 // Gallery will handle the orientation for the preview. For
1063 // SurfaceView we will have to take everything into account so the
1064 // display rotation is considered.
Doris Liu6432cd62013-06-13 17:20:31 -07001065 mCameraDevice.setDisplayOrientation(
Angus Kongb50b5cb2013-08-09 14:55:20 -07001066 CameraUtil.getDisplayOrientation(mDisplayRotation, mCameraId));
Angus Kong9ef99252013-07-18 18:04:19 -07001067 mCameraDevice.startPreview();
Michael Kolb8872c232013-01-29 10:33:22 -08001068 mPreviewing = true;
Doris Liu6827ce22013-03-12 19:24:28 -07001069 mMediaRecorder.setPreviewDisplay(mUI.getSurfaceHolder().getSurface());
Michael Kolb8872c232013-01-29 10:33:22 -08001070 }
1071 }
1072
1073 // Prepares media recorder.
1074 private void initializeRecorder() {
1075 Log.v(TAG, "initializeRecorder");
1076 // If the mCameraDevice is null, then this activity is going to finish
Doris Liu6432cd62013-06-13 17:20:31 -07001077 if (mCameraDevice == null) return;
Michael Kolb8872c232013-01-29 10:33:22 -08001078
Doris Liu6432cd62013-06-13 17:20:31 -07001079 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
Michael Kolb8872c232013-01-29 10:33:22 -08001080 // Set the SurfaceView to visible so the surface gets created.
1081 // surfaceCreated() is called immediately when the visibility is
1082 // changed to visible. Thus, mSurfaceViewReady should become true
1083 // right after calling setVisibility().
Doris Liu6827ce22013-03-12 19:24:28 -07001084 mUI.showSurfaceView();
Michael Kolb8872c232013-01-29 10:33:22 -08001085 }
1086
1087 Intent intent = mActivity.getIntent();
1088 Bundle myExtras = intent.getExtras();
1089
1090 long requestedSizeLimit = 0;
1091 closeVideoFileDescriptor();
1092 if (mIsVideoCaptureIntent && myExtras != null) {
1093 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1094 if (saveUri != null) {
1095 try {
1096 mVideoFileDescriptor =
1097 mContentResolver.openFileDescriptor(saveUri, "rw");
1098 mCurrentVideoUri = saveUri;
1099 } catch (java.io.FileNotFoundException ex) {
1100 // invalid uri
1101 Log.e(TAG, ex.toString());
1102 }
1103 }
1104 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1105 }
1106 mMediaRecorder = new MediaRecorder();
1107
1108 setupMediaRecorderPreviewDisplay();
1109 // Unlock the camera object before passing it to media recorder.
Doris Liu6432cd62013-06-13 17:20:31 -07001110 mCameraDevice.unlock();
Doris Liu6432cd62013-06-13 17:20:31 -07001111 mMediaRecorder.setCamera(mCameraDevice.getCamera());
Michael Kolb8872c232013-01-29 10:33:22 -08001112 if (!mCaptureTimeLapse) {
1113 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
1114 }
1115 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
1116 mMediaRecorder.setProfile(mProfile);
1117 mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
1118 if (mCaptureTimeLapse) {
1119 double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs;
1120 setCaptureRate(mMediaRecorder, fps);
1121 }
1122
1123 setRecordLocation();
1124
1125 // Set output file.
1126 // Try Uri in the intent first. If it doesn't exist, use our own
1127 // instead.
1128 if (mVideoFileDescriptor != null) {
1129 mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1130 } else {
1131 generateVideoFilename(mProfile.fileFormat);
1132 mMediaRecorder.setOutputFile(mVideoFilename);
1133 }
1134
1135 // Set maximum file size.
1136 long maxFileSize = mActivity.getStorageSpace() - Storage.LOW_STORAGE_THRESHOLD;
1137 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1138 maxFileSize = requestedSizeLimit;
1139 }
1140
1141 try {
1142 mMediaRecorder.setMaxFileSize(maxFileSize);
1143 } catch (RuntimeException exception) {
1144 // We are going to ignore failure of setMaxFileSize here, as
1145 // a) The composer selected may simply not support it, or
1146 // b) The underlying media framework may not handle 64-bit range
1147 // on the size restriction.
1148 }
1149
1150 // See android.hardware.Camera.Parameters.setRotation for
1151 // documentation.
1152 // Note that mOrientation here is the device orientation, which is the opposite of
1153 // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
1154 // which is the orientation the graphics need to rotate in order to render correctly.
1155 int rotation = 0;
1156 if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1157 CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1158 if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
1159 rotation = (info.orientation - mOrientation + 360) % 360;
1160 } else { // back-facing camera
1161 rotation = (info.orientation + mOrientation) % 360;
1162 }
1163 }
1164 mMediaRecorder.setOrientationHint(rotation);
1165
1166 try {
1167 mMediaRecorder.prepare();
1168 } catch (IOException e) {
1169 Log.e(TAG, "prepare failed for " + mVideoFilename, e);
1170 releaseMediaRecorder();
1171 throw new RuntimeException(e);
1172 }
1173
1174 mMediaRecorder.setOnErrorListener(this);
1175 mMediaRecorder.setOnInfoListener(this);
1176 }
1177
1178 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
1179 private static void setCaptureRate(MediaRecorder recorder, double fps) {
1180 recorder.setCaptureRate(fps);
1181 }
1182
1183 @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
1184 private void setRecordLocation() {
1185 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
1186 Location loc = mLocationManager.getCurrentLocation();
1187 if (loc != null) {
1188 mMediaRecorder.setLocation((float) loc.getLatitude(),
1189 (float) loc.getLongitude());
1190 }
1191 }
1192 }
1193
1194 private void initializeEffectsPreview() {
1195 Log.v(TAG, "initializeEffectsPreview");
1196 // If the mCameraDevice is null, then this activity is going to finish
Doris Liu6432cd62013-06-13 17:20:31 -07001197 if (mCameraDevice == null) return;
Michael Kolb8872c232013-01-29 10:33:22 -08001198
Michael Kolb8872c232013-01-29 10:33:22 -08001199 CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1200
1201 mEffectsDisplayResult = false;
1202 mEffectsRecorder = new EffectsRecorder(mActivity);
1203
1204 // TODO: Confirm none of the following need to go to initializeEffectsRecording()
1205 // and none of these change even when the preview is not refreshed.
1206 mEffectsRecorder.setCameraDisplayOrientation(mCameraDisplayOrientation);
Doris Liu6432cd62013-06-13 17:20:31 -07001207 mEffectsRecorder.setCamera(mCameraDevice);
Michael Kolb8872c232013-01-29 10:33:22 -08001208 mEffectsRecorder.setCameraFacing(info.facing);
1209 mEffectsRecorder.setProfile(mProfile);
1210 mEffectsRecorder.setEffectsListener(this);
1211 mEffectsRecorder.setOnInfoListener(this);
1212 mEffectsRecorder.setOnErrorListener(this);
1213
1214 // The input of effects recorder is affected by
1215 // android.hardware.Camera.setDisplayOrientation. Its value only
1216 // compensates the camera orientation (no Display.getRotation). So the
1217 // orientation hint here should only consider sensor orientation.
1218 int orientation = 0;
1219 if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1220 orientation = mOrientation;
1221 }
1222 mEffectsRecorder.setOrientationHint(orientation);
1223
Doris Liu6432cd62013-06-13 17:20:31 -07001224 mEffectsRecorder.setPreviewSurfaceTexture(mUI.getSurfaceTexture(),
1225 mUI.getPreviewWidth(), mUI.getPreviewHeight());
Michael Kolb8872c232013-01-29 10:33:22 -08001226
1227 if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER &&
1228 ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) {
1229 mEffectsRecorder.setEffect(mEffectType, mEffectUriFromGallery);
1230 } else {
1231 mEffectsRecorder.setEffect(mEffectType, mEffectParameter);
1232 }
1233 }
1234
1235 private void initializeEffectsRecording() {
1236 Log.v(TAG, "initializeEffectsRecording");
1237
1238 Intent intent = mActivity.getIntent();
1239 Bundle myExtras = intent.getExtras();
1240
1241 long requestedSizeLimit = 0;
1242 closeVideoFileDescriptor();
1243 if (mIsVideoCaptureIntent && myExtras != null) {
1244 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1245 if (saveUri != null) {
1246 try {
1247 mVideoFileDescriptor =
1248 mContentResolver.openFileDescriptor(saveUri, "rw");
1249 mCurrentVideoUri = saveUri;
1250 } catch (java.io.FileNotFoundException ex) {
1251 // invalid uri
1252 Log.e(TAG, ex.toString());
1253 }
1254 }
1255 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1256 }
1257
1258 mEffectsRecorder.setProfile(mProfile);
1259 // important to set the capture rate to zero if not timelapsed, since the
1260 // effectsrecorder object does not get created again for each recording
1261 // session
1262 if (mCaptureTimeLapse) {
1263 mEffectsRecorder.setCaptureRate((1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs));
1264 } else {
1265 mEffectsRecorder.setCaptureRate(0);
1266 }
1267
1268 // Set output file
1269 if (mVideoFileDescriptor != null) {
1270 mEffectsRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1271 } else {
1272 generateVideoFilename(mProfile.fileFormat);
1273 mEffectsRecorder.setOutputFile(mVideoFilename);
1274 }
1275
1276 // Set maximum file size.
1277 long maxFileSize = mActivity.getStorageSpace() - Storage.LOW_STORAGE_THRESHOLD;
1278 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1279 maxFileSize = requestedSizeLimit;
1280 }
1281 mEffectsRecorder.setMaxFileSize(maxFileSize);
1282 mEffectsRecorder.setMaxDuration(mMaxVideoDurationInMs);
1283 }
1284
1285
1286 private void releaseMediaRecorder() {
1287 Log.v(TAG, "Releasing media recorder.");
1288 if (mMediaRecorder != null) {
1289 cleanupEmptyFile();
1290 mMediaRecorder.reset();
1291 mMediaRecorder.release();
1292 mMediaRecorder = null;
1293 }
1294 mVideoFilename = null;
1295 }
1296
1297 private void releaseEffectsRecorder() {
1298 Log.v(TAG, "Releasing effects recorder.");
1299 if (mEffectsRecorder != null) {
1300 cleanupEmptyFile();
1301 mEffectsRecorder.release();
1302 mEffectsRecorder = null;
1303 }
1304 mEffectType = EffectsRecorder.EFFECT_NONE;
1305 mVideoFilename = null;
1306 }
1307
1308 private void generateVideoFilename(int outputFileFormat) {
1309 long dateTaken = System.currentTimeMillis();
1310 String title = createName(dateTaken);
1311 // Used when emailing.
1312 String filename = title + convertOutputFormatToFileExt(outputFileFormat);
1313 String mime = convertOutputFormatToMimeType(outputFileFormat);
1314 String path = Storage.DIRECTORY + '/' + filename;
1315 String tmpPath = path + ".tmp";
Ruben Brunk16007962013-04-19 15:27:57 -07001316 mCurrentVideoValues = new ContentValues(9);
Michael Kolb8872c232013-01-29 10:33:22 -08001317 mCurrentVideoValues.put(Video.Media.TITLE, title);
1318 mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
1319 mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
Ruben Brunk16007962013-04-19 15:27:57 -07001320 mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000);
Michael Kolb8872c232013-01-29 10:33:22 -08001321 mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
1322 mCurrentVideoValues.put(Video.Media.DATA, path);
1323 mCurrentVideoValues.put(Video.Media.RESOLUTION,
1324 Integer.toString(mProfile.videoFrameWidth) + "x" +
1325 Integer.toString(mProfile.videoFrameHeight));
1326 Location loc = mLocationManager.getCurrentLocation();
1327 if (loc != null) {
1328 mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
1329 mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
1330 }
Michael Kolb8872c232013-01-29 10:33:22 -08001331 mVideoFilename = tmpPath;
1332 Log.v(TAG, "New video filename: " + mVideoFilename);
1333 }
1334
Angus Kong83a99ae2013-04-17 15:37:07 -07001335 private void saveVideo() {
Michael Kolb8872c232013-01-29 10:33:22 -08001336 if (mVideoFileDescriptor == null) {
Michael Kolb8872c232013-01-29 10:33:22 -08001337 long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
1338 if (duration > 0) {
1339 if (mCaptureTimeLapse) {
1340 duration = getTimeLapseVideoLength(duration);
1341 }
Michael Kolb8872c232013-01-29 10:33:22 -08001342 } else {
1343 Log.w(TAG, "Video duration <= 0 : " + duration);
1344 }
Angus Kong83a99ae2013-04-17 15:37:07 -07001345 mActivity.getMediaSaveService().addVideo(mCurrentVideoFilename,
1346 duration, mCurrentVideoValues,
1347 mOnVideoSavedListener, mContentResolver);
Michael Kolb8872c232013-01-29 10:33:22 -08001348 }
1349 mCurrentVideoValues = null;
Michael Kolb8872c232013-01-29 10:33:22 -08001350 }
1351
1352 private void deleteVideoFile(String fileName) {
1353 Log.v(TAG, "Deleting video " + fileName);
1354 File f = new File(fileName);
1355 if (!f.delete()) {
1356 Log.v(TAG, "Could not delete " + fileName);
1357 }
1358 }
1359
1360 private PreferenceGroup filterPreferenceScreenByIntent(
1361 PreferenceGroup screen) {
1362 Intent intent = mActivity.getIntent();
1363 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
1364 CameraSettings.removePreferenceFromScreen(screen,
1365 CameraSettings.KEY_VIDEO_QUALITY);
1366 }
1367
1368 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
1369 CameraSettings.removePreferenceFromScreen(screen,
1370 CameraSettings.KEY_VIDEO_QUALITY);
1371 }
1372 return screen;
1373 }
1374
1375 // from MediaRecorder.OnErrorListener
1376 @Override
1377 public void onError(MediaRecorder mr, int what, int extra) {
1378 Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
1379 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1380 // We may have run out of space on the sdcard.
1381 stopVideoRecording();
1382 mActivity.updateStorageSpaceAndHint();
1383 }
1384 }
1385
1386 // from MediaRecorder.OnInfoListener
1387 @Override
1388 public void onInfo(MediaRecorder mr, int what, int extra) {
1389 if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
1390 if (mMediaRecorderRecording) onStopVideoRecording();
1391 } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
1392 if (mMediaRecorderRecording) onStopVideoRecording();
1393
1394 // Show the toast.
1395 Toast.makeText(mActivity, R.string.video_reach_size_limit,
1396 Toast.LENGTH_LONG).show();
1397 }
1398 }
1399
1400 /*
1401 * Make sure we're not recording music playing in the background, ask the
1402 * MediaPlaybackService to pause playback.
1403 */
1404 private void pauseAudioPlayback() {
1405 // Shamelessly copied from MediaPlaybackService.java, which
1406 // should be public, but isn't.
1407 Intent i = new Intent("com.android.music.musicservicecommand");
1408 i.putExtra("command", "pause");
1409
1410 mActivity.sendBroadcast(i);
1411 }
1412
1413 // For testing.
1414 public boolean isRecording() {
1415 return mMediaRecorderRecording;
1416 }
1417
1418 private void startVideoRecording() {
1419 Log.v(TAG, "startVideoRecording");
Sascha Haeberling37f36112013-08-06 14:31:52 -07001420 mUI.cancelAnimations();
Doris Liu6432cd62013-06-13 17:20:31 -07001421 mUI.setSwipingEnabled(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001422
1423 mActivity.updateStorageSpaceAndHint();
1424 if (mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) {
1425 Log.v(TAG, "Storage issue, ignore the start request");
1426 return;
1427 }
1428
Angus Kong9ef99252013-07-18 18:04:19 -07001429 //??
1430 //if (!mCameraDevice.waitDone()) return;
Michael Kolb8872c232013-01-29 10:33:22 -08001431 mCurrentVideoUri = null;
1432 if (effectsActive()) {
1433 initializeEffectsRecording();
1434 if (mEffectsRecorder == null) {
1435 Log.e(TAG, "Fail to initialize effect recorder");
1436 return;
1437 }
1438 } else {
1439 initializeRecorder();
1440 if (mMediaRecorder == null) {
1441 Log.e(TAG, "Fail to initialize media recorder");
1442 return;
1443 }
1444 }
1445
1446 pauseAudioPlayback();
1447
1448 if (effectsActive()) {
1449 try {
1450 mEffectsRecorder.startRecording();
1451 } catch (RuntimeException e) {
1452 Log.e(TAG, "Could not start effects recorder. ", e);
1453 releaseEffectsRecorder();
1454 return;
1455 }
1456 } else {
1457 try {
1458 mMediaRecorder.start(); // Recording is now started
1459 } catch (RuntimeException e) {
1460 Log.e(TAG, "Could not start media recorder. ", e);
1461 releaseMediaRecorder();
1462 // If start fails, frameworks will not lock the camera for us.
Doris Liu6432cd62013-06-13 17:20:31 -07001463 mCameraDevice.lock();
Michael Kolb8872c232013-01-29 10:33:22 -08001464 return;
1465 }
1466 }
1467
1468 // Make sure the video recording has started before announcing
1469 // this in accessibility.
Doris Liu6432cd62013-06-13 17:20:31 -07001470 AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
Michael Kolb8872c232013-01-29 10:33:22 -08001471 mActivity.getString(R.string.video_recording_started));
1472
Angus Kong104e0012013-04-03 16:56:18 -07001473 // The parameters might have been altered by MediaRecorder already.
1474 // We need to force mCameraDevice to refresh before getting it.
Doris Liu6432cd62013-06-13 17:20:31 -07001475 mCameraDevice.refreshParameters();
Michael Kolb8872c232013-01-29 10:33:22 -08001476 // The parameters may have been changed by MediaRecorder upon starting
1477 // recording. We need to alter the parameters if we support camcorder
1478 // zoom. To reduce latency when setting the parameters during zoom, we
1479 // update mParameters here once.
1480 if (ApiHelper.HAS_ZOOM_WHEN_RECORDING) {
Doris Liu6432cd62013-06-13 17:20:31 -07001481 mParameters = mCameraDevice.getParameters();
Michael Kolb8872c232013-01-29 10:33:22 -08001482 }
1483
Doris Liu6827ce22013-03-12 19:24:28 -07001484 mUI.enableCameraControls(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001485
1486 mMediaRecorderRecording = true;
Doris Liu6432cd62013-06-13 17:20:31 -07001487 mOrientationManager.lockOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001488 mRecordingStartTime = SystemClock.uptimeMillis();
Doris Liu6827ce22013-03-12 19:24:28 -07001489 mUI.showRecordingUI(true, mParameters.isZoomSupported());
Michael Kolb8872c232013-01-29 10:33:22 -08001490
1491 updateRecordingTime();
1492 keepScreenOn();
Bobby Georgescu301b6462013-04-01 15:33:17 -07001493 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1494 UsageStatistics.ACTION_CAPTURE_START, "Video");
Michael Kolb8872c232013-01-29 10:33:22 -08001495 }
1496
Sascha Haeberling37f36112013-08-06 14:31:52 -07001497 private Bitmap getVideoThumbnail() {
Michael Kolb8872c232013-01-29 10:33:22 -08001498 Bitmap bitmap = null;
1499 if (mVideoFileDescriptor != null) {
1500 bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
Doris Liu3c2fca32013-02-13 18:28:03 -08001501 mDesiredPreviewWidth);
Doris Liu2a7f44c2013-08-12 15:18:53 -07001502 } else if (mCurrentVideoUri != null) {
1503 try {
1504 mVideoFileDescriptor = mContentResolver.openFileDescriptor(mCurrentVideoUri, "r");
1505 bitmap = Thumbnail.createVideoThumbnailBitmap(
1506 mVideoFileDescriptor.getFileDescriptor(), mDesiredPreviewWidth);
1507 } catch (java.io.FileNotFoundException ex) {
1508 // invalid uri
1509 Log.e(TAG, ex.toString());
1510 }
Michael Kolb8872c232013-01-29 10:33:22 -08001511 }
1512 if (bitmap != null) {
1513 // MetadataRetriever already rotates the thumbnail. We should rotate
1514 // it to match the UI orientation (and mirror if it is front-facing camera).
1515 CameraInfo[] info = CameraHolder.instance().getCameraInfo();
1516 boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT);
Angus Kongb50b5cb2013-08-09 14:55:20 -07001517 bitmap = CameraUtil.rotateAndMirror(bitmap, 0, mirror);
Sascha Haeberling37f36112013-08-06 14:31:52 -07001518 }
1519 return bitmap;
1520 }
1521
1522 private void showCaptureResult() {
1523 mIsInReviewMode = true;
1524 Bitmap bitmap = getVideoThumbnail();
1525 if (bitmap != null) {
Doris Liu6827ce22013-03-12 19:24:28 -07001526 mUI.showReviewImage(bitmap);
Michael Kolb8872c232013-01-29 10:33:22 -08001527 }
Doris Liu6827ce22013-03-12 19:24:28 -07001528 mUI.showReviewControls();
1529 mUI.enableCameraControls(false);
1530 mUI.showTimeLapseUI(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001531 }
1532
Michael Kolb8872c232013-01-29 10:33:22 -08001533 private boolean stopVideoRecording() {
1534 Log.v(TAG, "stopVideoRecording");
Doris Liu6432cd62013-06-13 17:20:31 -07001535 mUI.setSwipingEnabled(true);
Doris Liu2a7f44c2013-08-12 15:18:53 -07001536 if (!isVideoCaptureIntent()) {
1537 mUI.showSwitcher();
1538 }
Michael Kolb8872c232013-01-29 10:33:22 -08001539
1540 boolean fail = false;
1541 if (mMediaRecorderRecording) {
1542 boolean shouldAddToMediaStoreNow = false;
1543
1544 try {
1545 if (effectsActive()) {
1546 // This is asynchronous, so we can't add to media store now because thumbnail
Angus Kong83a99ae2013-04-17 15:37:07 -07001547 // may not be ready. In such case saveVideo() is called later
Michael Kolb8872c232013-01-29 10:33:22 -08001548 // through a callback from the MediaEncoderFilter to EffectsRecorder,
1549 // and then to the VideoModule.
1550 mEffectsRecorder.stopRecording();
1551 } else {
1552 mMediaRecorder.setOnErrorListener(null);
1553 mMediaRecorder.setOnInfoListener(null);
1554 mMediaRecorder.stop();
1555 shouldAddToMediaStoreNow = true;
1556 }
1557 mCurrentVideoFilename = mVideoFilename;
1558 Log.v(TAG, "stopVideoRecording: Setting current video filename: "
1559 + mCurrentVideoFilename);
Doris Liu6432cd62013-06-13 17:20:31 -07001560 AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
Michael Kolb8872c232013-01-29 10:33:22 -08001561 mActivity.getString(R.string.video_recording_stopped));
1562 } catch (RuntimeException e) {
1563 Log.e(TAG, "stop fail", e);
1564 if (mVideoFilename != null) deleteVideoFile(mVideoFilename);
1565 fail = true;
1566 }
1567 mMediaRecorderRecording = false;
Doris Liu6432cd62013-06-13 17:20:31 -07001568 mOrientationManager.unlockOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001569
1570 // If the activity is paused, this means activity is interrupted
1571 // during recording. Release the camera as soon as possible because
1572 // face unlock or other applications may need to use the camera.
1573 // However, if the effects are active, then we can only release the
1574 // camera and cannot release the effects recorder since that will
1575 // stop the graph. It is possible to separate out the Camera release
1576 // part and the effects release part. However, the effects recorder
1577 // does hold on to the camera, hence, it needs to be "disconnected"
1578 // from the camera in the closeCamera call.
1579 if (mPaused) {
1580 // Closing only the camera part if effects active. Effects will
1581 // be closed in the callback from effects.
1582 boolean closeEffects = !effectsActive();
1583 closeCamera(closeEffects);
1584 }
1585
Doris Liu6827ce22013-03-12 19:24:28 -07001586 mUI.showRecordingUI(false, mParameters.isZoomSupported());
Michael Kolb8872c232013-01-29 10:33:22 -08001587 if (!mIsVideoCaptureIntent) {
Doris Liu6827ce22013-03-12 19:24:28 -07001588 mUI.enableCameraControls(true);
Michael Kolb8872c232013-01-29 10:33:22 -08001589 }
1590 // The orientation was fixed during video recording. Now make it
1591 // reflect the device orientation as video recording is stopped.
Doris Liu6827ce22013-03-12 19:24:28 -07001592 mUI.setOrientationIndicator(0, true);
Michael Kolb8872c232013-01-29 10:33:22 -08001593 keepScreenOnAwhile();
Doris Liu2a7f44c2013-08-12 15:18:53 -07001594 if (shouldAddToMediaStoreNow && !fail) {
1595 if (mVideoFileDescriptor == null) {
1596 saveVideo();
1597 } else if (mIsVideoCaptureIntent) {
1598 // if no file save is needed, we can show the post capture UI now
1599 showCaptureResult();
1600 }
Michael Kolb8872c232013-01-29 10:33:22 -08001601 }
1602 }
1603 // always release media recorder if no effects running
1604 if (!effectsActive()) {
1605 releaseMediaRecorder();
1606 if (!mPaused) {
Doris Liu6432cd62013-06-13 17:20:31 -07001607 mCameraDevice.lock();
Doris Liu6432cd62013-06-13 17:20:31 -07001608 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
Michael Kolb8872c232013-01-29 10:33:22 -08001609 stopPreview();
Doris Liu6432cd62013-06-13 17:20:31 -07001610 mUI.hideSurfaceView();
Michael Kolb8872c232013-01-29 10:33:22 -08001611 // Switch back to use SurfaceTexture for preview.
Michael Kolb8872c232013-01-29 10:33:22 -08001612 startPreview();
1613 }
1614 }
1615 }
1616 // Update the parameters here because the parameters might have been altered
1617 // by MediaRecorder.
Doris Liu6432cd62013-06-13 17:20:31 -07001618 if (!mPaused) mParameters = mCameraDevice.getParameters();
Bobby Georgescu301b6462013-04-01 15:33:17 -07001619 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1620 fail ? UsageStatistics.ACTION_CAPTURE_FAIL :
1621 UsageStatistics.ACTION_CAPTURE_DONE, "Video",
1622 SystemClock.uptimeMillis() - mRecordingStartTime);
Michael Kolb8872c232013-01-29 10:33:22 -08001623 return fail;
1624 }
1625
1626 private void resetScreenOn() {
1627 mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1628 mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1629 }
1630
1631 private void keepScreenOnAwhile() {
1632 mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1633 mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1634 mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1635 }
1636
1637 private void keepScreenOn() {
1638 mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1639 mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1640 }
1641
1642 private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
1643 long seconds = milliSeconds / 1000; // round down to compute seconds
1644 long minutes = seconds / 60;
1645 long hours = minutes / 60;
1646 long remainderMinutes = minutes - (hours * 60);
1647 long remainderSeconds = seconds - (minutes * 60);
1648
1649 StringBuilder timeStringBuilder = new StringBuilder();
1650
1651 // Hours
1652 if (hours > 0) {
1653 if (hours < 10) {
1654 timeStringBuilder.append('0');
1655 }
1656 timeStringBuilder.append(hours);
1657
1658 timeStringBuilder.append(':');
1659 }
1660
1661 // Minutes
1662 if (remainderMinutes < 10) {
1663 timeStringBuilder.append('0');
1664 }
1665 timeStringBuilder.append(remainderMinutes);
1666 timeStringBuilder.append(':');
1667
1668 // Seconds
1669 if (remainderSeconds < 10) {
1670 timeStringBuilder.append('0');
1671 }
1672 timeStringBuilder.append(remainderSeconds);
1673
1674 // Centi seconds
1675 if (displayCentiSeconds) {
1676 timeStringBuilder.append('.');
1677 long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
1678 if (remainderCentiSeconds < 10) {
1679 timeStringBuilder.append('0');
1680 }
1681 timeStringBuilder.append(remainderCentiSeconds);
1682 }
1683
1684 return timeStringBuilder.toString();
1685 }
1686
1687 private long getTimeLapseVideoLength(long deltaMs) {
1688 // For better approximation calculate fractional number of frames captured.
1689 // This will update the video time at a higher resolution.
1690 double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
1691 return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
1692 }
1693
1694 private void updateRecordingTime() {
1695 if (!mMediaRecorderRecording) {
1696 return;
1697 }
1698 long now = SystemClock.uptimeMillis();
1699 long delta = now - mRecordingStartTime;
1700
1701 // Starting a minute before reaching the max duration
1702 // limit, we'll countdown the remaining time instead.
1703 boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1704 && delta >= mMaxVideoDurationInMs - 60000);
1705
1706 long deltaAdjusted = delta;
1707 if (countdownRemainingTime) {
1708 deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
1709 }
1710 String text;
1711
1712 long targetNextUpdateDelay;
1713 if (!mCaptureTimeLapse) {
1714 text = millisecondToTimeString(deltaAdjusted, false);
1715 targetNextUpdateDelay = 1000;
1716 } else {
1717 // The length of time lapse video is different from the length
1718 // of the actual wall clock time elapsed. Display the video length
1719 // only in format hh:mm:ss.dd, where dd are the centi seconds.
1720 text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
1721 targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
1722 }
1723
Doris Liu6827ce22013-03-12 19:24:28 -07001724 mUI.setRecordingTime(text);
Michael Kolb8872c232013-01-29 10:33:22 -08001725
1726 if (mRecordingTimeCountsDown != countdownRemainingTime) {
1727 // Avoid setting the color on every update, do it only
1728 // when it needs changing.
1729 mRecordingTimeCountsDown = countdownRemainingTime;
1730
1731 int color = mActivity.getResources().getColor(countdownRemainingTime
1732 ? R.color.recording_time_remaining_text
1733 : R.color.recording_time_elapsed_text);
1734
Doris Liu6827ce22013-03-12 19:24:28 -07001735 mUI.setRecordingTimeTextColor(color);
Michael Kolb8872c232013-01-29 10:33:22 -08001736 }
1737
1738 long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
1739 mHandler.sendEmptyMessageDelayed(
1740 UPDATE_RECORD_TIME, actualNextUpdateDelay);
1741 }
1742
1743 private static boolean isSupported(String value, List<String> supported) {
1744 return supported == null ? false : supported.indexOf(value) >= 0;
1745 }
1746
1747 @SuppressWarnings("deprecation")
1748 private void setCameraParameters() {
1749 mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
Angus Kongb50b5cb2013-08-09 14:55:20 -07001750 int[] fpsRange = CameraUtil.getMaxPreviewFpsRange(mParameters);
Doris Liu6432cd62013-06-13 17:20:31 -07001751 if (fpsRange.length > 0) {
1752 mParameters.setPreviewFpsRange(
1753 fpsRange[Parameters.PREVIEW_FPS_MIN_INDEX],
1754 fpsRange[Parameters.PREVIEW_FPS_MAX_INDEX]);
1755 } else {
1756 mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
1757 }
Michael Kolb8872c232013-01-29 10:33:22 -08001758
1759 // Set flash mode.
1760 String flashMode;
Doris Liu6432cd62013-06-13 17:20:31 -07001761 if (mUI.isVisible()) {
Michael Kolb8872c232013-01-29 10:33:22 -08001762 flashMode = mPreferences.getString(
1763 CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
1764 mActivity.getString(R.string.pref_camera_video_flashmode_default));
1765 } else {
1766 flashMode = Parameters.FLASH_MODE_OFF;
1767 }
1768 List<String> supportedFlash = mParameters.getSupportedFlashModes();
1769 if (isSupported(flashMode, supportedFlash)) {
1770 mParameters.setFlashMode(flashMode);
1771 } else {
1772 flashMode = mParameters.getFlashMode();
1773 if (flashMode == null) {
1774 flashMode = mActivity.getString(
1775 R.string.pref_camera_flashmode_no_flash);
1776 }
1777 }
1778
1779 // Set white balance parameter.
1780 String whiteBalance = mPreferences.getString(
1781 CameraSettings.KEY_WHITE_BALANCE,
1782 mActivity.getString(R.string.pref_camera_whitebalance_default));
1783 if (isSupported(whiteBalance,
1784 mParameters.getSupportedWhiteBalance())) {
1785 mParameters.setWhiteBalance(whiteBalance);
1786 } else {
1787 whiteBalance = mParameters.getWhiteBalance();
1788 if (whiteBalance == null) {
1789 whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1790 }
1791 }
1792
1793 // Set zoom.
1794 if (mParameters.isZoomSupported()) {
1795 mParameters.setZoom(mZoomValue);
1796 }
1797
1798 // Set continuous autofocus.
1799 List<String> supportedFocus = mParameters.getSupportedFocusModes();
1800 if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
1801 mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
1802 }
1803
Angus Kongb50b5cb2013-08-09 14:55:20 -07001804 mParameters.set(CameraUtil.RECORDING_HINT, CameraUtil.TRUE);
Michael Kolb8872c232013-01-29 10:33:22 -08001805
1806 // Enable video stabilization. Convenience methods not available in API
1807 // level <= 14
1808 String vstabSupported = mParameters.get("video-stabilization-supported");
1809 if ("true".equals(vstabSupported)) {
1810 mParameters.set("video-stabilization", "true");
1811 }
1812
1813 // Set picture size.
1814 // The logic here is different from the logic in still-mode camera.
1815 // There we determine the preview size based on the picture size, but
1816 // here we determine the picture size based on the preview size.
1817 List<Size> supported = mParameters.getSupportedPictureSizes();
Angus Kongb50b5cb2013-08-09 14:55:20 -07001818 Size optimalSize = CameraUtil.getOptimalVideoSnapshotPictureSize(supported,
Michael Kolb8872c232013-01-29 10:33:22 -08001819 (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
1820 Size original = mParameters.getPictureSize();
1821 if (!original.equals(optimalSize)) {
1822 mParameters.setPictureSize(optimalSize.width, optimalSize.height);
1823 }
1824 Log.v(TAG, "Video snapshot size is " + optimalSize.width + "x" +
1825 optimalSize.height);
1826
1827 // Set JPEG quality.
1828 int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1829 CameraProfile.QUALITY_HIGH);
1830 mParameters.setJpegQuality(jpegQuality);
1831
Doris Liu6432cd62013-06-13 17:20:31 -07001832 mCameraDevice.setParameters(mParameters);
Michael Kolb8872c232013-01-29 10:33:22 -08001833 // Keep preview size up to date.
Doris Liu6432cd62013-06-13 17:20:31 -07001834 mParameters = mCameraDevice.getParameters();
Michael Kolb8872c232013-01-29 10:33:22 -08001835 }
1836
1837 @Override
1838 public void onActivityResult(int requestCode, int resultCode, Intent data) {
1839 switch (requestCode) {
1840 case REQUEST_EFFECT_BACKDROPPER:
1841 if (resultCode == Activity.RESULT_OK) {
1842 // onActivityResult() runs before onResume(), so this parameter will be
1843 // seen by startPreview from onResume()
1844 mEffectUriFromGallery = data.getData().toString();
1845 Log.v(TAG, "Received URI from gallery: " + mEffectUriFromGallery);
1846 mResetEffect = false;
1847 } else {
1848 mEffectUriFromGallery = null;
1849 Log.w(TAG, "No URI from gallery");
1850 mResetEffect = true;
1851 }
1852 break;
1853 }
1854 }
1855
1856 @Override
1857 public void onEffectsUpdate(int effectId, int effectMsg) {
1858 Log.v(TAG, "onEffectsUpdate. Effect Message = " + effectMsg);
1859 if (effectMsg == EffectsRecorder.EFFECT_MSG_EFFECTS_STOPPED) {
1860 // Effects have shut down. Hide learning message if any,
1861 // and restart regular preview.
Michael Kolb8872c232013-01-29 10:33:22 -08001862 checkQualityAndStartPreview();
1863 } else if (effectMsg == EffectsRecorder.EFFECT_MSG_RECORDING_DONE) {
1864 // This follows the codepath from onStopVideoRecording.
Angus Kong83a99ae2013-04-17 15:37:07 -07001865 if (mEffectsDisplayResult) {
1866 saveVideo();
Michael Kolb8872c232013-01-29 10:33:22 -08001867 if (mIsVideoCaptureIntent) {
1868 if (mQuickCapture) {
1869 doReturnToCaller(true);
1870 } else {
Doris Liu3c2fca32013-02-13 18:28:03 -08001871 showCaptureResult();
Michael Kolb8872c232013-01-29 10:33:22 -08001872 }
1873 }
1874 }
1875 mEffectsDisplayResult = false;
1876 // In onPause, these were not called if the effects were active. We
1877 // had to wait till the effects recording is complete to do this.
1878 if (mPaused) {
1879 closeVideoFileDescriptor();
Michael Kolb8872c232013-01-29 10:33:22 -08001880 }
1881 } else if (effectMsg == EffectsRecorder.EFFECT_MSG_PREVIEW_RUNNING) {
1882 // Enable the shutter button once the preview is complete.
Doris Liu6827ce22013-03-12 19:24:28 -07001883 mUI.enableShutter(true);
Michael Kolb8872c232013-01-29 10:33:22 -08001884 }
1885 // In onPause, this was not called if the effects were active. We had to
1886 // wait till the effects completed to do this.
1887 if (mPaused) {
1888 Log.v(TAG, "OnEffectsUpdate: closing effects if activity paused");
1889 closeEffects();
1890 }
1891 }
1892
1893 public void onCancelBgTraining(View v) {
Michael Kolb8872c232013-01-29 10:33:22 -08001894 // Write default effect out to shared prefs
1895 writeDefaultEffectToPrefs();
1896 // Tell VideoCamer to re-init based on new shared pref values.
1897 onSharedPreferenceChanged();
1898 }
1899
1900 @Override
1901 public synchronized void onEffectsError(Exception exception, String fileName) {
1902 // TODO: Eventually we may want to show the user an error dialog, and then restart the
1903 // camera and encoder gracefully. For now, we just delete the file and bail out.
1904 if (fileName != null && new File(fileName).exists()) {
1905 deleteVideoFile(fileName);
1906 }
1907 try {
1908 if (Class.forName("android.filterpacks.videosink.MediaRecorderStopException")
1909 .isInstance(exception)) {
1910 Log.w(TAG, "Problem recoding video file. Removing incomplete file.");
1911 return;
1912 }
1913 } catch (ClassNotFoundException ex) {
1914 Log.w(TAG, ex);
1915 }
1916 throw new RuntimeException("Error during recording!", exception);
1917 }
1918
Michael Kolb8872c232013-01-29 10:33:22 -08001919 @Override
1920 public void onConfigurationChanged(Configuration newConfig) {
Doris Liu6a0de792013-02-26 10:54:25 -08001921 Log.v(TAG, "onConfigurationChanged");
Michael Kolb8872c232013-01-29 10:33:22 -08001922 setDisplayOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001923 }
1924
1925 @Override
1926 public void onOverriddenPreferencesClicked() {
1927 }
1928
1929 @Override
1930 // TODO: Delete this after old camera code is removed
1931 public void onRestorePreferencesClicked() {
1932 }
1933
1934 private boolean effectsActive() {
1935 return (mEffectType != EffectsRecorder.EFFECT_NONE);
1936 }
1937
1938 @Override
1939 public void onSharedPreferenceChanged() {
1940 // ignore the events after "onPause()" or preview has not started yet
1941 if (mPaused) return;
1942 synchronized (mPreferences) {
1943 // If mCameraDevice is not ready then we can set the parameter in
1944 // startPreview().
Doris Liu6432cd62013-06-13 17:20:31 -07001945 if (mCameraDevice == null) return;
Michael Kolb8872c232013-01-29 10:33:22 -08001946
1947 boolean recordLocation = RecordLocationPreference.get(
1948 mPreferences, mContentResolver);
1949 mLocationManager.recordLocation(recordLocation);
1950
1951 // Check if the current effects selection has changed
1952 if (updateEffectSelection()) return;
1953
1954 readVideoPreferences();
Doris Liu6827ce22013-03-12 19:24:28 -07001955 mUI.showTimeLapseUI(mCaptureTimeLapse);
Michael Kolb8872c232013-01-29 10:33:22 -08001956 // We need to restart the preview if preview size is changed.
1957 Size size = mParameters.getPreviewSize();
1958 if (size.width != mDesiredPreviewWidth
1959 || size.height != mDesiredPreviewHeight) {
1960 if (!effectsActive()) {
1961 stopPreview();
1962 } else {
1963 mEffectsRecorder.release();
1964 mEffectsRecorder = null;
1965 }
1966 resizeForPreviewAspectRatio();
1967 startPreview(); // Parameters will be set in startPreview().
1968 } else {
1969 setCameraParameters();
1970 }
Michael Kolb87880792013-04-30 15:38:49 -07001971 mUI.updateOnScreenIndicators(mParameters, mPreferences);
Michael Kolb8872c232013-01-29 10:33:22 -08001972 }
1973 }
1974
Doris Liu6827ce22013-03-12 19:24:28 -07001975 protected void setCameraId(int cameraId) {
1976 ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID);
1977 pref.setValue("" + cameraId);
Michael Kolb8872c232013-01-29 10:33:22 -08001978 }
1979
1980 private void switchCamera() {
1981 if (mPaused) return;
1982
1983 Log.d(TAG, "Start to switch camera.");
1984 mCameraId = mPendingSwitchCameraId;
1985 mPendingSwitchCameraId = -1;
Doris Liu6827ce22013-03-12 19:24:28 -07001986 setCameraId(mCameraId);
Michael Kolb8872c232013-01-29 10:33:22 -08001987
1988 closeCamera();
Doris Liu6827ce22013-03-12 19:24:28 -07001989 mUI.collapseCameraControls();
Michael Kolb8872c232013-01-29 10:33:22 -08001990 // Restart the camera and initialize the UI. From onCreate.
1991 mPreferences.setLocalId(mActivity, mCameraId);
1992 CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
1993 openCamera();
1994 readVideoPreferences();
1995 startPreview();
1996 initializeVideoSnapshot();
1997 resizeForPreviewAspectRatio();
1998 initializeVideoControl();
1999
2000 // From onResume
Doris Liu6432cd62013-06-13 17:20:31 -07002001 mZoomValue = 0;
Doris Liu6827ce22013-03-12 19:24:28 -07002002 mUI.initializeZoom(mParameters);
2003 mUI.setOrientationIndicator(0, false);
Michael Kolb8872c232013-01-29 10:33:22 -08002004
Doris Liu6432cd62013-06-13 17:20:31 -07002005 // Start switch camera animation. Post a message because
2006 // onFrameAvailable from the old camera may already exist.
2007 mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
Michael Kolb87880792013-04-30 15:38:49 -07002008 mUI.updateOnScreenIndicators(mParameters, mPreferences);
Michael Kolb8872c232013-01-29 10:33:22 -08002009 }
2010
2011 // Preview texture has been copied. Now camera can be released and the
2012 // animation can be started.
2013 @Override
2014 public void onPreviewTextureCopied() {
2015 mHandler.sendEmptyMessage(SWITCH_CAMERA);
2016 }
2017
2018 @Override
2019 public void onCaptureTextureCopied() {
2020 }
2021
2022 private boolean updateEffectSelection() {
2023 int previousEffectType = mEffectType;
2024 Object previousEffectParameter = mEffectParameter;
2025 mEffectType = CameraSettings.readEffectType(mPreferences);
2026 mEffectParameter = CameraSettings.readEffectParameter(mPreferences);
2027
2028 if (mEffectType == previousEffectType) {
2029 if (mEffectType == EffectsRecorder.EFFECT_NONE) return false;
2030 if (mEffectParameter.equals(previousEffectParameter)) return false;
2031 }
2032 Log.v(TAG, "New effect selection: " + mPreferences.getString(
2033 CameraSettings.KEY_VIDEO_EFFECT, "none"));
2034
2035 if (mEffectType == EffectsRecorder.EFFECT_NONE) {
2036 // Stop effects and return to normal preview
2037 mEffectsRecorder.stopPreview();
2038 mPreviewing = false;
2039 return true;
2040 }
2041 if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER &&
2042 ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) {
2043 // Request video from gallery to use for background
2044 Intent i = new Intent(Intent.ACTION_PICK);
2045 i.setDataAndType(Video.Media.EXTERNAL_CONTENT_URI,
2046 "video/*");
2047 i.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
2048 mActivity.startActivityForResult(i, REQUEST_EFFECT_BACKDROPPER);
2049 return true;
2050 }
2051 if (previousEffectType == EffectsRecorder.EFFECT_NONE) {
2052 // Stop regular preview and start effects.
2053 stopPreview();
2054 checkQualityAndStartPreview();
2055 } else {
2056 // Switch currently running effect
2057 mEffectsRecorder.setEffect(mEffectType, mEffectParameter);
2058 }
2059 return true;
2060 }
2061
2062 // Verifies that the current preview view size is correct before starting
2063 // preview. If not, resets the surface texture and resizes the view.
2064 private void checkQualityAndStartPreview() {
2065 readVideoPreferences();
Doris Liu6827ce22013-03-12 19:24:28 -07002066 mUI.showTimeLapseUI(mCaptureTimeLapse);
Michael Kolb8872c232013-01-29 10:33:22 -08002067 Size size = mParameters.getPreviewSize();
2068 if (size.width != mDesiredPreviewWidth
2069 || size.height != mDesiredPreviewHeight) {
2070 resizeForPreviewAspectRatio();
2071 }
2072 // Start up preview again
2073 startPreview();
2074 }
2075
Michael Kolb8872c232013-01-29 10:33:22 -08002076 private void initializeVideoSnapshot() {
Doris Liu537718c2013-02-20 16:29:44 -08002077 if (mParameters == null) return;
Angus Kongb50b5cb2013-08-09 14:55:20 -07002078 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
Michael Kolb8872c232013-01-29 10:33:22 -08002079 // Show the tap to focus toast if this is the first start.
2080 if (mPreferences.getBoolean(
2081 CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, true)) {
2082 // Delay the toast for one second to wait for orientation.
2083 mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_SNAPSHOT_TOAST, 1000);
2084 }
Michael Kolb8872c232013-01-29 10:33:22 -08002085 }
2086 }
2087
2088 void showVideoSnapshotUI(boolean enabled) {
Doris Liu537718c2013-02-20 16:29:44 -08002089 if (mParameters == null) return;
Angus Kongb50b5cb2013-08-09 14:55:20 -07002090 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
Doris Liu6432cd62013-06-13 17:20:31 -07002091 if (enabled) {
Sascha Haeberling37f36112013-08-06 14:31:52 -07002092 mUI.animateFlash();
Michael Kolb8872c232013-01-29 10:33:22 -08002093 } else {
Doris Liu6827ce22013-03-12 19:24:28 -07002094 mUI.showPreviewBorder(enabled);
Michael Kolb8872c232013-01-29 10:33:22 -08002095 }
Doris Liu6827ce22013-03-12 19:24:28 -07002096 mUI.enableShutter(!enabled);
Michael Kolb8872c232013-01-29 10:33:22 -08002097 }
2098 }
2099
Michael Kolb8872c232013-01-29 10:33:22 -08002100 @Override
2101 public void updateCameraAppView() {
2102 if (!mPreviewing || mParameters.getFlashMode() == null) return;
2103
2104 // When going to and back from gallery, we need to turn off/on the flash.
Doris Liu6432cd62013-06-13 17:20:31 -07002105 if (!mUI.isVisible()) {
Michael Kolb8872c232013-01-29 10:33:22 -08002106 if (mParameters.getFlashMode().equals(Parameters.FLASH_MODE_OFF)) {
2107 mRestoreFlash = false;
2108 return;
2109 }
2110 mRestoreFlash = true;
2111 setCameraParameters();
2112 } else if (mRestoreFlash) {
2113 mRestoreFlash = false;
2114 setCameraParameters();
2115 }
2116 }
2117
Michael Kolb8872c232013-01-29 10:33:22 -08002118 @Override
Doris Liu6432cd62013-06-13 17:20:31 -07002119 public void onSwitchMode(boolean toCamera) {
2120 mUI.onSwitchMode(toCamera);
Michael Kolb8872c232013-01-29 10:33:22 -08002121 }
2122
Angus Kong9ef99252013-07-18 18:04:19 -07002123 private final class JpegPictureCallback implements CameraPictureCallback {
Michael Kolb8872c232013-01-29 10:33:22 -08002124 Location mLocation;
2125
2126 public JpegPictureCallback(Location loc) {
2127 mLocation = loc;
2128 }
2129
2130 @Override
Angus Kong9ef99252013-07-18 18:04:19 -07002131 public void onPictureTaken(byte [] jpegData, CameraProxy camera) {
Michael Kolb8872c232013-01-29 10:33:22 -08002132 Log.v(TAG, "onPictureTaken");
2133 mSnapshotInProgress = false;
2134 showVideoSnapshotUI(false);
2135 storeImage(jpegData, mLocation);
2136 }
2137 }
2138
2139 private void storeImage(final byte[] data, Location loc) {
2140 long dateTaken = System.currentTimeMillis();
Angus Kongb50b5cb2013-08-09 14:55:20 -07002141 String title = CameraUtil.createJpegName(dateTaken);
Angus Kong0d00a892013-03-26 11:40:40 -07002142 ExifInterface exif = Exif.getExif(data);
2143 int orientation = Exif.getOrientation(exif);
Michael Kolb8872c232013-01-29 10:33:22 -08002144 Size s = mParameters.getPictureSize();
Angus Kong86d36312013-01-31 18:22:44 -08002145 mActivity.getMediaSaveService().addImage(
Angus Kong0d00a892013-03-26 11:40:40 -07002146 data, title, dateTaken, loc, s.width, s.height, orientation,
Angus Kong83a99ae2013-04-17 15:37:07 -07002147 exif, mOnPhotoSavedListener, mContentResolver);
Michael Kolb8872c232013-01-29 10:33:22 -08002148 }
2149
2150 private boolean resetEffect() {
2151 if (mResetEffect) {
2152 String value = mPreferences.getString(CameraSettings.KEY_VIDEO_EFFECT,
2153 mPrefVideoEffectDefault);
2154 if (!mPrefVideoEffectDefault.equals(value)) {
2155 writeDefaultEffectToPrefs();
2156 return true;
2157 }
2158 }
2159 mResetEffect = true;
2160 return false;
2161 }
2162
2163 private String convertOutputFormatToMimeType(int outputFileFormat) {
2164 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
2165 return "video/mp4";
2166 }
2167 return "video/3gpp";
2168 }
2169
2170 private String convertOutputFormatToFileExt(int outputFileFormat) {
2171 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
2172 return ".mp4";
2173 }
2174 return ".3gp";
2175 }
2176
2177 private void closeVideoFileDescriptor() {
2178 if (mVideoFileDescriptor != null) {
2179 try {
2180 mVideoFileDescriptor.close();
2181 } catch (IOException e) {
2182 Log.e(TAG, "Fail to close fd", e);
2183 }
2184 mVideoFileDescriptor = null;
2185 }
2186 }
2187
2188 private void showTapToSnapshotToast() {
2189 new RotateTextToast(mActivity, R.string.video_snapshot_hint, 0)
2190 .show();
2191 // Clear the preference.
2192 Editor editor = mPreferences.edit();
2193 editor.putBoolean(CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, false);
2194 editor.apply();
2195 }
2196
Michael Kolb8872c232013-01-29 10:33:22 -08002197 @Override
2198 public boolean updateStorageHintOnResume() {
2199 return true;
2200 }
2201
2202 // required by OnPreferenceChangedListener
2203 @Override
2204 public void onCameraPickerClicked(int cameraId) {
2205 if (mPaused || mPendingSwitchCameraId != -1) return;
2206
2207 mPendingSwitchCameraId = cameraId;
Doris Liu6432cd62013-06-13 17:20:31 -07002208 Log.d(TAG, "Start to copy texture.");
2209 // We need to keep a preview frame for the animation before
2210 // releasing the camera. This will trigger onPreviewTextureCopied.
2211 // TODO: ((CameraScreenNail) mActivity.mCameraScreenNail).copyTexture();
2212 // Disable all camera controls.
2213 mSwitchingCamera = true;
Michael Kolb8872c232013-01-29 10:33:22 -08002214
Doris Liu6a0de792013-02-26 10:54:25 -08002215 }
2216
2217 @Override
Michael Kolb8872c232013-01-29 10:33:22 -08002218 public void onShowSwitcherPopup() {
Doris Liu6827ce22013-03-12 19:24:28 -07002219 mUI.onShowSwitcherPopup();
Michael Kolb8872c232013-01-29 10:33:22 -08002220 }
Angus Kong86d36312013-01-31 18:22:44 -08002221
2222 @Override
2223 public void onMediaSaveServiceConnected(MediaSaveService s) {
2224 // do nothing.
2225 }
Angus Kong395ee2d2013-07-15 12:42:41 -07002226
2227 @Override
2228 public void onPreviewUIReady() {
2229 startPreview();
2230 }
2231
2232 @Override
2233 public void onPreviewUIDestroyed() {
2234 stopPreview();
2235 }
Michael Kolb8872c232013-01-29 10:33:22 -08002236}