blob: 188a38965812c5e8cba6134eee7a6a5964e35f19 [file] [log] [blame]
Doris Liu7234f4d2013-05-14 14:37:46 -07001/*
2 * Copyright (C) 2013 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;
34import android.hardware.Camera.PictureCallback;
35import android.hardware.Camera.Size;
36import android.location.Location;
37import android.media.CamcorderProfile;
38import android.media.CameraProfile;
39import android.media.MediaRecorder;
40import android.net.Uri;
41import android.os.Build;
42import android.os.Bundle;
43import android.os.Handler;
44import android.os.Message;
45import android.os.ParcelFileDescriptor;
46import android.os.SystemClock;
47import android.provider.MediaStore;
Doris Liu29a8d5b2013-06-03 15:20:17 -070048import android.provider.MediaStore.MediaColumns;
Doris Liu7234f4d2013-05-14 14:37:46 -070049import android.provider.MediaStore.Video;
50import android.util.Log;
51import android.view.KeyEvent;
52import android.view.MotionEvent;
53import android.view.OrientationEventListener;
54import android.view.Surface;
55import android.view.View;
56import android.view.WindowManager;
57import android.widget.Toast;
58
59import com.android.camera.CameraManager.CameraProxy;
60import com.android.camera.ui.PopupManager;
61import com.android.camera.ui.RotateTextToast;
62import com.android.gallery3d.R;
63import com.android.gallery3d.app.OrientationManager;
64import com.android.gallery3d.common.ApiHelper;
65import com.android.gallery3d.exif.ExifInterface;
66import com.android.gallery3d.util.AccessibilityUtils;
67import com.android.gallery3d.util.UsageStatistics;
68
69import java.io.File;
70import java.io.IOException;
71import java.text.SimpleDateFormat;
72import java.util.Date;
73import java.util.Iterator;
74import java.util.List;
75
76public class NewVideoModule implements NewCameraModule,
77 VideoController,
78 CameraPreference.OnPreferenceChangedListener,
79 ShutterButton.OnShutterButtonListener,
80 MediaRecorder.OnErrorListener,
81 MediaRecorder.OnInfoListener,
82 EffectsRecorder.EffectsListener {
83
84 private static final String TAG = "CAM_VideoModule";
85
86 // We number the request code from 1000 to avoid collision with Gallery.
87 private static final int REQUEST_EFFECT_BACKDROPPER = 1000;
88
89 private static final int CHECK_DISPLAY_ROTATION = 3;
90 private static final int CLEAR_SCREEN_DELAY = 4;
91 private static final int UPDATE_RECORD_TIME = 5;
92 private static final int ENABLE_SHUTTER_BUTTON = 6;
93 private static final int SHOW_TAP_TO_SNAPSHOT_TOAST = 7;
94 private static final int SWITCH_CAMERA = 8;
95 private static final int SWITCH_CAMERA_START_ANIMATION = 9;
96 private static final int HIDE_SURFACE_VIEW = 10;
Doris Liu29a8d5b2013-06-03 15:20:17 -070097 private static final int CAPTURE_ANIMATION_DONE = 11;
Doris Liu7234f4d2013-05-14 14:37:46 -070098
99 private static final int SCREEN_DELAY = 2 * 60 * 1000;
100
101 private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
102
103 /**
104 * An unpublished intent flag requesting to start recording straight away
105 * and return as soon as recording is stopped.
106 * TODO: consider publishing by moving into MediaStore.
107 */
108 private static final String EXTRA_QUICK_CAPTURE =
109 "android.intent.extra.quickCapture";
110
111 private static final int MIN_THUMB_SIZE = 64;
112 // module fields
113 private NewCameraActivity mActivity;
114 private boolean mPaused;
115 private int mCameraId;
116 private Parameters mParameters;
117
118 private Boolean mCameraOpened = false;
119 private boolean mIsInReviewMode;
120 private boolean mSnapshotInProgress = false;
121
122 private static final String EFFECT_BG_FROM_GALLERY = "gallery";
123
124 private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
125
126 private ComboPreferences mPreferences;
127 private PreferenceGroup mPreferenceGroup;
128
129 private boolean mIsVideoCaptureIntent;
130 private boolean mQuickCapture;
131
132 private MediaRecorder mMediaRecorder;
133 private EffectsRecorder mEffectsRecorder;
134 private boolean mEffectsDisplayResult;
135
136 private int mEffectType = EffectsRecorder.EFFECT_NONE;
137 private Object mEffectParameter = null;
138 private String mEffectUriFromGallery = null;
139 private String mPrefVideoEffectDefault;
140 private boolean mResetEffect = true;
141
142 private boolean mSwitchingCamera;
143 private boolean mMediaRecorderRecording = false;
144 private long mRecordingStartTime;
145 private boolean mRecordingTimeCountsDown = false;
146 private long mOnResumeTime;
147 // The video file that the hardware camera is about to record into
148 // (or is recording into.)
149 private String mVideoFilename;
150 private ParcelFileDescriptor mVideoFileDescriptor;
151
152 // The video file that has already been recorded, and that is being
153 // examined by the user.
154 private String mCurrentVideoFilename;
155 private Uri mCurrentVideoUri;
156 private ContentValues mCurrentVideoValues;
157
158 private CamcorderProfile mProfile;
159
160 // The video duration limit. 0 menas no limit.
161 private int mMaxVideoDurationInMs;
162
163 // Time Lapse parameters.
164 private boolean mCaptureTimeLapse = false;
165 // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
166 private int mTimeBetweenTimeLapseFrameCaptureMs = 0;
167
168 boolean mPreviewing = false; // True if preview is started.
169 // The display rotation in degrees. This is only valid when mPreviewing is
170 // true.
171 private int mDisplayRotation;
172 private int mCameraDisplayOrientation;
173
174 private int mDesiredPreviewWidth;
175 private int mDesiredPreviewHeight;
176 private ContentResolver mContentResolver;
177
178 private LocationManager mLocationManager;
179 private OrientationManager mOrientationManager;
180
Doris Liu7234f4d2013-05-14 14:37:46 -0700181 private Surface mSurface;
182 private int mPendingSwitchCameraId;
183 private boolean mOpenCameraFail;
184 private boolean mCameraDisabled;
185 private final Handler mHandler = new MainHandler();
186 private NewVideoUI mUI;
187 private CameraProxy mCameraDevice;
188
189 // The degrees of the device rotated clockwise from its natural orientation.
190 private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
191
192 private int mZoomValue; // The current zoom value.
193
194 private boolean mRestoreFlash; // This is used to check if we need to restore the flash
195 // status when going back from gallery.
196
Doris Liu29a8d5b2013-06-03 15:20:17 -0700197 private final MediaSaveService.OnMediaSavedListener mOnVideoSavedListener =
Doris Liu7234f4d2013-05-14 14:37:46 -0700198 new MediaSaveService.OnMediaSavedListener() {
199 @Override
200 public void onMediaSaved(Uri uri) {
201 if (uri != null) {
Doris Liu29a8d5b2013-06-03 15:20:17 -0700202 mActivity.sendBroadcast(
203 new Intent(Util.ACTION_NEW_VIDEO, uri));
204 Util.broadcastNewPicture(mActivity, uri);
205 }
206 }
207 };
208
209 private final MediaSaveService.OnMediaSavedListener mOnPhotoSavedListener =
210 new MediaSaveService.OnMediaSavedListener() {
211 @Override
212 public void onMediaSaved(Uri uri) {
213 if (uri != null) {
214 Util.broadcastNewPicture(mActivity, uri);
Doris Liu7234f4d2013-05-14 14:37:46 -0700215 }
216 }
217 };
218
219
220 protected class CameraOpenThread extends Thread {
221 @Override
222 public void run() {
223 openCamera();
224 }
225 }
226
227 private void openCamera() {
228 try {
229 synchronized(mCameraOpened) {
230 if (!mCameraOpened) {
231 mCameraDevice = Util.openCamera(mActivity, mCameraId);
232 mCameraOpened = true;
233 }
234 }
235 mParameters = mCameraDevice.getParameters();
236 } catch (CameraHardwareException e) {
237 mOpenCameraFail = true;
238 } catch (CameraDisabledException e) {
239 mCameraDisabled = true;
240 }
241 }
242
243 // This Handler is used to post message back onto the main thread of the
244 // application
245 private class MainHandler extends Handler {
246 @Override
247 public void handleMessage(Message msg) {
248 switch (msg.what) {
249
250 case ENABLE_SHUTTER_BUTTON:
251 mUI.enableShutter(true);
252 break;
253
254 case CLEAR_SCREEN_DELAY: {
255 mActivity.getWindow().clearFlags(
256 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
257 break;
258 }
259
260 case UPDATE_RECORD_TIME: {
261 updateRecordingTime();
262 break;
263 }
264
265 case CHECK_DISPLAY_ROTATION: {
266 // Restart the preview if display rotation has changed.
267 // Sometimes this happens when the device is held upside
268 // down and camera app is opened. Rotation animation will
269 // take some time and the rotation value we have got may be
270 // wrong. Framework does not have a callback for this now.
271 if ((Util.getDisplayRotation(mActivity) != mDisplayRotation)
272 && !mMediaRecorderRecording && !mSwitchingCamera) {
273 startPreview();
274 }
275 if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
276 mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
277 }
278 break;
279 }
280
281 case SHOW_TAP_TO_SNAPSHOT_TOAST: {
282 showTapToSnapshotToast();
283 break;
284 }
285
286 case SWITCH_CAMERA: {
287 switchCamera();
288 break;
289 }
290
291 case SWITCH_CAMERA_START_ANIMATION: {
292 //TODO:
293 //((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
294
295 // Enable all camera controls.
296 mSwitchingCamera = false;
297 break;
298 }
299
Doris Liu29a8d5b2013-06-03 15:20:17 -0700300 case CAPTURE_ANIMATION_DONE: {
301 mUI.enablePreviewThumb(false);
302 break;
303 }
304
Doris Liu7234f4d2013-05-14 14:37:46 -0700305 default:
306 Log.v(TAG, "Unhandled message: " + msg.what);
307 break;
308 }
309 }
310 }
311
312 private BroadcastReceiver mReceiver = null;
313
314 private class MyBroadcastReceiver extends BroadcastReceiver {
315 @Override
316 public void onReceive(Context context, Intent intent) {
317 String action = intent.getAction();
318 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
319 stopVideoRecording();
320 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
321 Toast.makeText(mActivity,
322 mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
323 }
324 }
325 }
326
327 private String createName(long dateTaken) {
328 Date date = new Date(dateTaken);
329 SimpleDateFormat dateFormat = new SimpleDateFormat(
330 mActivity.getString(R.string.video_file_name_format));
331
332 return dateFormat.format(date);
333 }
334
335 private int getPreferredCameraId(ComboPreferences preferences) {
336 int intentCameraId = Util.getCameraFacingIntentExtras(mActivity);
337 if (intentCameraId != -1) {
338 // Testing purpose. Launch a specific camera through the intent
339 // extras.
340 return intentCameraId;
341 } else {
342 return CameraSettings.readPreferredCameraId(preferences);
343 }
344 }
345
346 private void initializeSurfaceView() {
347 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { // API level < 16
348 mUI.initializeSurfaceView();
349 }
350 }
351
352 @Override
353 public void init(NewCameraActivity activity, View root) {
354 mActivity = activity;
355 mUI = new NewVideoUI(activity, this, root);
356 mPreferences = new ComboPreferences(mActivity);
357 CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
358 mCameraId = getPreferredCameraId(mPreferences);
359
360 mPreferences.setLocalId(mActivity, mCameraId);
361 CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
362
363 mPrefVideoEffectDefault = mActivity.getString(R.string.pref_video_effect_default);
364 resetEffect();
365 mOrientationManager = new OrientationManager(mActivity);
366
367 /*
368 * To reduce startup time, we start the preview in another thread.
369 * We make sure the preview is started at the end of onCreate.
370 */
371 CameraOpenThread cameraOpenThread = new CameraOpenThread();
372 cameraOpenThread.start();
373
374 mContentResolver = mActivity.getContentResolver();
375
376 // Surface texture is from camera screen nail and startPreview needs it.
377 // This must be done before startPreview.
378 mIsVideoCaptureIntent = isVideoCaptureIntent();
379 initializeSurfaceView();
380
381 // Make sure camera device is opened.
382 try {
383 cameraOpenThread.join();
384 if (mOpenCameraFail) {
385 Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
386 return;
387 } else if (mCameraDisabled) {
388 Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
389 return;
390 }
391 } catch (InterruptedException ex) {
392 // ignore
393 }
394
395 readVideoPreferences();
396 mUI.setPrefChangedListener(this);
397 new Thread(new Runnable() {
398 @Override
399 public void run() {
400 startPreview();
401 }
402 }).start();
403
404 mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
405 mLocationManager = new LocationManager(mActivity, null);
406
407 mUI.setOrientationIndicator(0, false);
408 setDisplayOrientation();
409
410 mUI.showTimeLapseUI(mCaptureTimeLapse);
411 initializeVideoSnapshot();
412 resizeForPreviewAspectRatio();
413
414 initializeVideoControl();
415 mPendingSwitchCameraId = -1;
Doris Liu29a8d5b2013-06-03 15:20:17 -0700416 mUI.updateOnScreenIndicators(mParameters, mPreferences);
Doris Liu7234f4d2013-05-14 14:37:46 -0700417
418 // Disable the shutter button if effects are ON since it might take
419 // a little more time for the effects preview to be ready. We do not
420 // want to allow recording before that happens. The shutter button
421 // will be enabled when we get the message from effectsrecorder that
422 // the preview is running. This becomes critical when the camera is
423 // swapped.
424 if (effectsActive()) {
425 mUI.enableShutter(false);
426 }
427 }
428
429 // SingleTapListener
430 // Preview area is touched. Take a picture.
431 @Override
432 public void onSingleTapUp(View view, int x, int y) {
433 if (mMediaRecorderRecording && effectsActive()) {
434 new RotateTextToast(mActivity, R.string.disable_video_snapshot_hint,
435 mOrientation).show();
436 return;
437 }
438
439 MediaSaveService s = mActivity.getMediaSaveService();
440 if (mPaused || mSnapshotInProgress || effectsActive() || s == null || s.isQueueFull()) {
441 return;
442 }
443
444 if (!mMediaRecorderRecording) {
445 // check for dismissing popup
446 mUI.dismissPopup(true);
447 return;
448 }
449
450 // Set rotation and gps data.
451 int rotation = Util.getJpegRotation(mCameraId, mOrientation);
452 mParameters.setRotation(rotation);
453 Location loc = mLocationManager.getCurrentLocation();
454 Util.setGpsParameters(mParameters, loc);
455 mCameraDevice.setParameters(mParameters);
456
457 Log.v(TAG, "Video snapshot start");
458 mCameraDevice.takePicture(null, null, null, new JpegPictureCallback(loc));
459 showVideoSnapshotUI(true);
460 mSnapshotInProgress = true;
461 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
462 UsageStatistics.ACTION_CAPTURE_DONE, "VideoSnapshot");
463 }
464
465 @Override
466 public void onStop() {}
467
468 private void loadCameraPreferences() {
469 CameraSettings settings = new CameraSettings(mActivity, mParameters,
470 mCameraId, CameraHolder.instance().getCameraInfo());
471 // Remove the video quality preference setting when the quality is given in the intent.
472 mPreferenceGroup = filterPreferenceScreenByIntent(
473 settings.getPreferenceGroup(R.xml.video_preferences));
474 }
475
476 private void initializeVideoControl() {
477 loadCameraPreferences();
478 mUI.initializePopup(mPreferenceGroup);
479 if (effectsActive()) {
480 mUI.overrideSettings(
481 CameraSettings.KEY_VIDEO_QUALITY,
482 Integer.toString(getLowVideoQuality()));
483 }
484 }
485
486 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
487 private static int getLowVideoQuality() {
488 if (ApiHelper.HAS_FINE_RESOLUTION_QUALITY_LEVELS) {
489 return CamcorderProfile.QUALITY_480P;
490 } else {
491 return CamcorderProfile.QUALITY_LOW;
492 }
493 }
494
495
496 @Override
497 public void onOrientationChanged(int orientation) {
498 // We keep the last known orientation. So if the user first orient
499 // the camera then point the camera to floor or sky, we still have
500 // the correct orientation.
501 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return;
502 int newOrientation = Util.roundOrientation(orientation, mOrientation);
503
504 if (mOrientation != newOrientation) {
505 mOrientation = newOrientation;
506 // The input of effects recorder is affected by
507 // android.hardware.Camera.setDisplayOrientation. Its value only
508 // compensates the camera orientation (no Display.getRotation).
509 // So the orientation hint here should only consider sensor
510 // orientation.
511 if (effectsActive()) {
512 mEffectsRecorder.setOrientationHint(mOrientation);
513 }
514 }
515
516 // Show the toast after getting the first orientation changed.
517 if (mHandler.hasMessages(SHOW_TAP_TO_SNAPSHOT_TOAST)) {
518 mHandler.removeMessages(SHOW_TAP_TO_SNAPSHOT_TOAST);
519 showTapToSnapshotToast();
520 }
521 }
522
523 private void startPlayVideoActivity() {
524 Intent intent = new Intent(Intent.ACTION_VIEW);
525 intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
526 try {
527 mActivity.startActivity(intent);
528 } catch (ActivityNotFoundException ex) {
529 Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
530 }
531 }
532
533 @OnClickAttr
534 public void onReviewPlayClicked(View v) {
535 startPlayVideoActivity();
536 }
537
538 @OnClickAttr
539 public void onReviewDoneClicked(View v) {
540 mIsInReviewMode = false;
541 doReturnToCaller(true);
542 }
543
544 @OnClickAttr
545 public void onReviewCancelClicked(View v) {
546 mIsInReviewMode = false;
547 stopVideoRecording();
548 doReturnToCaller(false);
549 }
550
551 @Override
552 public boolean isInReviewMode() {
553 return mIsInReviewMode;
554 }
555
556 private void onStopVideoRecording() {
557 mEffectsDisplayResult = true;
558 boolean recordFail = stopVideoRecording();
559 if (mIsVideoCaptureIntent) {
560 if (!effectsActive()) {
561 if (mQuickCapture) {
562 doReturnToCaller(!recordFail);
563 } else if (!recordFail) {
564 showCaptureResult();
565 }
566 }
567 } else if (!recordFail){
568 // Start capture animation.
569 if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
570 // The capture animation is disabled on ICS because we use SurfaceView
571 // for preview during recording. When the recording is done, we switch
572 // back to use SurfaceTexture for preview and we need to stop then start
573 // the preview. This will cause the preview flicker since the preview
574 // will not be continuous for a short period of time.
575 // TODO: need to get the capture animation to work
576 // ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation);
Doris Liu29a8d5b2013-06-03 15:20:17 -0700577
578 mUI.enablePreviewThumb(true);
579
580 // Make sure to disable the thumbnail preview after the
581 // animation is done to disable the click target.
582 mHandler.removeMessages(CAPTURE_ANIMATION_DONE);
583 mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE,
584 CaptureAnimManager.getAnimationDuration());
Doris Liu7234f4d2013-05-14 14:37:46 -0700585 }
586 }
587 }
588
589 public void onProtectiveCurtainClick(View v) {
590 // Consume clicks
591 }
592
593 @Override
594 public void onShutterButtonClick() {
595 if (mUI.collapseCameraControls() || mSwitchingCamera) return;
596
597 boolean stop = mMediaRecorderRecording;
598
599 if (stop) {
600 onStopVideoRecording();
601 } else {
602 startVideoRecording();
603 }
604 mUI.enableShutter(false);
605
606 // Keep the shutter button disabled when in video capture intent
607 // mode and recording is stopped. It'll be re-enabled when
608 // re-take button is clicked.
609 if (!(mIsVideoCaptureIntent && stop)) {
610 mHandler.sendEmptyMessageDelayed(
611 ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
612 }
613 }
614
615 @Override
616 public void onShutterButtonFocus(boolean pressed) {
617 mUI.setShutterPressed(pressed);
618 }
619
620 private void readVideoPreferences() {
621 // The preference stores values from ListPreference and is thus string type for all values.
622 // We need to convert it to int manually.
623 String defaultQuality = CameraSettings.getDefaultVideoQuality(mCameraId,
624 mActivity.getResources().getString(R.string.pref_video_quality_default));
625 String videoQuality =
626 mPreferences.getString(CameraSettings.KEY_VIDEO_QUALITY,
627 defaultQuality);
628 int quality = Integer.valueOf(videoQuality);
629
630 // Set video quality.
631 Intent intent = mActivity.getIntent();
632 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
633 int extraVideoQuality =
634 intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
635 if (extraVideoQuality > 0) {
636 quality = CamcorderProfile.QUALITY_HIGH;
637 } else { // 0 is mms.
638 quality = CamcorderProfile.QUALITY_LOW;
639 }
640 }
641
642 // Set video duration limit. The limit is read from the preference,
643 // unless it is specified in the intent.
644 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
645 int seconds =
646 intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
647 mMaxVideoDurationInMs = 1000 * seconds;
648 } else {
649 mMaxVideoDurationInMs = CameraSettings.getMaxVideoDuration(mActivity);
650 }
651
652 // Set effect
653 mEffectType = CameraSettings.readEffectType(mPreferences);
654 if (mEffectType != EffectsRecorder.EFFECT_NONE) {
655 mEffectParameter = CameraSettings.readEffectParameter(mPreferences);
656 // Set quality to be no higher than 480p.
657 CamcorderProfile profile = CamcorderProfile.get(mCameraId, quality);
658 if (profile.videoFrameHeight > 480) {
659 quality = getLowVideoQuality();
660 }
661 } else {
662 mEffectParameter = null;
663 }
664 // Read time lapse recording interval.
665 if (ApiHelper.HAS_TIME_LAPSE_RECORDING) {
666 String frameIntervalStr = mPreferences.getString(
667 CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
668 mActivity.getString(R.string.pref_video_time_lapse_frame_interval_default));
669 mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
670 mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
671 }
672 // TODO: This should be checked instead directly +1000.
673 if (mCaptureTimeLapse) quality += 1000;
674 mProfile = CamcorderProfile.get(mCameraId, quality);
675 getDesiredPreviewSize();
676 }
677
678 private void writeDefaultEffectToPrefs() {
679 ComboPreferences.Editor editor = mPreferences.edit();
680 editor.putString(CameraSettings.KEY_VIDEO_EFFECT,
681 mActivity.getString(R.string.pref_video_effect_default));
682 editor.apply();
683 }
684
685 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
686 private void getDesiredPreviewSize() {
687 mParameters = mCameraDevice.getParameters();
688 if (ApiHelper.HAS_GET_SUPPORTED_VIDEO_SIZE) {
689 if (mParameters.getSupportedVideoSizes() == null || effectsActive()) {
690 mDesiredPreviewWidth = mProfile.videoFrameWidth;
691 mDesiredPreviewHeight = mProfile.videoFrameHeight;
692 } else { // Driver supports separates outputs for preview and video.
693 List<Size> sizes = mParameters.getSupportedPreviewSizes();
694 Size preferred = mParameters.getPreferredPreviewSizeForVideo();
695 int product = preferred.width * preferred.height;
696 Iterator<Size> it = sizes.iterator();
697 // Remove the preview sizes that are not preferred.
698 while (it.hasNext()) {
699 Size size = it.next();
700 if (size.width * size.height > product) {
701 it.remove();
702 }
703 }
704 Size optimalSize = Util.getOptimalPreviewSize(mActivity, sizes,
705 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
706 mDesiredPreviewWidth = optimalSize.width;
707 mDesiredPreviewHeight = optimalSize.height;
708 }
709 } else {
710 mDesiredPreviewWidth = mProfile.videoFrameWidth;
711 mDesiredPreviewHeight = mProfile.videoFrameHeight;
712 }
713 mUI.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
714 Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
715 ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
716 }
717
718 private void resizeForPreviewAspectRatio() {
719 mUI.setAspectRatio(
720 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
721 }
722
723 @Override
724 public void installIntentFilter() {
725 // install an intent filter to receive SD card related events.
726 IntentFilter intentFilter =
727 new IntentFilter(Intent.ACTION_MEDIA_EJECT);
728 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
729 intentFilter.addDataScheme("file");
730 mReceiver = new MyBroadcastReceiver();
731 mActivity.registerReceiver(mReceiver, intentFilter);
732 }
733
734 @Override
735 public void onResumeBeforeSuper() {
736 mPaused = false;
737 }
738
739 @Override
740 public void onResumeAfterSuper() {
741 if (mOpenCameraFail || mCameraDisabled)
742 return;
743 mUI.enableShutter(false);
744 mZoomValue = 0;
745
746 showVideoSnapshotUI(false);
747
748 if (!mPreviewing) {
749 resetEffect();
750 openCamera();
751 if (mOpenCameraFail) {
752 Util.showErrorAndFinish(mActivity,
753 R.string.cannot_connect_camera);
754 return;
755 } else if (mCameraDisabled) {
756 Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
757 return;
758 }
759 readVideoPreferences();
760 resizeForPreviewAspectRatio();
761 new Thread(new Runnable() {
762 @Override
763 public void run() {
764 startPreview();
765 }
766 }).start();
767 } else {
768 // preview already started
769 mUI.enableShutter(true);
770 }
771
772 // Initializing it here after the preview is started.
773 mUI.initializeZoom(mParameters);
774
775 keepScreenOnAwhile();
776
777 // Initialize location service.
778 boolean recordLocation = RecordLocationPreference.get(mPreferences,
779 mContentResolver);
780 mLocationManager.recordLocation(recordLocation);
781
782 if (mPreviewing) {
783 mOnResumeTime = SystemClock.uptimeMillis();
784 mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
785 }
786 // Dismiss open menu if exists.
787 PopupManager.getInstance(mActivity).notifyShowPopup(null);
788
Doris Liu7234f4d2013-05-14 14:37:46 -0700789 UsageStatistics.onContentViewChanged(
790 UsageStatistics.COMPONENT_CAMERA, "VideoModule");
791 }
792
793 private void setDisplayOrientation() {
794 mDisplayRotation = Util.getDisplayRotation(mActivity);
795 mCameraDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
796 // Change the camera display orientation
797 if (mCameraDevice != null) {
798 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
799 }
800 }
801
802 @Override
803 public int onZoomChanged(int index) {
804 // Not useful to change zoom value when the activity is paused.
805 if (mPaused) return index;
806 mZoomValue = index;
807 if (mParameters == null || mCameraDevice == null) return index;
808 // Set zoom parameters asynchronously
809 mParameters.setZoom(mZoomValue);
810 mCameraDevice.setParameters(mParameters);
811 Parameters p = mCameraDevice.getParameters();
812 if (p != null) return p.getZoom();
813 return index;
814 }
815 private void startPreview() {
816 Log.v(TAG, "startPreview");
817
818 mCameraDevice.setErrorCallback(mErrorCallback);
819 if (mPreviewing == true) {
820 stopPreview();
821 if (effectsActive() && mEffectsRecorder != null) {
822 mEffectsRecorder.release();
823 mEffectsRecorder = null;
824 }
825 }
826
827 setDisplayOrientation();
828 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
829 setCameraParameters();
830
831 try {
832 if (!effectsActive()) {
833 SurfaceTexture surfaceTexture = mUI.getSurfaceTexture();
834 if (surfaceTexture == null) {
835 return; // The texture has been destroyed (pause, etc)
836 }
837 mCameraDevice.setPreviewTextureAsync(surfaceTexture);
838 mCameraDevice.startPreviewAsync();
839 mPreviewing = true;
840 onPreviewStarted();
841 } else {
842 initializeEffectsPreview();
843 mEffectsRecorder.startPreview();
844 mPreviewing = true;
845 onPreviewStarted();
846 }
847 } catch (Throwable ex) {
848 closeCamera();
849 throw new RuntimeException("startPreview failed", ex);
850 } finally {
851 mActivity.runOnUiThread(new Runnable() {
852 @Override
853 public void run() {
854 if (mOpenCameraFail) {
855 Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
856 } else if (mCameraDisabled) {
857 Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
858 }
859 }
860 });
861 }
862
863 }
864
865 private void onPreviewStarted() {
866 mUI.enableShutter(true);
867 }
868
869 @Override
870 public void stopPreview() {
871 if (!mPreviewing) return;
872 mCameraDevice.stopPreview();
873 mPreviewing = false;
874 }
875
876 // Closing the effects out. Will shut down the effects graph.
877 private void closeEffects() {
878 Log.v(TAG, "Closing effects");
879 mEffectType = EffectsRecorder.EFFECT_NONE;
880 if (mEffectsRecorder == null) {
881 Log.d(TAG, "Effects are already closed. Nothing to do");
882 return;
883 }
884 // This call can handle the case where the camera is already released
885 // after the recording has been stopped.
886 mEffectsRecorder.release();
887 mEffectsRecorder = null;
888 }
889
890 // By default, we want to close the effects as well with the camera.
891 private void closeCamera() {
892 closeCamera(true);
893 }
894
895 // In certain cases, when the effects are active, we may want to shutdown
896 // only the camera related parts, and handle closing the effects in the
897 // effectsUpdate callback.
898 // For example, in onPause, we want to make the camera available to
899 // outside world immediately, however, want to wait till the effects
900 // callback to shut down the effects. In such a case, we just disconnect
901 // the effects from the camera by calling disconnectCamera. That way
902 // the effects can handle that when shutting down.
903 //
904 // @param closeEffectsAlso - indicates whether we want to close the
905 // effects also along with the camera.
906 private void closeCamera(boolean closeEffectsAlso) {
907 Log.v(TAG, "closeCamera");
908 if (mCameraDevice == null) {
909 Log.d(TAG, "already stopped.");
910 return;
911 }
912
913 if (mEffectsRecorder != null) {
914 // Disconnect the camera from effects so that camera is ready to
915 // be released to the outside world.
916 mEffectsRecorder.disconnectCamera();
917 }
918 if (closeEffectsAlso) closeEffects();
919 mCameraDevice.setZoomChangeListener(null);
920 mCameraDevice.setErrorCallback(null);
921 synchronized(mCameraOpened) {
922 if (mCameraOpened) {
923 CameraHolder.instance().release();
924 }
925 mCameraOpened = false;
926 }
927 mCameraDevice = null;
928 mPreviewing = false;
929 mSnapshotInProgress = false;
930 }
931
932 private void releasePreviewResources() {
933 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
934 mUI.hideSurfaceView();
935 }
936 }
937
938 @Override
939 public void onPauseBeforeSuper() {
940 mPaused = true;
941
942 if (mMediaRecorderRecording) {
943 // Camera will be released in onStopVideoRecording.
944 onStopVideoRecording();
945 } else {
946 closeCamera();
947 if (!effectsActive()) releaseMediaRecorder();
948 }
949 if (effectsActive()) {
950 // If the effects are active, make sure we tell the graph that the
951 // surfacetexture is not valid anymore. Disconnect the graph from
952 // the display. This should be done before releasing the surface
953 // texture.
954 mEffectsRecorder.disconnectDisplay();
955 } else {
956 // Close the file descriptor and clear the video namer only if the
957 // effects are not active. If effects are active, we need to wait
958 // till we get the callback from the Effects that the graph is done
959 // recording. That also needs a change in the stopVideoRecording()
960 // call to not call closeCamera if the effects are active, because
961 // that will close down the effects are well, thus making this if
962 // condition invalid.
963 closeVideoFileDescriptor();
Doris Liu7234f4d2013-05-14 14:37:46 -0700964 }
965
966 releasePreviewResources();
967
968 if (mReceiver != null) {
969 mActivity.unregisterReceiver(mReceiver);
970 mReceiver = null;
971 }
972 resetScreenOn();
973
974 if (mLocationManager != null) mLocationManager.recordLocation(false);
975
976 mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
977 mHandler.removeMessages(SWITCH_CAMERA);
978 mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION);
979 mPendingSwitchCameraId = -1;
980 mSwitchingCamera = false;
981 // Call onPause after stopping video recording. So the camera can be
982 // released as soon as possible.
983 }
984
985 @Override
986 public void onPauseAfterSuper() {
987 }
988
989 @Override
990 public void onUserInteraction() {
991 if (!mMediaRecorderRecording && !mActivity.isFinishing()) {
992 keepScreenOnAwhile();
993 }
994 }
995
996 @Override
997 public boolean onBackPressed() {
998 if (mPaused) return true;
999 if (mMediaRecorderRecording) {
1000 onStopVideoRecording();
1001 return true;
1002 } else if (mUI.hidePieRenderer()) {
1003 return true;
1004 } else {
1005 return mUI.removeTopLevelPopup();
1006 }
1007 }
1008
1009 @Override
1010 public boolean onKeyDown(int keyCode, KeyEvent event) {
1011 // Do not handle any key if the activity is paused.
1012 if (mPaused) {
1013 return true;
1014 }
1015
1016 switch (keyCode) {
1017 case KeyEvent.KEYCODE_CAMERA:
1018 if (event.getRepeatCount() == 0) {
1019 mUI.clickShutter();
1020 return true;
1021 }
1022 break;
1023 case KeyEvent.KEYCODE_DPAD_CENTER:
1024 if (event.getRepeatCount() == 0) {
1025 mUI.clickShutter();
1026 return true;
1027 }
1028 break;
1029 case KeyEvent.KEYCODE_MENU:
1030 if (mMediaRecorderRecording) return true;
1031 break;
1032 }
1033 return false;
1034 }
1035
1036 @Override
1037 public boolean onKeyUp(int keyCode, KeyEvent event) {
1038 switch (keyCode) {
1039 case KeyEvent.KEYCODE_CAMERA:
1040 mUI.pressShutter(false);
1041 return true;
1042 }
1043 return false;
1044 }
1045
1046 @Override
1047 public boolean isVideoCaptureIntent() {
1048 String action = mActivity.getIntent().getAction();
1049 return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
1050 }
1051
1052 private void doReturnToCaller(boolean valid) {
1053 Intent resultIntent = new Intent();
1054 int resultCode;
1055 if (valid) {
1056 resultCode = Activity.RESULT_OK;
1057 resultIntent.setData(mCurrentVideoUri);
1058 } else {
1059 resultCode = Activity.RESULT_CANCELED;
1060 }
1061 mActivity.setResultEx(resultCode, resultIntent);
1062 mActivity.finish();
1063 }
1064
1065 private void cleanupEmptyFile() {
1066 if (mVideoFilename != null) {
1067 File f = new File(mVideoFilename);
1068 if (f.length() == 0 && f.delete()) {
1069 Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
1070 mVideoFilename = null;
1071 }
1072 }
1073 }
1074
1075 private void setupMediaRecorderPreviewDisplay() {
1076 // Nothing to do here if using SurfaceTexture.
1077 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
1078 // We stop the preview here before unlocking the device because we
1079 // need to change the SurfaceTexture to SurfaceView for preview.
1080 stopPreview();
1081 mCameraDevice.setPreviewDisplayAsync(mUI.getSurfaceHolder());
1082 // The orientation for SurfaceTexture is different from that for
1083 // SurfaceView. For SurfaceTexture we don't need to consider the
1084 // display rotation. Just consider the sensor's orientation and we
1085 // will set the orientation correctly when showing the texture.
1086 // Gallery will handle the orientation for the preview. For
1087 // SurfaceView we will have to take everything into account so the
1088 // display rotation is considered.
1089 mCameraDevice.setDisplayOrientation(
1090 Util.getDisplayOrientation(mDisplayRotation, mCameraId));
1091 mCameraDevice.startPreviewAsync();
1092 mPreviewing = true;
1093 mMediaRecorder.setPreviewDisplay(mUI.getSurfaceHolder().getSurface());
1094 }
1095 }
1096
1097 // Prepares media recorder.
1098 private void initializeRecorder() {
1099 Log.v(TAG, "initializeRecorder");
1100 // If the mCameraDevice is null, then this activity is going to finish
1101 if (mCameraDevice == null) return;
1102
1103 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
1104 // Set the SurfaceView to visible so the surface gets created.
1105 // surfaceCreated() is called immediately when the visibility is
1106 // changed to visible. Thus, mSurfaceViewReady should become true
1107 // right after calling setVisibility().
1108 mUI.showSurfaceView();
1109 }
1110
1111 Intent intent = mActivity.getIntent();
1112 Bundle myExtras = intent.getExtras();
1113
1114 long requestedSizeLimit = 0;
1115 closeVideoFileDescriptor();
1116 if (mIsVideoCaptureIntent && myExtras != null) {
1117 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1118 if (saveUri != null) {
1119 try {
1120 mVideoFileDescriptor =
1121 mContentResolver.openFileDescriptor(saveUri, "rw");
1122 mCurrentVideoUri = saveUri;
1123 } catch (java.io.FileNotFoundException ex) {
1124 // invalid uri
1125 Log.e(TAG, ex.toString());
1126 }
1127 }
1128 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1129 }
1130 mMediaRecorder = new MediaRecorder();
1131
1132 setupMediaRecorderPreviewDisplay();
1133 // Unlock the camera object before passing it to media recorder.
1134 mCameraDevice.unlock();
1135 mCameraDevice.waitDone();
1136 mMediaRecorder.setCamera(mCameraDevice.getCamera());
1137 if (!mCaptureTimeLapse) {
1138 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
1139 }
1140 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
1141 mMediaRecorder.setProfile(mProfile);
1142 mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
1143 if (mCaptureTimeLapse) {
1144 double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs;
1145 setCaptureRate(mMediaRecorder, fps);
1146 }
1147
1148 setRecordLocation();
1149
1150 // Set output file.
1151 // Try Uri in the intent first. If it doesn't exist, use our own
1152 // instead.
1153 if (mVideoFileDescriptor != null) {
1154 mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1155 } else {
1156 generateVideoFilename(mProfile.fileFormat);
1157 mMediaRecorder.setOutputFile(mVideoFilename);
1158 }
1159
1160 // Set maximum file size.
1161 long maxFileSize = mActivity.getStorageSpace() - Storage.LOW_STORAGE_THRESHOLD;
1162 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1163 maxFileSize = requestedSizeLimit;
1164 }
1165
1166 try {
1167 mMediaRecorder.setMaxFileSize(maxFileSize);
1168 } catch (RuntimeException exception) {
1169 // We are going to ignore failure of setMaxFileSize here, as
1170 // a) The composer selected may simply not support it, or
1171 // b) The underlying media framework may not handle 64-bit range
1172 // on the size restriction.
1173 }
1174
1175 // See android.hardware.Camera.Parameters.setRotation for
1176 // documentation.
1177 // Note that mOrientation here is the device orientation, which is the opposite of
1178 // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
1179 // which is the orientation the graphics need to rotate in order to render correctly.
1180 int rotation = 0;
1181 if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1182 CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1183 if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
1184 rotation = (info.orientation - mOrientation + 360) % 360;
1185 } else { // back-facing camera
1186 rotation = (info.orientation + mOrientation) % 360;
1187 }
1188 }
1189 mMediaRecorder.setOrientationHint(rotation);
1190
1191 try {
1192 mMediaRecorder.prepare();
1193 } catch (IOException e) {
1194 Log.e(TAG, "prepare failed for " + mVideoFilename, e);
1195 releaseMediaRecorder();
1196 throw new RuntimeException(e);
1197 }
1198
1199 mMediaRecorder.setOnErrorListener(this);
1200 mMediaRecorder.setOnInfoListener(this);
1201 }
1202
1203 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
1204 private static void setCaptureRate(MediaRecorder recorder, double fps) {
1205 recorder.setCaptureRate(fps);
1206 }
1207
1208 @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
1209 private void setRecordLocation() {
1210 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
1211 Location loc = mLocationManager.getCurrentLocation();
1212 if (loc != null) {
1213 mMediaRecorder.setLocation((float) loc.getLatitude(),
1214 (float) loc.getLongitude());
1215 }
1216 }
1217 }
1218
1219 private void initializeEffectsPreview() {
1220 Log.v(TAG, "initializeEffectsPreview");
1221 // If the mCameraDevice is null, then this activity is going to finish
1222 if (mCameraDevice == null) return;
1223
1224 boolean inLandscape = (mActivity.getResources().getConfiguration().orientation
1225 == Configuration.ORIENTATION_LANDSCAPE);
1226
1227 CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1228
1229 mEffectsDisplayResult = false;
1230 mEffectsRecorder = new EffectsRecorder(mActivity);
1231
1232 // TODO: Confirm none of the following need to go to initializeEffectsRecording()
1233 // and none of these change even when the preview is not refreshed.
1234 mEffectsRecorder.setCameraDisplayOrientation(mCameraDisplayOrientation);
1235 mEffectsRecorder.setCamera(mCameraDevice);
1236 mEffectsRecorder.setCameraFacing(info.facing);
1237 mEffectsRecorder.setProfile(mProfile);
1238 mEffectsRecorder.setEffectsListener(this);
1239 mEffectsRecorder.setOnInfoListener(this);
1240 mEffectsRecorder.setOnErrorListener(this);
1241
1242 // The input of effects recorder is affected by
1243 // android.hardware.Camera.setDisplayOrientation. Its value only
1244 // compensates the camera orientation (no Display.getRotation). So the
1245 // orientation hint here should only consider sensor orientation.
1246 int orientation = 0;
1247 if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1248 orientation = mOrientation;
1249 }
1250 mEffectsRecorder.setOrientationHint(orientation);
1251
1252 mEffectsRecorder.setPreviewSurfaceTexture(mUI.getSurfaceTexture(),
1253 mUI.getPreviewWidth(), mUI.getPreviewHeight());
1254
1255 if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER &&
1256 ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) {
1257 mEffectsRecorder.setEffect(mEffectType, mEffectUriFromGallery);
1258 } else {
1259 mEffectsRecorder.setEffect(mEffectType, mEffectParameter);
1260 }
1261 }
1262
1263 private void initializeEffectsRecording() {
1264 Log.v(TAG, "initializeEffectsRecording");
1265
1266 Intent intent = mActivity.getIntent();
1267 Bundle myExtras = intent.getExtras();
1268
1269 long requestedSizeLimit = 0;
1270 closeVideoFileDescriptor();
1271 if (mIsVideoCaptureIntent && myExtras != null) {
1272 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1273 if (saveUri != null) {
1274 try {
1275 mVideoFileDescriptor =
1276 mContentResolver.openFileDescriptor(saveUri, "rw");
1277 mCurrentVideoUri = saveUri;
1278 } catch (java.io.FileNotFoundException ex) {
1279 // invalid uri
1280 Log.e(TAG, ex.toString());
1281 }
1282 }
1283 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1284 }
1285
1286 mEffectsRecorder.setProfile(mProfile);
1287 // important to set the capture rate to zero if not timelapsed, since the
1288 // effectsrecorder object does not get created again for each recording
1289 // session
1290 if (mCaptureTimeLapse) {
1291 mEffectsRecorder.setCaptureRate((1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs));
1292 } else {
1293 mEffectsRecorder.setCaptureRate(0);
1294 }
1295
1296 // Set output file
1297 if (mVideoFileDescriptor != null) {
1298 mEffectsRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1299 } else {
1300 generateVideoFilename(mProfile.fileFormat);
1301 mEffectsRecorder.setOutputFile(mVideoFilename);
1302 }
1303
1304 // Set maximum file size.
1305 long maxFileSize = mActivity.getStorageSpace() - Storage.LOW_STORAGE_THRESHOLD;
1306 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1307 maxFileSize = requestedSizeLimit;
1308 }
1309 mEffectsRecorder.setMaxFileSize(maxFileSize);
1310 mEffectsRecorder.setMaxDuration(mMaxVideoDurationInMs);
1311 }
1312
1313
1314 private void releaseMediaRecorder() {
1315 Log.v(TAG, "Releasing media recorder.");
1316 if (mMediaRecorder != null) {
1317 cleanupEmptyFile();
1318 mMediaRecorder.reset();
1319 mMediaRecorder.release();
1320 mMediaRecorder = null;
1321 }
1322 mVideoFilename = null;
1323 }
1324
1325 private void releaseEffectsRecorder() {
1326 Log.v(TAG, "Releasing effects recorder.");
1327 if (mEffectsRecorder != null) {
1328 cleanupEmptyFile();
1329 mEffectsRecorder.release();
1330 mEffectsRecorder = null;
1331 }
1332 mEffectType = EffectsRecorder.EFFECT_NONE;
1333 mVideoFilename = null;
1334 }
1335
1336 private void generateVideoFilename(int outputFileFormat) {
1337 long dateTaken = System.currentTimeMillis();
1338 String title = createName(dateTaken);
1339 // Used when emailing.
1340 String filename = title + convertOutputFormatToFileExt(outputFileFormat);
1341 String mime = convertOutputFormatToMimeType(outputFileFormat);
1342 String path = Storage.DIRECTORY + '/' + filename;
1343 String tmpPath = path + ".tmp";
Doris Liu29a8d5b2013-06-03 15:20:17 -07001344 mCurrentVideoValues = new ContentValues(9);
Doris Liu7234f4d2013-05-14 14:37:46 -07001345 mCurrentVideoValues.put(Video.Media.TITLE, title);
1346 mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
1347 mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
Doris Liu29a8d5b2013-06-03 15:20:17 -07001348 mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000);
Doris Liu7234f4d2013-05-14 14:37:46 -07001349 mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
1350 mCurrentVideoValues.put(Video.Media.DATA, path);
1351 mCurrentVideoValues.put(Video.Media.RESOLUTION,
1352 Integer.toString(mProfile.videoFrameWidth) + "x" +
1353 Integer.toString(mProfile.videoFrameHeight));
1354 Location loc = mLocationManager.getCurrentLocation();
1355 if (loc != null) {
1356 mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
1357 mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
1358 }
Doris Liu7234f4d2013-05-14 14:37:46 -07001359 mVideoFilename = tmpPath;
1360 Log.v(TAG, "New video filename: " + mVideoFilename);
1361 }
1362
Doris Liu29a8d5b2013-06-03 15:20:17 -07001363 private void saveVideo() {
Doris Liu7234f4d2013-05-14 14:37:46 -07001364 if (mVideoFileDescriptor == null) {
Doris Liu7234f4d2013-05-14 14:37:46 -07001365 long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
1366 if (duration > 0) {
1367 if (mCaptureTimeLapse) {
1368 duration = getTimeLapseVideoLength(duration);
1369 }
Doris Liu7234f4d2013-05-14 14:37:46 -07001370 } else {
1371 Log.w(TAG, "Video duration <= 0 : " + duration);
1372 }
Doris Liu29a8d5b2013-06-03 15:20:17 -07001373 mActivity.getMediaSaveService().addVideo(mCurrentVideoFilename,
1374 duration, mCurrentVideoValues,
1375 mOnVideoSavedListener, mContentResolver);
Doris Liu7234f4d2013-05-14 14:37:46 -07001376 }
1377 mCurrentVideoValues = null;
Doris Liu7234f4d2013-05-14 14:37:46 -07001378 }
1379
1380 private void deleteVideoFile(String fileName) {
1381 Log.v(TAG, "Deleting video " + fileName);
1382 File f = new File(fileName);
1383 if (!f.delete()) {
1384 Log.v(TAG, "Could not delete " + fileName);
1385 }
1386 }
1387
1388 private PreferenceGroup filterPreferenceScreenByIntent(
1389 PreferenceGroup screen) {
1390 Intent intent = mActivity.getIntent();
1391 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
1392 CameraSettings.removePreferenceFromScreen(screen,
1393 CameraSettings.KEY_VIDEO_QUALITY);
1394 }
1395
1396 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
1397 CameraSettings.removePreferenceFromScreen(screen,
1398 CameraSettings.KEY_VIDEO_QUALITY);
1399 }
1400 return screen;
1401 }
1402
1403 // from MediaRecorder.OnErrorListener
1404 @Override
1405 public void onError(MediaRecorder mr, int what, int extra) {
1406 Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
1407 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1408 // We may have run out of space on the sdcard.
1409 stopVideoRecording();
1410 mActivity.updateStorageSpaceAndHint();
1411 }
1412 }
1413
1414 // from MediaRecorder.OnInfoListener
1415 @Override
1416 public void onInfo(MediaRecorder mr, int what, int extra) {
1417 if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
1418 if (mMediaRecorderRecording) onStopVideoRecording();
1419 } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
1420 if (mMediaRecorderRecording) onStopVideoRecording();
1421
1422 // Show the toast.
1423 Toast.makeText(mActivity, R.string.video_reach_size_limit,
1424 Toast.LENGTH_LONG).show();
1425 }
1426 }
1427
1428 /*
1429 * Make sure we're not recording music playing in the background, ask the
1430 * MediaPlaybackService to pause playback.
1431 */
1432 private void pauseAudioPlayback() {
1433 // Shamelessly copied from MediaPlaybackService.java, which
1434 // should be public, but isn't.
1435 Intent i = new Intent("com.android.music.musicservicecommand");
1436 i.putExtra("command", "pause");
1437
1438 mActivity.sendBroadcast(i);
1439 }
1440
1441 // For testing.
1442 public boolean isRecording() {
1443 return mMediaRecorderRecording;
1444 }
1445
1446 private void startVideoRecording() {
1447 Log.v(TAG, "startVideoRecording");
Doris Liu29a8d5b2013-06-03 15:20:17 -07001448 mUI.enablePreviewThumb(false);
Doris Liuf7758b62013-06-06 16:54:18 -07001449 mUI.setSwipingEnabled(false);
Doris Liu7234f4d2013-05-14 14:37:46 -07001450
1451 mActivity.updateStorageSpaceAndHint();
1452 if (mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) {
1453 Log.v(TAG, "Storage issue, ignore the start request");
1454 return;
1455 }
1456
Doris Liu29a8d5b2013-06-03 15:20:17 -07001457 if (!mCameraDevice.waitDone()) return;
Doris Liu7234f4d2013-05-14 14:37:46 -07001458 mCurrentVideoUri = null;
1459 if (effectsActive()) {
1460 initializeEffectsRecording();
1461 if (mEffectsRecorder == null) {
1462 Log.e(TAG, "Fail to initialize effect recorder");
1463 return;
1464 }
1465 } else {
1466 initializeRecorder();
1467 if (mMediaRecorder == null) {
1468 Log.e(TAG, "Fail to initialize media recorder");
1469 return;
1470 }
1471 }
1472
1473 pauseAudioPlayback();
1474
1475 if (effectsActive()) {
1476 try {
1477 mEffectsRecorder.startRecording();
1478 } catch (RuntimeException e) {
1479 Log.e(TAG, "Could not start effects recorder. ", e);
1480 releaseEffectsRecorder();
1481 return;
1482 }
1483 } else {
1484 try {
1485 mMediaRecorder.start(); // Recording is now started
1486 } catch (RuntimeException e) {
1487 Log.e(TAG, "Could not start media recorder. ", e);
1488 releaseMediaRecorder();
1489 // If start fails, frameworks will not lock the camera for us.
1490 mCameraDevice.lock();
1491 return;
1492 }
1493 }
1494
1495 // Make sure the video recording has started before announcing
1496 // this in accessibility.
1497 AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
1498 mActivity.getString(R.string.video_recording_started));
1499
1500 // The parameters might have been altered by MediaRecorder already.
1501 // We need to force mCameraDevice to refresh before getting it.
1502 mCameraDevice.refreshParameters();
1503 // The parameters may have been changed by MediaRecorder upon starting
1504 // recording. We need to alter the parameters if we support camcorder
1505 // zoom. To reduce latency when setting the parameters during zoom, we
1506 // update mParameters here once.
1507 if (ApiHelper.HAS_ZOOM_WHEN_RECORDING) {
1508 mParameters = mCameraDevice.getParameters();
1509 }
1510
1511 mUI.enableCameraControls(false);
1512
1513 mMediaRecorderRecording = true;
1514 mOrientationManager.lockOrientation();
1515 mRecordingStartTime = SystemClock.uptimeMillis();
1516 mUI.showRecordingUI(true, mParameters.isZoomSupported());
1517
1518 updateRecordingTime();
1519 keepScreenOn();
1520 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1521 UsageStatistics.ACTION_CAPTURE_START, "Video");
1522 }
1523
1524 private void showCaptureResult() {
1525 mIsInReviewMode = true;
1526 Bitmap bitmap = null;
1527 if (mVideoFileDescriptor != null) {
1528 bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
1529 mDesiredPreviewWidth);
1530 } else if (mCurrentVideoFilename != null) {
1531 bitmap = Thumbnail.createVideoThumbnailBitmap(mCurrentVideoFilename,
1532 mDesiredPreviewWidth);
1533 }
1534 if (bitmap != null) {
1535 // MetadataRetriever already rotates the thumbnail. We should rotate
1536 // it to match the UI orientation (and mirror if it is front-facing camera).
1537 CameraInfo[] info = CameraHolder.instance().getCameraInfo();
1538 boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT);
1539 bitmap = Util.rotateAndMirror(bitmap, 0, mirror);
1540 mUI.showReviewImage(bitmap);
1541 }
1542
1543 mUI.showReviewControls();
1544 mUI.enableCameraControls(false);
1545 mUI.showTimeLapseUI(false);
1546 }
1547
1548 private void hideAlert() {
1549 mUI.enableCameraControls(true);
1550 mUI.hideReviewUI();
1551 if (mCaptureTimeLapse) {
1552 mUI.showTimeLapseUI(true);
1553 }
1554 }
1555
1556 private boolean stopVideoRecording() {
1557 Log.v(TAG, "stopVideoRecording");
Doris Liuf7758b62013-06-06 16:54:18 -07001558 mUI.setSwipingEnabled(true);
Doris Liu7234f4d2013-05-14 14:37:46 -07001559 mUI.showSwitcher();
1560
1561 boolean fail = false;
1562 if (mMediaRecorderRecording) {
1563 boolean shouldAddToMediaStoreNow = false;
1564
1565 try {
1566 if (effectsActive()) {
1567 // This is asynchronous, so we can't add to media store now because thumbnail
Doris Liu29a8d5b2013-06-03 15:20:17 -07001568 // may not be ready. In such case saveVideo() is called later
Doris Liu7234f4d2013-05-14 14:37:46 -07001569 // through a callback from the MediaEncoderFilter to EffectsRecorder,
1570 // and then to the VideoModule.
1571 mEffectsRecorder.stopRecording();
1572 } else {
1573 mMediaRecorder.setOnErrorListener(null);
1574 mMediaRecorder.setOnInfoListener(null);
1575 mMediaRecorder.stop();
1576 shouldAddToMediaStoreNow = true;
1577 }
1578 mCurrentVideoFilename = mVideoFilename;
1579 Log.v(TAG, "stopVideoRecording: Setting current video filename: "
1580 + mCurrentVideoFilename);
1581 AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
1582 mActivity.getString(R.string.video_recording_stopped));
1583 } catch (RuntimeException e) {
1584 Log.e(TAG, "stop fail", e);
1585 if (mVideoFilename != null) deleteVideoFile(mVideoFilename);
1586 fail = true;
1587 }
1588 mMediaRecorderRecording = false;
1589 mOrientationManager.unlockOrientation();
1590
1591 // If the activity is paused, this means activity is interrupted
1592 // during recording. Release the camera as soon as possible because
1593 // face unlock or other applications may need to use the camera.
1594 // However, if the effects are active, then we can only release the
1595 // camera and cannot release the effects recorder since that will
1596 // stop the graph. It is possible to separate out the Camera release
1597 // part and the effects release part. However, the effects recorder
1598 // does hold on to the camera, hence, it needs to be "disconnected"
1599 // from the camera in the closeCamera call.
1600 if (mPaused) {
1601 // Closing only the camera part if effects active. Effects will
1602 // be closed in the callback from effects.
1603 boolean closeEffects = !effectsActive();
1604 closeCamera(closeEffects);
1605 }
1606
1607 mUI.showRecordingUI(false, mParameters.isZoomSupported());
1608 if (!mIsVideoCaptureIntent) {
1609 mUI.enableCameraControls(true);
1610 }
1611 // The orientation was fixed during video recording. Now make it
1612 // reflect the device orientation as video recording is stopped.
1613 mUI.setOrientationIndicator(0, true);
1614 keepScreenOnAwhile();
1615 if (shouldAddToMediaStoreNow) {
Doris Liu29a8d5b2013-06-03 15:20:17 -07001616 saveVideo();
Doris Liu7234f4d2013-05-14 14:37:46 -07001617 }
1618 }
1619 // always release media recorder if no effects running
1620 if (!effectsActive()) {
1621 releaseMediaRecorder();
1622 if (!mPaused) {
1623 mCameraDevice.lock();
1624 mCameraDevice.waitDone();
1625 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
1626 stopPreview();
1627 mUI.hideSurfaceView();
1628 // Switch back to use SurfaceTexture for preview.
1629 startPreview();
1630 }
1631 }
1632 }
1633 // Update the parameters here because the parameters might have been altered
1634 // by MediaRecorder.
1635 if (!mPaused) mParameters = mCameraDevice.getParameters();
1636 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1637 fail ? UsageStatistics.ACTION_CAPTURE_FAIL :
1638 UsageStatistics.ACTION_CAPTURE_DONE, "Video",
1639 SystemClock.uptimeMillis() - mRecordingStartTime);
1640 return fail;
1641 }
1642
1643 private void resetScreenOn() {
1644 mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1645 mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1646 }
1647
1648 private void keepScreenOnAwhile() {
1649 mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1650 mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1651 mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1652 }
1653
1654 private void keepScreenOn() {
1655 mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1656 mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1657 }
1658
1659 private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
1660 long seconds = milliSeconds / 1000; // round down to compute seconds
1661 long minutes = seconds / 60;
1662 long hours = minutes / 60;
1663 long remainderMinutes = minutes - (hours * 60);
1664 long remainderSeconds = seconds - (minutes * 60);
1665
1666 StringBuilder timeStringBuilder = new StringBuilder();
1667
1668 // Hours
1669 if (hours > 0) {
1670 if (hours < 10) {
1671 timeStringBuilder.append('0');
1672 }
1673 timeStringBuilder.append(hours);
1674
1675 timeStringBuilder.append(':');
1676 }
1677
1678 // Minutes
1679 if (remainderMinutes < 10) {
1680 timeStringBuilder.append('0');
1681 }
1682 timeStringBuilder.append(remainderMinutes);
1683 timeStringBuilder.append(':');
1684
1685 // Seconds
1686 if (remainderSeconds < 10) {
1687 timeStringBuilder.append('0');
1688 }
1689 timeStringBuilder.append(remainderSeconds);
1690
1691 // Centi seconds
1692 if (displayCentiSeconds) {
1693 timeStringBuilder.append('.');
1694 long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
1695 if (remainderCentiSeconds < 10) {
1696 timeStringBuilder.append('0');
1697 }
1698 timeStringBuilder.append(remainderCentiSeconds);
1699 }
1700
1701 return timeStringBuilder.toString();
1702 }
1703
1704 private long getTimeLapseVideoLength(long deltaMs) {
1705 // For better approximation calculate fractional number of frames captured.
1706 // This will update the video time at a higher resolution.
1707 double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
1708 return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
1709 }
1710
1711 private void updateRecordingTime() {
1712 if (!mMediaRecorderRecording) {
1713 return;
1714 }
1715 long now = SystemClock.uptimeMillis();
1716 long delta = now - mRecordingStartTime;
1717
1718 // Starting a minute before reaching the max duration
1719 // limit, we'll countdown the remaining time instead.
1720 boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1721 && delta >= mMaxVideoDurationInMs - 60000);
1722
1723 long deltaAdjusted = delta;
1724 if (countdownRemainingTime) {
1725 deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
1726 }
1727 String text;
1728
1729 long targetNextUpdateDelay;
1730 if (!mCaptureTimeLapse) {
1731 text = millisecondToTimeString(deltaAdjusted, false);
1732 targetNextUpdateDelay = 1000;
1733 } else {
1734 // The length of time lapse video is different from the length
1735 // of the actual wall clock time elapsed. Display the video length
1736 // only in format hh:mm:ss.dd, where dd are the centi seconds.
1737 text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
1738 targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
1739 }
1740
1741 mUI.setRecordingTime(text);
1742
1743 if (mRecordingTimeCountsDown != countdownRemainingTime) {
1744 // Avoid setting the color on every update, do it only
1745 // when it needs changing.
1746 mRecordingTimeCountsDown = countdownRemainingTime;
1747
1748 int color = mActivity.getResources().getColor(countdownRemainingTime
1749 ? R.color.recording_time_remaining_text
1750 : R.color.recording_time_elapsed_text);
1751
1752 mUI.setRecordingTimeTextColor(color);
1753 }
1754
1755 long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
1756 mHandler.sendEmptyMessageDelayed(
1757 UPDATE_RECORD_TIME, actualNextUpdateDelay);
1758 }
1759
1760 private static boolean isSupported(String value, List<String> supported) {
1761 return supported == null ? false : supported.indexOf(value) >= 0;
1762 }
1763
1764 @SuppressWarnings("deprecation")
1765 private void setCameraParameters() {
1766 mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
Angus Kongd82eae22013-06-11 12:00:15 -07001767 int[] fpsRange = Util.getMaxPreviewFpsRange(mParameters);
1768 if (fpsRange.length > 0) {
1769 mParameters.setPreviewFpsRange(
1770 fpsRange[Parameters.PREVIEW_FPS_MIN_INDEX],
1771 fpsRange[Parameters.PREVIEW_FPS_MAX_INDEX]);
1772 } else {
1773 mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
1774 }
Doris Liu7234f4d2013-05-14 14:37:46 -07001775
1776 // Set flash mode.
1777 String flashMode;
1778 if (mUI.isVisible()) {
1779 flashMode = mPreferences.getString(
1780 CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
1781 mActivity.getString(R.string.pref_camera_video_flashmode_default));
1782 } else {
1783 flashMode = Parameters.FLASH_MODE_OFF;
1784 }
1785 List<String> supportedFlash = mParameters.getSupportedFlashModes();
1786 if (isSupported(flashMode, supportedFlash)) {
1787 mParameters.setFlashMode(flashMode);
1788 } else {
1789 flashMode = mParameters.getFlashMode();
1790 if (flashMode == null) {
1791 flashMode = mActivity.getString(
1792 R.string.pref_camera_flashmode_no_flash);
1793 }
1794 }
1795
1796 // Set white balance parameter.
1797 String whiteBalance = mPreferences.getString(
1798 CameraSettings.KEY_WHITE_BALANCE,
1799 mActivity.getString(R.string.pref_camera_whitebalance_default));
1800 if (isSupported(whiteBalance,
1801 mParameters.getSupportedWhiteBalance())) {
1802 mParameters.setWhiteBalance(whiteBalance);
1803 } else {
1804 whiteBalance = mParameters.getWhiteBalance();
1805 if (whiteBalance == null) {
1806 whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1807 }
1808 }
1809
1810 // Set zoom.
1811 if (mParameters.isZoomSupported()) {
1812 mParameters.setZoom(mZoomValue);
1813 }
1814
1815 // Set continuous autofocus.
1816 List<String> supportedFocus = mParameters.getSupportedFocusModes();
1817 if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
1818 mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
1819 }
1820
1821 mParameters.set(Util.RECORDING_HINT, Util.TRUE);
1822
1823 // Enable video stabilization. Convenience methods not available in API
1824 // level <= 14
1825 String vstabSupported = mParameters.get("video-stabilization-supported");
1826 if ("true".equals(vstabSupported)) {
1827 mParameters.set("video-stabilization", "true");
1828 }
1829
1830 // Set picture size.
1831 // The logic here is different from the logic in still-mode camera.
1832 // There we determine the preview size based on the picture size, but
1833 // here we determine the picture size based on the preview size.
1834 List<Size> supported = mParameters.getSupportedPictureSizes();
1835 Size optimalSize = Util.getOptimalVideoSnapshotPictureSize(supported,
1836 (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
1837 Size original = mParameters.getPictureSize();
1838 if (!original.equals(optimalSize)) {
1839 mParameters.setPictureSize(optimalSize.width, optimalSize.height);
1840 }
1841 Log.v(TAG, "Video snapshot size is " + optimalSize.width + "x" +
1842 optimalSize.height);
1843
1844 // Set JPEG quality.
1845 int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1846 CameraProfile.QUALITY_HIGH);
1847 mParameters.setJpegQuality(jpegQuality);
1848
1849 mCameraDevice.setParameters(mParameters);
1850 // Keep preview size up to date.
1851 mParameters = mCameraDevice.getParameters();
1852 }
1853
1854 @Override
1855 public void onActivityResult(int requestCode, int resultCode, Intent data) {
1856 switch (requestCode) {
1857 case REQUEST_EFFECT_BACKDROPPER:
1858 if (resultCode == Activity.RESULT_OK) {
1859 // onActivityResult() runs before onResume(), so this parameter will be
1860 // seen by startPreview from onResume()
1861 mEffectUriFromGallery = data.getData().toString();
1862 Log.v(TAG, "Received URI from gallery: " + mEffectUriFromGallery);
1863 mResetEffect = false;
1864 } else {
1865 mEffectUriFromGallery = null;
1866 Log.w(TAG, "No URI from gallery");
1867 mResetEffect = true;
1868 }
1869 break;
1870 }
1871 }
1872
1873 @Override
1874 public void onEffectsUpdate(int effectId, int effectMsg) {
1875 Log.v(TAG, "onEffectsUpdate. Effect Message = " + effectMsg);
1876 if (effectMsg == EffectsRecorder.EFFECT_MSG_EFFECTS_STOPPED) {
1877 // Effects have shut down. Hide learning message if any,
1878 // and restart regular preview.
1879 checkQualityAndStartPreview();
1880 } else if (effectMsg == EffectsRecorder.EFFECT_MSG_RECORDING_DONE) {
1881 // This follows the codepath from onStopVideoRecording.
Doris Liu29a8d5b2013-06-03 15:20:17 -07001882 if (mEffectsDisplayResult) {
1883 saveVideo();
Doris Liu7234f4d2013-05-14 14:37:46 -07001884 if (mIsVideoCaptureIntent) {
1885 if (mQuickCapture) {
1886 doReturnToCaller(true);
1887 } else {
1888 showCaptureResult();
1889 }
1890 }
1891 }
1892 mEffectsDisplayResult = false;
1893 // In onPause, these were not called if the effects were active. We
1894 // had to wait till the effects recording is complete to do this.
1895 if (mPaused) {
1896 closeVideoFileDescriptor();
Doris Liu7234f4d2013-05-14 14:37:46 -07001897 }
1898 } else if (effectMsg == EffectsRecorder.EFFECT_MSG_PREVIEW_RUNNING) {
1899 // Enable the shutter button once the preview is complete.
1900 mUI.enableShutter(true);
1901 }
1902 // In onPause, this was not called if the effects were active. We had to
1903 // wait till the effects completed to do this.
1904 if (mPaused) {
1905 Log.v(TAG, "OnEffectsUpdate: closing effects if activity paused");
1906 closeEffects();
1907 }
1908 }
1909
1910 public void onCancelBgTraining(View v) {
1911 // Write default effect out to shared prefs
1912 writeDefaultEffectToPrefs();
1913 // Tell VideoCamer to re-init based on new shared pref values.
1914 onSharedPreferenceChanged();
1915 }
1916
1917 @Override
1918 public synchronized void onEffectsError(Exception exception, String fileName) {
1919 // TODO: Eventually we may want to show the user an error dialog, and then restart the
1920 // camera and encoder gracefully. For now, we just delete the file and bail out.
1921 if (fileName != null && new File(fileName).exists()) {
1922 deleteVideoFile(fileName);
1923 }
1924 try {
1925 if (Class.forName("android.filterpacks.videosink.MediaRecorderStopException")
1926 .isInstance(exception)) {
1927 Log.w(TAG, "Problem recoding video file. Removing incomplete file.");
1928 return;
1929 }
1930 } catch (ClassNotFoundException ex) {
1931 Log.w(TAG, ex);
1932 }
1933 throw new RuntimeException("Error during recording!", exception);
1934 }
1935
1936 @Override
1937 public void onConfigurationChanged(Configuration newConfig) {
1938 Log.v(TAG, "onConfigurationChanged");
1939 setDisplayOrientation();
1940 }
1941
1942 @Override
1943 public void onOverriddenPreferencesClicked() {
1944 }
1945
1946 @Override
1947 // TODO: Delete this after old camera code is removed
1948 public void onRestorePreferencesClicked() {
1949 }
1950
1951 private boolean effectsActive() {
1952 return (mEffectType != EffectsRecorder.EFFECT_NONE);
1953 }
1954
1955 @Override
1956 public void onSharedPreferenceChanged() {
1957 // ignore the events after "onPause()" or preview has not started yet
1958 if (mPaused) return;
1959 synchronized (mPreferences) {
1960 // If mCameraDevice is not ready then we can set the parameter in
1961 // startPreview().
1962 if (mCameraDevice == null) return;
1963
1964 boolean recordLocation = RecordLocationPreference.get(
1965 mPreferences, mContentResolver);
1966 mLocationManager.recordLocation(recordLocation);
1967
1968 // Check if the current effects selection has changed
1969 if (updateEffectSelection()) return;
1970
1971 readVideoPreferences();
1972 mUI.showTimeLapseUI(mCaptureTimeLapse);
1973 // We need to restart the preview if preview size is changed.
1974 Size size = mParameters.getPreviewSize();
1975 if (size.width != mDesiredPreviewWidth
1976 || size.height != mDesiredPreviewHeight) {
1977 if (!effectsActive()) {
1978 stopPreview();
1979 } else {
1980 mEffectsRecorder.release();
1981 mEffectsRecorder = null;
1982 }
1983 resizeForPreviewAspectRatio();
1984 startPreview(); // Parameters will be set in startPreview().
1985 } else {
1986 setCameraParameters();
1987 }
Doris Liu29a8d5b2013-06-03 15:20:17 -07001988 mUI.updateOnScreenIndicators(mParameters, mPreferences);
Doris Liu7234f4d2013-05-14 14:37:46 -07001989 }
1990 }
1991
1992 protected void setCameraId(int cameraId) {
1993 ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID);
1994 pref.setValue("" + cameraId);
1995 }
1996
1997 private void switchCamera() {
1998 if (mPaused) return;
1999
2000 Log.d(TAG, "Start to switch camera.");
2001 mCameraId = mPendingSwitchCameraId;
2002 mPendingSwitchCameraId = -1;
2003 setCameraId(mCameraId);
2004
2005 closeCamera();
2006 mUI.collapseCameraControls();
2007 // Restart the camera and initialize the UI. From onCreate.
2008 mPreferences.setLocalId(mActivity, mCameraId);
2009 CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
2010 openCamera();
2011 readVideoPreferences();
2012 startPreview();
2013 initializeVideoSnapshot();
2014 resizeForPreviewAspectRatio();
2015 initializeVideoControl();
2016
2017 // From onResume
2018 mUI.initializeZoom(mParameters);
2019 mUI.setOrientationIndicator(0, false);
2020
2021 // Start switch camera animation. Post a message because
2022 // onFrameAvailable from the old camera may already exist.
2023 mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
Doris Liu29a8d5b2013-06-03 15:20:17 -07002024 mUI.updateOnScreenIndicators(mParameters, mPreferences);
Doris Liu7234f4d2013-05-14 14:37:46 -07002025 }
2026
2027 // Preview texture has been copied. Now camera can be released and the
2028 // animation can be started.
2029 @Override
2030 public void onPreviewTextureCopied() {
2031 mHandler.sendEmptyMessage(SWITCH_CAMERA);
2032 }
2033
2034 @Override
2035 public void onCaptureTextureCopied() {
2036 }
2037
2038 private boolean updateEffectSelection() {
2039 int previousEffectType = mEffectType;
2040 Object previousEffectParameter = mEffectParameter;
2041 mEffectType = CameraSettings.readEffectType(mPreferences);
2042 mEffectParameter = CameraSettings.readEffectParameter(mPreferences);
2043
2044 if (mEffectType == previousEffectType) {
2045 if (mEffectType == EffectsRecorder.EFFECT_NONE) return false;
2046 if (mEffectParameter.equals(previousEffectParameter)) return false;
2047 }
2048 Log.v(TAG, "New effect selection: " + mPreferences.getString(
2049 CameraSettings.KEY_VIDEO_EFFECT, "none"));
2050
2051 if (mEffectType == EffectsRecorder.EFFECT_NONE) {
2052 // Stop effects and return to normal preview
2053 mEffectsRecorder.stopPreview();
2054 mPreviewing = false;
2055 return true;
2056 }
2057 if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER &&
2058 ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) {
2059 // Request video from gallery to use for background
2060 Intent i = new Intent(Intent.ACTION_PICK);
2061 i.setDataAndType(Video.Media.EXTERNAL_CONTENT_URI,
2062 "video/*");
2063 i.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
2064 mActivity.startActivityForResult(i, REQUEST_EFFECT_BACKDROPPER);
2065 return true;
2066 }
2067 if (previousEffectType == EffectsRecorder.EFFECT_NONE) {
2068 // Stop regular preview and start effects.
2069 stopPreview();
2070 checkQualityAndStartPreview();
2071 } else {
2072 // Switch currently running effect
2073 mEffectsRecorder.setEffect(mEffectType, mEffectParameter);
2074 }
2075 return true;
2076 }
2077
2078 // Verifies that the current preview view size is correct before starting
2079 // preview. If not, resets the surface texture and resizes the view.
2080 private void checkQualityAndStartPreview() {
2081 readVideoPreferences();
2082 mUI.showTimeLapseUI(mCaptureTimeLapse);
2083 Size size = mParameters.getPreviewSize();
2084 if (size.width != mDesiredPreviewWidth
2085 || size.height != mDesiredPreviewHeight) {
2086 resizeForPreviewAspectRatio();
2087 }
2088 // Start up preview again
2089 startPreview();
2090 }
2091
Doris Liu7234f4d2013-05-14 14:37:46 -07002092 private void initializeVideoSnapshot() {
2093 if (mParameters == null) return;
2094 if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
2095 // Show the tap to focus toast if this is the first start.
2096 if (mPreferences.getBoolean(
2097 CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, true)) {
2098 // Delay the toast for one second to wait for orientation.
2099 mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_SNAPSHOT_TOAST, 1000);
2100 }
2101 }
2102 }
2103
2104 void showVideoSnapshotUI(boolean enabled) {
2105 if (mParameters == null) return;
2106 if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
2107 if (enabled) {
2108 // TODO: ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation);
2109 } else {
2110 mUI.showPreviewBorder(enabled);
2111 }
2112 mUI.enableShutter(!enabled);
2113 }
2114 }
2115
2116 @Override
2117 public void updateCameraAppView() {
2118 if (!mPreviewing || mParameters.getFlashMode() == null) return;
2119
2120 // When going to and back from gallery, we need to turn off/on the flash.
2121 if (!mUI.isVisible()) {
2122 if (mParameters.getFlashMode().equals(Parameters.FLASH_MODE_OFF)) {
2123 mRestoreFlash = false;
2124 return;
2125 }
2126 mRestoreFlash = true;
2127 setCameraParameters();
2128 } else if (mRestoreFlash) {
2129 mRestoreFlash = false;
2130 setCameraParameters();
2131 }
2132 }
2133
2134 @Override
2135 public void onFullScreenChanged(boolean full) {
2136 mUI.onFullScreenChanged(full);
2137 }
2138
2139 private final class JpegPictureCallback implements PictureCallback {
2140 Location mLocation;
2141
2142 public JpegPictureCallback(Location loc) {
2143 mLocation = loc;
2144 }
2145
2146 @Override
2147 public void onPictureTaken(byte [] jpegData, android.hardware.Camera camera) {
2148 Log.v(TAG, "onPictureTaken");
2149 mSnapshotInProgress = false;
2150 showVideoSnapshotUI(false);
2151 storeImage(jpegData, mLocation);
2152 }
2153 }
2154
2155 private void storeImage(final byte[] data, Location loc) {
2156 long dateTaken = System.currentTimeMillis();
2157 String title = Util.createJpegName(dateTaken);
2158 ExifInterface exif = Exif.getExif(data);
2159 int orientation = Exif.getOrientation(exif);
2160 Size s = mParameters.getPictureSize();
2161 mActivity.getMediaSaveService().addImage(
2162 data, title, dateTaken, loc, s.width, s.height, orientation,
Doris Liu29a8d5b2013-06-03 15:20:17 -07002163 exif, mOnPhotoSavedListener, mContentResolver);
Doris Liu7234f4d2013-05-14 14:37:46 -07002164 }
2165
2166 private boolean resetEffect() {
2167 if (mResetEffect) {
2168 String value = mPreferences.getString(CameraSettings.KEY_VIDEO_EFFECT,
2169 mPrefVideoEffectDefault);
2170 if (!mPrefVideoEffectDefault.equals(value)) {
2171 writeDefaultEffectToPrefs();
2172 return true;
2173 }
2174 }
2175 mResetEffect = true;
2176 return false;
2177 }
2178
2179 private String convertOutputFormatToMimeType(int outputFileFormat) {
2180 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
2181 return "video/mp4";
2182 }
2183 return "video/3gpp";
2184 }
2185
2186 private String convertOutputFormatToFileExt(int outputFileFormat) {
2187 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
2188 return ".mp4";
2189 }
2190 return ".3gp";
2191 }
2192
2193 private void closeVideoFileDescriptor() {
2194 if (mVideoFileDescriptor != null) {
2195 try {
2196 mVideoFileDescriptor.close();
2197 } catch (IOException e) {
2198 Log.e(TAG, "Fail to close fd", e);
2199 }
2200 mVideoFileDescriptor = null;
2201 }
2202 }
2203
2204 private void showTapToSnapshotToast() {
2205 new RotateTextToast(mActivity, R.string.video_snapshot_hint, 0)
2206 .show();
2207 // Clear the preference.
2208 Editor editor = mPreferences.edit();
2209 editor.putBoolean(CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, false);
2210 editor.apply();
2211 }
2212
Doris Liu7234f4d2013-05-14 14:37:46 -07002213 @Override
2214 public boolean updateStorageHintOnResume() {
2215 return true;
2216 }
2217
2218 // required by OnPreferenceChangedListener
2219 @Override
2220 public void onCameraPickerClicked(int cameraId) {
2221 if (mPaused || mPendingSwitchCameraId != -1) return;
2222
2223 mPendingSwitchCameraId = cameraId;
2224 Log.d(TAG, "Start to copy texture.");
2225 // We need to keep a preview frame for the animation before
2226 // releasing the camera. This will trigger onPreviewTextureCopied.
2227 // TODO: ((CameraScreenNail) mActivity.mCameraScreenNail).copyTexture();
2228 // Disable all camera controls.
2229 mSwitchingCamera = true;
2230
2231 }
2232
2233 @Override
2234 public boolean needsSwitcher() {
2235 return !mIsVideoCaptureIntent;
2236 }
2237
2238 @Override
2239 public boolean needsPieMenu() {
2240 return true;
2241 }
2242
2243 @Override
2244 public void onShowSwitcherPopup() {
2245 mUI.onShowSwitcherPopup();
2246 }
2247
2248 @Override
2249 public void onMediaSaveServiceConnected(MediaSaveService s) {
2250 // do nothing.
2251 }
2252}