blob: 70cc12ec6c4c718858b7ee98f90c263251842919 [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;
Michael Kolb8872c232013-01-29 10:33:22 -080028import android.graphics.Bitmap;
Angus Kong5f8c30e2014-03-06 17:15:08 -080029import android.graphics.Point;
Michael Kolb8872c232013-01-29 10:33:22 -080030import android.graphics.SurfaceTexture;
31import android.hardware.Camera.CameraInfo;
32import android.hardware.Camera.Parameters;
Michael Kolb8872c232013-01-29 10:33:22 -080033import android.hardware.Camera.Size;
34import android.location.Location;
Marco Nelissen20694b22013-10-29 15:27:24 -070035import android.media.AudioManager;
Michael Kolb8872c232013-01-29 10:33:22 -080036import 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;
Michael Kolb8872c232013-01-29 10:33:22 -080049import android.view.KeyEvent;
Michael Kolb8872c232013-01-29 10:33:22 -080050import android.view.OrientationEventListener;
Michael Kolb8872c232013-01-29 10:33:22 -080051import android.view.View;
Michael Kolb8872c232013-01-29 10:33:22 -080052import android.widget.Toast;
53
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080054import com.android.camera.app.AppController;
Spike Sprague39f8a762014-01-13 14:22:18 -080055import com.android.camera.app.CameraAppUI;
Doris Liua1ec04a2014-01-13 17:29:40 -080056import com.android.camera.app.CameraManager;
Angus Kong20fad242013-11-11 18:23:46 -080057import com.android.camera.app.CameraManager.CameraPictureCallback;
58import com.android.camera.app.CameraManager.CameraProxy;
Kevin Gabayanffbc43c2013-12-09 11:41:50 -080059import com.android.camera.app.LocationManager;
Angus Kongfd4fc0e2013-11-07 15:38:09 -080060import com.android.camera.app.MediaSaver;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080061import com.android.camera.app.MemoryManager;
62import com.android.camera.app.MemoryManager.MemoryListener;
Angus Kong5596b4c2014-03-11 16:27:30 -070063import com.android.camera.debug.Log;
ztenghuia16e7b52013-08-23 11:47:56 -070064import com.android.camera.exif.ExifInterface;
Erin Dahlgrenb1641f52014-01-14 15:58:52 -080065import com.android.camera.hardware.HardwareSpec;
66import com.android.camera.hardware.HardwareSpecImpl;
Angus Kong20fad242013-11-11 18:23:46 -080067import com.android.camera.module.ModuleController;
Erin Dahlgrenbd3da262013-12-02 11:48:13 -080068import com.android.camera.settings.SettingsManager;
Sascha Haeberlingde303232014-02-07 02:30:53 +010069import com.android.camera.settings.SettingsUtil;
Sascha Haeberling638e6f02013-09-18 14:28:51 -070070import com.android.camera.util.ApiHelper;
Angus Kongb50b5cb2013-08-09 14:55:20 -070071import com.android.camera.util.CameraUtil;
Sascha Haeberling8e963a52013-08-06 11:43:02 -070072import com.android.camera.util.UsageStatistics;
73import com.android.camera2.R;
Seth Raphael5e09d012013-12-18 13:45:03 -080074import com.google.common.logging.eventprotos;
Michael Kolb8872c232013-01-29 10:33:22 -080075
Angus Kongb50b5cb2013-08-09 14:55:20 -070076import java.io.File;
77import java.io.IOException;
78import java.text.SimpleDateFormat;
79import java.util.Date;
80import java.util.Iterator;
81import java.util.List;
82
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080083public class VideoModule extends CameraModule
84 implements ModuleController,
85 VideoController,
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080086 MemoryListener,
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080087 MediaRecorder.OnErrorListener,
Doris Liua1ec04a2014-01-13 17:29:40 -080088 MediaRecorder.OnInfoListener, FocusOverlayManager.Listener {
Michael Kolb8872c232013-01-29 10:33:22 -080089
Angus Kong5596b4c2014-03-11 16:27:30 -070090 private static final Log.Tag TAG = new Log.Tag("VideoModule");
Michael Kolb8872c232013-01-29 10:33:22 -080091
Angus Kong13e87c42013-11-25 10:02:47 -080092 // Messages defined for the UI thread handler.
93 private static final int MSG_CHECK_DISPLAY_ROTATION = 4;
94 private static final int MSG_UPDATE_RECORD_TIME = 5;
95 private static final int MSG_ENABLE_SHUTTER_BUTTON = 6;
Angus Kong13e87c42013-11-25 10:02:47 -080096 private static final int MSG_SWITCH_CAMERA = 8;
97 private static final int MSG_SWITCH_CAMERA_START_ANIMATION = 9;
Michael Kolb8872c232013-01-29 10:33:22 -080098
99 private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
100
101 /**
102 * An unpublished intent flag requesting to start recording straight away
103 * and return as soon as recording is stopped.
104 * TODO: consider publishing by moving into MediaStore.
105 */
106 private static final String EXTRA_QUICK_CAPTURE =
107 "android.intent.extra.quickCapture";
108
Michael Kolb8872c232013-01-29 10:33:22 -0800109 // module fields
110 private CameraActivity mActivity;
Michael Kolb8872c232013-01-29 10:33:22 -0800111 private boolean mPaused;
Spike Sprague51c877c2014-02-18 11:14:12 -0800112
113 // if, during and intent capture, the activity is paused (e.g. when app switching or reviewing a
114 // shot video), we don't want the bottom bar intent ui to reset to the capture button
115 private boolean mDontResetIntentUiOnResume;
116
Michael Kolb8872c232013-01-29 10:33:22 -0800117 private int mCameraId;
118 private Parameters mParameters;
119
Doris Liu6432cd62013-06-13 17:20:31 -0700120 private boolean mIsInReviewMode;
Michael Kolb8872c232013-01-29 10:33:22 -0800121 private boolean mSnapshotInProgress = false;
122
Michael Kolb8872c232013-01-29 10:33:22 -0800123 private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
124
Angus Kong395ee2d2013-07-15 12:42:41 -0700125 // Preference must be read before starting preview. We check this before starting
126 // preview.
127 private boolean mPreferenceRead;
Michael Kolb8872c232013-01-29 10:33:22 -0800128
Michael Kolb8872c232013-01-29 10:33:22 -0800129 private boolean mIsVideoCaptureIntent;
130 private boolean mQuickCapture;
131
132 private MediaRecorder mMediaRecorder;
Michael Kolb8872c232013-01-29 10:33:22 -0800133
134 private boolean mSwitchingCamera;
135 private boolean mMediaRecorderRecording = false;
136 private long mRecordingStartTime;
137 private boolean mRecordingTimeCountsDown = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800138 private long mOnResumeTime;
139 // The video file that the hardware camera is about to record into
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800140 // (or is recording into.
Michael Kolb8872c232013-01-29 10:33:22 -0800141 private String mVideoFilename;
142 private ParcelFileDescriptor mVideoFileDescriptor;
143
144 // The video file that has already been recorded, and that is being
145 // examined by the user.
146 private String mCurrentVideoFilename;
147 private Uri mCurrentVideoUri;
ztenghui70bd0242013-10-14 11:15:44 -0700148 private boolean mCurrentVideoUriFromMediaSaved;
Michael Kolb8872c232013-01-29 10:33:22 -0800149 private ContentValues mCurrentVideoValues;
150
151 private CamcorderProfile mProfile;
152
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800153 // The video duration limit. 0 means no limit.
Michael Kolb8872c232013-01-29 10:33:22 -0800154 private int mMaxVideoDurationInMs;
155
156 // Time Lapse parameters.
Sascha Haeberling8c000c22014-03-17 13:56:05 -0700157 private final boolean mCaptureTimeLapse = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800158 // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
Sascha Haeberling8c000c22014-03-17 13:56:05 -0700159 private final int mTimeBetweenTimeLapseFrameCaptureMs = 0;
Michael Kolb8872c232013-01-29 10:33:22 -0800160
161 boolean mPreviewing = false; // True if preview is started.
162 // The display rotation in degrees. This is only valid when mPreviewing is
163 // true.
164 private int mDisplayRotation;
165 private int mCameraDisplayOrientation;
Doris Liu2b906b82013-12-10 16:34:08 -0800166 private AppController mAppController;
Michael Kolb8872c232013-01-29 10:33:22 -0800167
Doris Liu6827ce22013-03-12 19:24:28 -0700168 private int mDesiredPreviewWidth;
169 private int mDesiredPreviewHeight;
Michael Kolb8872c232013-01-29 10:33:22 -0800170 private ContentResolver mContentResolver;
171
172 private LocationManager mLocationManager;
173
Michael Kolb8872c232013-01-29 10:33:22 -0800174 private int mPendingSwitchCameraId;
Michael Kolb8872c232013-01-29 10:33:22 -0800175 private final Handler mHandler = new MainHandler();
Doris Liu6827ce22013-03-12 19:24:28 -0700176 private VideoUI mUI;
Doris Liu6432cd62013-06-13 17:20:31 -0700177 private CameraProxy mCameraDevice;
178
Michael Kolb8872c232013-01-29 10:33:22 -0800179 // The degrees of the device rotated clockwise from its natural orientation.
180 private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
181
182 private int mZoomValue; // The current zoom value.
Doris Liu6827ce22013-03-12 19:24:28 -0700183
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800184 private final MediaSaver.OnMediaSavedListener mOnVideoSavedListener =
185 new MediaSaver.OnMediaSavedListener() {
Angus Kong83a99ae2013-04-17 15:37:07 -0700186 @Override
187 public void onMediaSaved(Uri uri) {
188 if (uri != null) {
Doris Liu2a7f44c2013-08-12 15:18:53 -0700189 mCurrentVideoUri = uri;
ztenghui70bd0242013-10-14 11:15:44 -0700190 mCurrentVideoUriFromMediaSaved = true;
Doris Liu2a7f44c2013-08-12 15:18:53 -0700191 onVideoSaved();
Sascha Haeberling37f36112013-08-06 14:31:52 -0700192 mActivity.notifyNewMedia(uri);
Angus Kong83a99ae2013-04-17 15:37:07 -0700193 }
194 }
195 };
196
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800197 private final MediaSaver.OnMediaSavedListener mOnPhotoSavedListener =
198 new MediaSaver.OnMediaSavedListener() {
Angus Kong86d36312013-01-31 18:22:44 -0800199 @Override
200 public void onMediaSaved(Uri uri) {
201 if (uri != null) {
Sascha Haeberling37f36112013-08-06 14:31:52 -0700202 mActivity.notifyNewMedia(uri);
Angus Kong86d36312013-01-31 18:22:44 -0800203 }
204 }
205 };
Doris Liua1ec04a2014-01-13 17:29:40 -0800206 private FocusOverlayManager mFocusManager;
207 private boolean mMirror;
208 private Parameters mInitialParams;
209 private boolean mFocusAreaSupported;
210 private boolean mMeteringAreaSupported;
211
212 private final CameraManager.CameraAFCallback mAutoFocusCallback =
213 new CameraManager.CameraAFCallback() {
214 @Override
215 public void onAutoFocus(boolean focused, CameraProxy camera) {
216 if (mPaused) {
217 return;
218 }
219 mFocusManager.onAutoFocus(focused, false);
220 }
221 };
222
223 private final Object mAutoFocusMoveCallback =
224 ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
225 ? new CameraManager.CameraAFMoveCallback() {
226 @Override
227 public void onAutoFocusMoving(boolean moving, CameraProxy camera) {
228 mFocusManager.onAutoFocusMoving(moving);
229 }
230 } : null;
Angus Kong86d36312013-01-31 18:22:44 -0800231
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800232 /**
233 * This Handler is used to post message back onto the main thread of the
234 * application.
235 */
Michael Kolb8872c232013-01-29 10:33:22 -0800236 private class MainHandler extends Handler {
237 @Override
238 public void handleMessage(Message msg) {
239 switch (msg.what) {
240
Angus Kong13e87c42013-11-25 10:02:47 -0800241 case MSG_ENABLE_SHUTTER_BUTTON:
Erin Dahlgren667630d2014-04-01 14:03:25 -0700242 mAppController.setShutterEnabled(true);
Michael Kolb8872c232013-01-29 10:33:22 -0800243 break;
244
Angus Kong13e87c42013-11-25 10:02:47 -0800245 case MSG_UPDATE_RECORD_TIME: {
Michael Kolb8872c232013-01-29 10:33:22 -0800246 updateRecordingTime();
247 break;
248 }
249
Angus Kong13e87c42013-11-25 10:02:47 -0800250 case MSG_CHECK_DISPLAY_ROTATION: {
Michael Kolb8872c232013-01-29 10:33:22 -0800251 // Restart the preview if display rotation has changed.
252 // Sometimes this happens when the device is held upside
253 // down and camera app is opened. Rotation animation will
254 // take some time and the rotation value we have got may be
255 // wrong. Framework does not have a callback for this now.
Angus Kongb50b5cb2013-08-09 14:55:20 -0700256 if ((CameraUtil.getDisplayRotation(mActivity) != mDisplayRotation)
Michael Kolb8872c232013-01-29 10:33:22 -0800257 && !mMediaRecorderRecording && !mSwitchingCamera) {
258 startPreview();
259 }
260 if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
Angus Kong13e87c42013-11-25 10:02:47 -0800261 mHandler.sendEmptyMessageDelayed(MSG_CHECK_DISPLAY_ROTATION, 100);
Michael Kolb8872c232013-01-29 10:33:22 -0800262 }
263 break;
264 }
265
Angus Kong13e87c42013-11-25 10:02:47 -0800266 case MSG_SWITCH_CAMERA: {
Michael Kolb8872c232013-01-29 10:33:22 -0800267 switchCamera();
268 break;
269 }
270
Angus Kong13e87c42013-11-25 10:02:47 -0800271 case MSG_SWITCH_CAMERA_START_ANIMATION: {
Doris Liu6432cd62013-06-13 17:20:31 -0700272 //TODO:
273 //((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
Michael Kolb8872c232013-01-29 10:33:22 -0800274
275 // Enable all camera controls.
276 mSwitchingCamera = false;
277 break;
278 }
279
Michael Kolb8872c232013-01-29 10:33:22 -0800280 default:
281 Log.v(TAG, "Unhandled message: " + msg.what);
282 break;
283 }
284 }
285 }
286
287 private BroadcastReceiver mReceiver = null;
288
289 private class MyBroadcastReceiver extends BroadcastReceiver {
290 @Override
291 public void onReceive(Context context, Intent intent) {
292 String action = intent.getAction();
293 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
294 stopVideoRecording();
295 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
296 Toast.makeText(mActivity,
297 mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
298 }
299 }
300 }
301
Spike Sprague41f68002014-01-24 16:59:25 -0800302 private int mShutterIconId;
303
304
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800305 /**
306 * Construct a new video module.
307 */
Angus Kongc4e66562013-11-22 23:03:21 -0800308 public VideoModule(AppController app) {
309 super(app);
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800310 }
311
Michael Kolb8872c232013-01-29 10:33:22 -0800312 private String createName(long dateTaken) {
313 Date date = new Date(dateTaken);
314 SimpleDateFormat dateFormat = new SimpleDateFormat(
315 mActivity.getString(R.string.video_file_name_format));
316
317 return dateFormat.format(date);
318 }
319
Michael Kolb8872c232013-01-29 10:33:22 -0800320 @Override
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100321 public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
322 mActivity = activity;
323 // TODO: Need to look at the controller interface to see if we can get
324 // rid of passing in the activity directly.
325 mAppController = mActivity;
326 mUI = new VideoUI(mActivity, this, mActivity.getModuleLayoutRoot());
327 mActivity.setPreviewStatusListener(mUI);
Michael Kolb8872c232013-01-29 10:33:22 -0800328
Erin Dahlgrenbd3da262013-12-02 11:48:13 -0800329 SettingsManager settingsManager = mActivity.getSettingsManager();
330 mCameraId = Integer.parseInt(settingsManager.get(SettingsManager.SETTING_CAMERA_ID));
Michael Kolb8872c232013-01-29 10:33:22 -0800331
Spike Spraguee6374b72014-04-25 17:24:32 -0700332 mActivity.updateStorageSpaceAndHint(null);
333
Michael Kolb8872c232013-01-29 10:33:22 -0800334 /*
335 * To reduce startup time, we start the preview in another thread.
336 * We make sure the preview is started at the end of onCreate.
337 */
Angus Kong20fad242013-11-11 18:23:46 -0800338 requestCamera(mCameraId);
Michael Kolb8872c232013-01-29 10:33:22 -0800339
340 mContentResolver = mActivity.getContentResolver();
341
Michael Kolb8872c232013-01-29 10:33:22 -0800342 // Surface texture is from camera screen nail and startPreview needs it.
343 // This must be done before startPreview.
344 mIsVideoCaptureIntent = isVideoCaptureIntent();
Michael Kolb8872c232013-01-29 10:33:22 -0800345
Michael Kolb8872c232013-01-29 10:33:22 -0800346 mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
Erin Dahlgren21c21a62013-11-19 16:37:38 -0800347 mLocationManager = mActivity.getLocationManager();
Michael Kolb8872c232013-01-29 10:33:22 -0800348
Doris Liu6827ce22013-03-12 19:24:28 -0700349 mUI.setOrientationIndicator(0, false);
Michael Kolb8872c232013-01-29 10:33:22 -0800350 setDisplayOrientation();
351
Doris Liu6827ce22013-03-12 19:24:28 -0700352 mUI.showTimeLapseUI(mCaptureTimeLapse);
Michael Kolb8872c232013-01-29 10:33:22 -0800353 mPendingSwitchCameraId = -1;
Spike Sprague41f68002014-01-24 16:59:25 -0800354
355 mShutterIconId = CameraUtil.getCameraShutterIconId(
356 mAppController.getCurrentModuleIndex(), mAppController.getAndroidContext());
Doris Liu6827ce22013-03-12 19:24:28 -0700357 }
358
Erin Dahlgren4efa8b52013-12-17 18:31:35 -0800359 @Override
360 public boolean isUsingBottomBar() {
361 return true;
362 }
363
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800364 private void initializeControlByIntent() {
365 if (isVideoCaptureIntent()) {
Spike Sprague51c877c2014-02-18 11:14:12 -0800366 if (!mDontResetIntentUiOnResume) {
367 mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
368 }
369 // reset the flag
370 mDontResetIntentUiOnResume = false;
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800371 }
372 }
373
Doris Liu6827ce22013-03-12 19:24:28 -0700374 @Override
375 public void onSingleTapUp(View view, int x, int y) {
Doris Liu057b21a2014-04-25 17:35:27 -0700376 if (mPaused || mCameraDevice == null) {
377 return;
378 }
379 if (mMediaRecorderRecording) {
380 if (!mSnapshotInProgress) {
381 takeASnapshot();
382 }
Doris Liua1ec04a2014-01-13 17:29:40 -0800383 return;
384 }
385 // Check if metering area or focus area is supported.
386 if (!mFocusAreaSupported && !mMeteringAreaSupported) {
387 return;
388 }
389 // Tap to focus.
390 mFocusManager.onSingleTapUp(x, y);
Doris Liu38605742013-08-13 15:01:52 -0700391 }
392
393 private void takeASnapshot() {
Sascha Haeberlinga514b142013-08-13 16:10:01 -0700394 // Only take snapshots if video snapshot is supported by device
Doris Liu38605742013-08-13 15:01:52 -0700395 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
Erin Dahlgren667630d2014-04-01 14:03:25 -0700396 if (!mMediaRecorderRecording || mPaused || mSnapshotInProgress
Doris Liu057b21a2014-04-25 17:35:27 -0700397 || !mAppController.isShutterEnabled()) {
Doris Liu38605742013-08-13 15:01:52 -0700398 return;
399 }
400
401 // Set rotation and gps data.
Sascha Haeberlinga7cbfc02014-02-14 11:06:03 +0100402 CameraInfo info = mActivity.getCameraProvider().getCameraInfo()[mCameraId];
403 int rotation = CameraUtil.getJpegRotation(info, mOrientation);
Doris Liu38605742013-08-13 15:01:52 -0700404 mParameters.setRotation(rotation);
405 Location loc = mLocationManager.getCurrentLocation();
406 CameraUtil.setGpsParameters(mParameters, loc);
407 mCameraDevice.setParameters(mParameters);
408
Doris Liu057b21a2014-04-25 17:35:27 -0700409 Log.i(TAG, "Video snapshot start");
Doris Liu38605742013-08-13 15:01:52 -0700410 mCameraDevice.takePicture(mHandler,
411 null, null, null, new JpegPictureCallback(loc));
412 showVideoSnapshotUI(true);
413 mSnapshotInProgress = true;
Doris Liu6827ce22013-03-12 19:24:28 -0700414 }
Michael Kolb8872c232013-01-29 10:33:22 -0800415 }
416
Doris Liua1ec04a2014-01-13 17:29:40 -0800417 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
418 private void updateAutoFocusMoveCallback() {
Sascha Haeberling8793eff2014-01-15 16:33:59 -0800419 if (mPaused) {
420 return;
421 }
422
Doris Liua1ec04a2014-01-13 17:29:40 -0800423 if (mParameters.getFocusMode().equals(CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE)) {
424 mCameraDevice.setAutoFocusMoveCallback(mHandler,
425 (CameraManager.CameraAFMoveCallback) mAutoFocusMoveCallback);
426 } else {
427 mCameraDevice.setAutoFocusMoveCallback(null, null);
428 }
429 }
430
431 /**
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700432 * @return Whether the currently active camera is front-facing.
433 */
434 private boolean isCameraFrontFacing() {
435 CameraInfo info = mAppController.getCameraProvider().getCameraInfo()[mCameraId];
436 return info.facing == CameraInfo.CAMERA_FACING_FRONT;
437 }
438
439 /**
Doris Liua1ec04a2014-01-13 17:29:40 -0800440 * The focus manager gets initialized after camera is available.
441 */
442 private void initializeFocusManager() {
443 // Create FocusManager object. startPreview needs it.
444 // if mFocusManager not null, reuse it
445 // otherwise create a new instance
446 if (mFocusManager != null) {
447 mFocusManager.removeMessages();
448 } else {
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700449 mMirror = isCameraFrontFacing();
Doris Liua1ec04a2014-01-13 17:29:40 -0800450 String[] defaultFocusModes = mActivity.getResources().getStringArray(
451 R.array.pref_camera_focusmode_default_array);
452 mFocusManager = new FocusOverlayManager(mActivity.getSettingsManager(),
453 defaultFocusModes,
454 mInitialParams, this, mMirror,
455 mActivity.getMainLooper(), mUI.getFocusUI());
456 }
457 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
458 }
459
Michael Kolb8872c232013-01-29 10:33:22 -0800460 @Override
461 public void onOrientationChanged(int orientation) {
462 // We keep the last known orientation. So if the user first orient
463 // the camera then point the camera to floor or sky, we still have
464 // the correct orientation.
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100465 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
466 return;
467 }
Angus Kongb50b5cb2013-08-09 14:55:20 -0700468 int newOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800469
470 if (mOrientation != newOrientation) {
471 mOrientation = newOrientation;
Michael Kolb8872c232013-01-29 10:33:22 -0800472 }
473
Michael Kolb8872c232013-01-29 10:33:22 -0800474 }
475
Erin Dahlgren0a6a8d82014-01-09 22:17:38 -0800476 private final ButtonManager.ButtonCallback mFlashCallback =
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -0800477 new ButtonManager.ButtonCallback() {
478 @Override
479 public void onStateChanged(int state) {
480 // Update flash parameters.
481 enableTorchMode(true);
482 }
483 };
484
Erin Dahlgren0a6a8d82014-01-09 22:17:38 -0800485 private final ButtonManager.ButtonCallback mCameraCallback =
Erin Dahlgren18e2ef62013-12-05 14:53:38 -0800486 new ButtonManager.ButtonCallback() {
487 @Override
488 public void onStateChanged(int state) {
Angus Kong97e282a2014-03-04 18:44:49 -0800489 if (mPaused || mAppController.getCameraProvider().waitingForCamera()) {
Erin Dahlgren18e2ef62013-12-05 14:53:38 -0800490 return;
491 }
492 mPendingSwitchCameraId = state;
493 Log.d(TAG, "Start to copy texture.");
494
495 // Disable all camera controls.
496 mSwitchingCamera = true;
497 switchCamera();
498 }
499 };
500
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800501 private final View.OnClickListener mCancelCallback = new View.OnClickListener() {
502 @Override
503 public void onClick(View v) {
504 onReviewCancelClicked(v);
505 }
506 };
507
508 private final View.OnClickListener mDoneCallback = new View.OnClickListener() {
509 @Override
510 public void onClick(View v) {
511 onReviewDoneClicked(v);
512 }
513 };
514 private final View.OnClickListener mReviewCallback = new View.OnClickListener() {
515 @Override
516 public void onClick(View v) {
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800517 onReviewPlayClicked(v);
518 }
519 };
520
Angus Kong20fad242013-11-11 18:23:46 -0800521 @Override
Erin Dahlgren1ca516f2014-03-28 12:44:04 -0700522 public void hardResetSettings(SettingsManager settingsManager) {
523 // VideoModule does not need to hard reset any settings.
524 }
525
526 @Override
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800527 public HardwareSpec getHardwareSpec() {
Erin Dahlgren49ab9222014-01-28 17:40:28 -0800528 return (mParameters != null ? new HardwareSpecImpl(mParameters) : null);
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800529 }
530
531 @Override
532 public CameraAppUI.BottomBarUISpec getBottomBarSpec() {
533 CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
534
535 bottomBarSpec.enableCamera = true;
536 bottomBarSpec.cameraCallback = mCameraCallback;
537 bottomBarSpec.enableTorchFlash = true;
538 bottomBarSpec.flashCallback = mFlashCallback;
539 bottomBarSpec.hideHdr = true;
Spike Spragueab3adde2014-03-10 12:19:08 -0700540 bottomBarSpec.enableGridLines = true;
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800541
542 if (isVideoCaptureIntent()) {
543 bottomBarSpec.showCancel = true;
544 bottomBarSpec.cancelCallback = mCancelCallback;
545 bottomBarSpec.showDone = true;
546 bottomBarSpec.doneCallback = mDoneCallback;
547 bottomBarSpec.showReview = true;
548 bottomBarSpec.reviewCallback = mReviewCallback;
549 }
550
551 return bottomBarSpec;
Erin Dahlgren0a6a8d82014-01-09 22:17:38 -0800552 }
553
554 @Override
Angus Kong20fad242013-11-11 18:23:46 -0800555 public void onCameraAvailable(CameraProxy cameraProxy) {
556 mCameraDevice = cameraProxy;
Doris Liua1ec04a2014-01-13 17:29:40 -0800557 mInitialParams = mCameraDevice.getParameters();
558 mFocusAreaSupported = CameraUtil.isFocusAreaSupported(mInitialParams);
559 mMeteringAreaSupported = CameraUtil.isMeteringAreaSupported(mInitialParams);
Angus Kong20fad242013-11-11 18:23:46 -0800560 readVideoPreferences();
561 resizeForPreviewAspectRatio();
Doris Liua1ec04a2014-01-13 17:29:40 -0800562 initializeFocusManager();
Doris Liu5dd85a12014-04-15 11:45:55 -0700563 // TODO: Having focus overlay manager caching the parameters is prone to error,
564 // we should consider passing the parameters to focus overlay to ensure the
565 // parameters are up to date.
566 mFocusManager.setParameters(mInitialParams);
Doris Liu5a367542014-01-17 17:21:42 -0800567
Angus Kong20fad242013-11-11 18:23:46 -0800568 startPreview();
569 initializeVideoSnapshot();
Angus Kong20fad242013-11-11 18:23:46 -0800570 mUI.initializeZoom(mParameters);
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800571 initializeControlByIntent();
Angus Kong20fad242013-11-11 18:23:46 -0800572 }
573
Michael Kolb8872c232013-01-29 10:33:22 -0800574 private void startPlayVideoActivity() {
575 Intent intent = new Intent(Intent.ACTION_VIEW);
576 intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
577 try {
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700578 mActivity
579 .startActivityForResult(intent, CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
Michael Kolb8872c232013-01-29 10:33:22 -0800580 } catch (ActivityNotFoundException ex) {
581 Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
582 }
583 }
584
ztenghui7b265a62013-09-09 14:58:44 -0700585 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800586 @OnClickAttr
587 public void onReviewPlayClicked(View v) {
588 startPlayVideoActivity();
589 }
590
ztenghui7b265a62013-09-09 14:58:44 -0700591 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800592 @OnClickAttr
593 public void onReviewDoneClicked(View v) {
Doris Liu69ef5ea2013-05-07 13:48:10 -0700594 mIsInReviewMode = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800595 doReturnToCaller(true);
596 }
597
ztenghui7b265a62013-09-09 14:58:44 -0700598 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800599 @OnClickAttr
600 public void onReviewCancelClicked(View v) {
ztenghuiaf3a1972013-09-17 16:51:15 -0700601 // TODO: It should be better to not even insert the URI at all before we
602 // confirm done in review, which means we need to handle temporary video
603 // files in a quite different way than we currently had.
ztenghui70bd0242013-10-14 11:15:44 -0700604 // Make sure we don't delete the Uri sent from the video capture intent.
605 if (mCurrentVideoUriFromMediaSaved) {
ztenghuidfe5b152013-10-08 17:07:15 -0700606 mContentResolver.delete(mCurrentVideoUri, null, null);
607 }
ztenghui638bf9a2013-10-09 10:52:33 -0700608 mIsInReviewMode = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800609 doReturnToCaller(false);
610 }
611
Doris Liu69ef5ea2013-05-07 13:48:10 -0700612 @Override
613 public boolean isInReviewMode() {
614 return mIsInReviewMode;
615 }
616
Michael Kolb8872c232013-01-29 10:33:22 -0800617 private void onStopVideoRecording() {
Sascha Haeberling8793eff2014-01-15 16:33:59 -0800618 mAppController.getCameraAppUI().setSwipeEnabled(true);
Michael Kolb8872c232013-01-29 10:33:22 -0800619 boolean recordFail = stopVideoRecording();
620 if (mIsVideoCaptureIntent) {
Doris Liu3973deb2013-08-21 13:42:22 -0700621 if (mQuickCapture) {
622 doReturnToCaller(!recordFail);
623 } else if (!recordFail) {
624 showCaptureResult();
Michael Kolb8872c232013-01-29 10:33:22 -0800625 }
626 } else if (!recordFail){
627 // Start capture animation.
628 if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
629 // The capture animation is disabled on ICS because we use SurfaceView
630 // for preview during recording. When the recording is done, we switch
631 // back to use SurfaceTexture for preview and we need to stop then start
632 // the preview. This will cause the preview flicker since the preview
633 // will not be continuous for a short period of time.
Sascha Haeberling4f91ab52013-05-21 11:26:13 -0700634
Sascha Haeberling37f36112013-08-06 14:31:52 -0700635 mUI.animateFlash();
Michael Kolb8872c232013-01-29 10:33:22 -0800636 }
637 }
638 }
639
Doris Liu2a7f44c2013-08-12 15:18:53 -0700640 public void onVideoSaved() {
641 if (mIsVideoCaptureIntent) {
642 showCaptureResult();
643 }
644 }
645
Michael Kolb8872c232013-01-29 10:33:22 -0800646 public void onProtectiveCurtainClick(View v) {
647 // Consume clicks
648 }
649
650 @Override
651 public void onShutterButtonClick() {
Sascha Haeberling8793eff2014-01-15 16:33:59 -0800652 if (mSwitchingCamera) {
653 return;
654 }
Michael Kolb8872c232013-01-29 10:33:22 -0800655 boolean stop = mMediaRecorderRecording;
656
657 if (stop) {
658 onStopVideoRecording();
659 } else {
660 startVideoRecording();
661 }
Erin Dahlgren667630d2014-04-01 14:03:25 -0700662 mAppController.setShutterEnabled(false);
Doris Liua1ec04a2014-01-13 17:29:40 -0800663 mFocusManager.onShutterUp();
Michael Kolb8872c232013-01-29 10:33:22 -0800664
665 // Keep the shutter button disabled when in video capture intent
666 // mode and recording is stopped. It'll be re-enabled when
667 // re-take button is clicked.
668 if (!(mIsVideoCaptureIntent && stop)) {
Angus Kong13e87c42013-11-25 10:02:47 -0800669 mHandler.sendEmptyMessageDelayed(MSG_ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
Michael Kolb8872c232013-01-29 10:33:22 -0800670 }
671 }
672
673 @Override
674 public void onShutterButtonFocus(boolean pressed) {
Doris Liuf55f3c42013-11-20 00:24:46 -0800675 // TODO: Remove this when old camera controls are removed from the UI.
Michael Kolb8872c232013-01-29 10:33:22 -0800676 }
677
678 private void readVideoPreferences() {
679 // The preference stores values from ListPreference and is thus string type for all values.
680 // We need to convert it to int manually.
Erin Dahlgrenbd3da262013-12-02 11:48:13 -0800681 SettingsManager settingsManager = mActivity.getSettingsManager();
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700682 if (!settingsManager.isSet(SettingsManager.SETTING_VIDEO_QUALITY_BACK)) {
683 settingsManager.setDefault(SettingsManager.SETTING_VIDEO_QUALITY_BACK);
Doris Liu3f7e0042013-07-31 11:25:09 -0700684 }
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700685 if (!settingsManager.isSet(SettingsManager.SETTING_VIDEO_QUALITY_FRONT)) {
686 settingsManager.setDefault(SettingsManager.SETTING_VIDEO_QUALITY_FRONT);
687 }
688 String videoQuality = settingsManager
689 .get(isCameraFrontFacing() ? SettingsManager.SETTING_VIDEO_QUALITY_FRONT
690 : SettingsManager.SETTING_VIDEO_QUALITY_BACK);
Sascha Haeberlingde303232014-02-07 02:30:53 +0100691 int quality = SettingsUtil.getVideoQuality(videoQuality, mCameraId);
692 Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality);
Michael Kolb8872c232013-01-29 10:33:22 -0800693
694 // Set video quality.
695 Intent intent = mActivity.getIntent();
696 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
697 int extraVideoQuality =
698 intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
699 if (extraVideoQuality > 0) {
700 quality = CamcorderProfile.QUALITY_HIGH;
701 } else { // 0 is mms.
702 quality = CamcorderProfile.QUALITY_LOW;
703 }
704 }
705
706 // Set video duration limit. The limit is read from the preference,
707 // unless it is specified in the intent.
708 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
709 int seconds =
710 intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
711 mMaxVideoDurationInMs = 1000 * seconds;
712 } else {
Sascha Haeberling4044ab72014-04-02 16:25:03 -0700713 mMaxVideoDurationInMs = SettingsManager.getMaxVideoDuration(mActivity
714 .getAndroidContext());
Michael Kolb8872c232013-01-29 10:33:22 -0800715 }
716
Sascha Haeberling8c000c22014-03-17 13:56:05 -0700717 // TODO: Uncomment this block to re-enable time-lapse.
718 /* // Read time lapse recording interval.
Erin Dahlgrenbd3da262013-12-02 11:48:13 -0800719 String frameIntervalStr = settingsManager.get(
720 SettingsManager.SETTING_VIDEO_TIME_LAPSE_FRAME_INTERVAL);
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700721 mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
722 mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
Michael Kolb8872c232013-01-29 10:33:22 -0800723 // TODO: This should be checked instead directly +1000.
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100724 if (mCaptureTimeLapse) {
725 quality += 1000;
Sascha Haeberling8c000c22014-03-17 13:56:05 -0700726 } */
Andy Huibers329e1012014-02-04 08:03:35 -0800727
728 // If quality is not supported, request QUALITY_HIGH which is always supported.
729 if (CamcorderProfile.hasProfile(mCameraId, quality) == false) {
730 quality = CamcorderProfile.QUALITY_HIGH;
731 }
Michael Kolb8872c232013-01-29 10:33:22 -0800732 mProfile = CamcorderProfile.get(mCameraId, quality);
Angus Kong395ee2d2013-07-15 12:42:41 -0700733 mPreferenceRead = true;
ztenghuief01a312013-10-14 15:25:16 -0700734 if (mCameraDevice == null) {
735 return;
736 }
Doris Liu6432cd62013-06-13 17:20:31 -0700737 mParameters = mCameraDevice.getParameters();
Angus Kong5f8c30e2014-03-06 17:15:08 -0800738 Point desiredPreviewSize = getDesiredPreviewSize(mAppController.getAndroidContext(),
739 mParameters, mProfile, mUI.getPreviewScreenSize());
740 mDesiredPreviewWidth = desiredPreviewSize.x;
741 mDesiredPreviewHeight = desiredPreviewSize.y;
Doris Liu6432cd62013-06-13 17:20:31 -0700742 mUI.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800743 Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
744 ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
745 }
746
Angus Kong5f8c30e2014-03-06 17:15:08 -0800747 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
748 /**
749 * Calculates the preview size and stores it in mDesiredPreviewWidth and
750 * mDesiredPreviewHeight. This function checks {@link
751 * android.hardware.Camera.Parameters#getPreferredPreviewSizeForVideo()}
752 * but also considers the current preview area size on screen and make sure
753 * the final preview size will not be smaller than 1/2 of the current
754 * on screen preview area in terms of their short sides.
755 *
756 * @return The preferred preview size or {@code null} if the camera is not
757 * opened yet.
758 */
759 private static Point getDesiredPreviewSize(Context context, Parameters parameters,
760 CamcorderProfile profile, Point previewScreenSize) {
761 if (parameters.getSupportedVideoSizes() == null) {
762 // Driver doesn't support separate outputs for preview and video.
763 return new Point(profile.videoFrameWidth, profile.videoFrameHeight);
764 }
765
766 final int previewScreenShortSide = (previewScreenSize.x < previewScreenSize.y ?
767 previewScreenSize.x : previewScreenSize.y);
768 List<Size> sizes = parameters.getSupportedPreviewSizes();
769 Size preferred = parameters.getPreferredPreviewSizeForVideo();
770 final int preferredPreviewSizeShortSide = (preferred.width < preferred.height ?
771 preferred.width : preferred.height);
772 if (preferredPreviewSizeShortSide * 2 < previewScreenShortSide) {
773 preferred.width = profile.videoFrameWidth;
774 preferred.height = profile.videoFrameHeight;
775 }
776 int product = preferred.width * preferred.height;
777 Iterator<Size> it = sizes.iterator();
778 // Remove the preview sizes that are not preferred.
779 while (it.hasNext()) {
780 Size size = it.next();
781 if (size.width * size.height > product) {
782 it.remove();
783 }
784 }
785 Size optimalSize = CameraUtil.getOptimalPreviewSize(context, sizes,
786 (double) profile.videoFrameWidth / profile.videoFrameHeight);
787 return new Point(optimalSize.width, optimalSize.height);
788 }
789
Michael Kolb8872c232013-01-29 10:33:22 -0800790 private void resizeForPreviewAspectRatio() {
Doris Liue038c162013-12-13 23:06:11 -0800791 mUI.setAspectRatio((float) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800792 }
793
Angus Kong13e87c42013-11-25 10:02:47 -0800794 private void installIntentFilter() {
Michael Kolb8872c232013-01-29 10:33:22 -0800795 // install an intent filter to receive SD card related events.
796 IntentFilter intentFilter =
797 new IntentFilter(Intent.ACTION_MEDIA_EJECT);
798 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
799 intentFilter.addDataScheme("file");
800 mReceiver = new MyBroadcastReceiver();
801 mActivity.registerReceiver(mReceiver, intentFilter);
802 }
803
Michael Kolb8872c232013-01-29 10:33:22 -0800804 private void setDisplayOrientation() {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700805 mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
806 mCameraDisplayOrientation = CameraUtil.getDisplayOrientation(mDisplayRotation, mCameraId);
Doris Liu6432cd62013-06-13 17:20:31 -0700807 // Change the camera display orientation
808 if (mCameraDevice != null) {
809 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800810 }
Doris Liua1ec04a2014-01-13 17:29:40 -0800811 if (mFocusManager != null) {
812 mFocusManager.setDisplayOrientation(mCameraDisplayOrientation);
813 }
Doris Liu6432cd62013-06-13 17:20:31 -0700814 }
815
816 @Override
817 public void updateCameraOrientation() {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100818 if (mMediaRecorderRecording) {
819 return;
820 }
Angus Kongb50b5cb2013-08-09 14:55:20 -0700821 if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
Doris Liu6432cd62013-06-13 17:20:31 -0700822 setDisplayOrientation();
823 }
Michael Kolb8872c232013-01-29 10:33:22 -0800824 }
825
Doris Liu6827ce22013-03-12 19:24:28 -0700826 @Override
Doris Liu70da9182013-12-17 18:41:15 -0800827 public void updatePreviewAspectRatio(float aspectRatio) {
828 mAppController.updatePreviewAspectRatio(aspectRatio);
829 }
830
831 @Override
Doris Liu6827ce22013-03-12 19:24:28 -0700832 public int onZoomChanged(int index) {
833 // Not useful to change zoom value when the activity is paused.
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100834 if (mPaused) {
835 return index;
836 }
Doris Liu6827ce22013-03-12 19:24:28 -0700837 mZoomValue = index;
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100838 if (mParameters == null || mCameraDevice == null) {
839 return index;
840 }
Doris Liu6827ce22013-03-12 19:24:28 -0700841 // Set zoom parameters asynchronously
842 mParameters.setZoom(mZoomValue);
Doris Liu6432cd62013-06-13 17:20:31 -0700843 mCameraDevice.setParameters(mParameters);
844 Parameters p = mCameraDevice.getParameters();
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100845 if (p != null) {
846 return p.getZoom();
847 }
Doris Liu6827ce22013-03-12 19:24:28 -0700848 return index;
849 }
Angus Kong395ee2d2013-07-15 12:42:41 -0700850
Michael Kolb8872c232013-01-29 10:33:22 -0800851 private void startPreview() {
Alan Newbergerd41766f2014-04-09 18:25:34 -0700852 Log.i(TAG, "startPreview");
Michael Kolb8872c232013-01-29 10:33:22 -0800853
Erin Dahlgrend8de0772014-02-03 10:12:27 -0800854 SurfaceTexture surfaceTexture = mActivity.getCameraAppUI().getSurfaceTexture();
ztenghuief01a312013-10-14 15:25:16 -0700855 if (!mPreferenceRead || surfaceTexture == null || mPaused == true ||
856 mCameraDevice == null) {
857 return;
858 }
Angus Kong395ee2d2013-07-15 12:42:41 -0700859
Angus Kong5596b4c2014-03-11 16:27:30 -0700860 mCameraDevice.setErrorCallback(mHandler, mErrorCallback);
Michael Kolb8872c232013-01-29 10:33:22 -0800861 if (mPreviewing == true) {
862 stopPreview();
Michael Kolb8872c232013-01-29 10:33:22 -0800863 }
864
Michael Kolb8872c232013-01-29 10:33:22 -0800865 setDisplayOrientation();
Doris Liu6432cd62013-06-13 17:20:31 -0700866 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800867 setCameraParameters();
868
Doris Liua1ec04a2014-01-13 17:29:40 -0800869 if (mFocusManager != null) {
870 // If the focus mode is continuous autofocus, call cancelAutoFocus
871 // to resume it because it may have been paused by autoFocus call.
872 String focusMode = mFocusManager.getFocusMode();
873 if (CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE.equals(focusMode)) {
874 mCameraDevice.cancelAutoFocus();
875 }
876 }
Doris Liu5a367542014-01-17 17:21:42 -0800877
878 // This is to notify app controller that preview will start next, so app
879 // controller can set preview callbacks if needed. This has to happen before
880 // preview is started as a workaround of the framework issue related to preview
881 // callbacks that causes preview stretch and crash. (More details see b/12210027
882 // and b/12591410
883 mAppController.onPreviewReadyToStart();
Michael Kolb8872c232013-01-29 10:33:22 -0800884 try {
Doris Liu3973deb2013-08-21 13:42:22 -0700885 mCameraDevice.setPreviewTexture(surfaceTexture);
886 mCameraDevice.startPreview();
887 mPreviewing = true;
888 onPreviewStarted();
Michael Kolb8872c232013-01-29 10:33:22 -0800889 } catch (Throwable ex) {
890 closeCamera();
891 throw new RuntimeException("startPreview failed", ex);
Michael Kolb8872c232013-01-29 10:33:22 -0800892 }
Michael Kolbb1aeb392013-03-11 12:37:40 -0700893 }
894
895 private void onPreviewStarted() {
Erin Dahlgren667630d2014-04-01 14:03:25 -0700896 mAppController.setShutterEnabled(true);
Doris Liu2b906b82013-12-10 16:34:08 -0800897 mAppController.onPreviewStarted();
Doris Liua1ec04a2014-01-13 17:29:40 -0800898 if (mFocusManager != null) {
899 mFocusManager.onPreviewStarted();
900 }
Michael Kolb8872c232013-01-29 10:33:22 -0800901 }
902
Doris Liu6827ce22013-03-12 19:24:28 -0700903 @Override
904 public void stopPreview() {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100905 if (!mPreviewing) {
906 return;
907 }
Doris Liu6432cd62013-06-13 17:20:31 -0700908 mCameraDevice.stopPreview();
Doris Liua1ec04a2014-01-13 17:29:40 -0800909 if (mFocusManager != null) {
910 mFocusManager.onPreviewStopped();
911 }
Michael Kolb8872c232013-01-29 10:33:22 -0800912 mPreviewing = false;
913 }
914
Michael Kolb8872c232013-01-29 10:33:22 -0800915 private void closeCamera() {
Alan Newbergerd41766f2014-04-09 18:25:34 -0700916 Log.i(TAG, "closeCamera");
Doris Liu6432cd62013-06-13 17:20:31 -0700917 if (mCameraDevice == null) {
Michael Kolb8872c232013-01-29 10:33:22 -0800918 Log.d(TAG, "already stopped.");
919 return;
920 }
Doris Liu6432cd62013-06-13 17:20:31 -0700921 mCameraDevice.setZoomChangeListener(null);
Angus Kong5596b4c2014-03-11 16:27:30 -0700922 mCameraDevice.setErrorCallback(null, null);
Angus Kong20fad242013-11-11 18:23:46 -0800923 mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
Angus Kong395ee2d2013-07-15 12:42:41 -0700924 mCameraDevice = null;
Michael Kolb8872c232013-01-29 10:33:22 -0800925 mPreviewing = false;
926 mSnapshotInProgress = false;
Doris Liua1ec04a2014-01-13 17:29:40 -0800927 if (mFocusManager != null) {
928 mFocusManager.onCameraReleased();
929 }
Michael Kolb8872c232013-01-29 10:33:22 -0800930 }
931
Michael Kolb8872c232013-01-29 10:33:22 -0800932 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800933 public boolean onBackPressed() {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100934 if (mPaused) {
935 return true;
936 }
Michael Kolb8872c232013-01-29 10:33:22 -0800937 if (mMediaRecorderRecording) {
938 onStopVideoRecording();
939 return true;
Michael Kolb8872c232013-01-29 10:33:22 -0800940 } else {
Doris Liuf9e4f8f2013-12-04 18:04:22 -0800941 return false;
Michael Kolb8872c232013-01-29 10:33:22 -0800942 }
943 }
944
945 @Override
946 public boolean onKeyDown(int keyCode, KeyEvent event) {
947 // Do not handle any key if the activity is paused.
948 if (mPaused) {
949 return true;
950 }
951
952 switch (keyCode) {
953 case KeyEvent.KEYCODE_CAMERA:
954 if (event.getRepeatCount() == 0) {
Erin Dahlgren667630d2014-04-01 14:03:25 -0700955 onShutterButtonClick();
Michael Kolb8872c232013-01-29 10:33:22 -0800956 return true;
957 }
Michael Kolb8872c232013-01-29 10:33:22 -0800958 case KeyEvent.KEYCODE_DPAD_CENTER:
959 if (event.getRepeatCount() == 0) {
Erin Dahlgren667630d2014-04-01 14:03:25 -0700960 onShutterButtonClick();
Michael Kolb8872c232013-01-29 10:33:22 -0800961 return true;
962 }
Michael Kolb8872c232013-01-29 10:33:22 -0800963 case KeyEvent.KEYCODE_MENU:
Erin Dahlgren15691af2014-03-14 14:10:57 -0700964 // Consume menu button presses during capture.
965 return mMediaRecorderRecording;
Michael Kolb8872c232013-01-29 10:33:22 -0800966 }
967 return false;
968 }
969
970 @Override
971 public boolean onKeyUp(int keyCode, KeyEvent event) {
972 switch (keyCode) {
973 case KeyEvent.KEYCODE_CAMERA:
Erin Dahlgren667630d2014-04-01 14:03:25 -0700974 onShutterButtonClick();
Michael Kolb8872c232013-01-29 10:33:22 -0800975 return true;
Erin Dahlgren15691af2014-03-14 14:10:57 -0700976 case KeyEvent.KEYCODE_MENU:
977 // Consume menu button presses during capture.
978 return mMediaRecorderRecording;
Michael Kolb8872c232013-01-29 10:33:22 -0800979 }
980 return false;
981 }
982
Doris Liu6827ce22013-03-12 19:24:28 -0700983 @Override
984 public boolean isVideoCaptureIntent() {
Michael Kolb8872c232013-01-29 10:33:22 -0800985 String action = mActivity.getIntent().getAction();
986 return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
987 }
988
989 private void doReturnToCaller(boolean valid) {
990 Intent resultIntent = new Intent();
991 int resultCode;
992 if (valid) {
993 resultCode = Activity.RESULT_OK;
994 resultIntent.setData(mCurrentVideoUri);
995 } else {
996 resultCode = Activity.RESULT_CANCELED;
997 }
998 mActivity.setResultEx(resultCode, resultIntent);
999 mActivity.finish();
1000 }
1001
1002 private void cleanupEmptyFile() {
1003 if (mVideoFilename != null) {
1004 File f = new File(mVideoFilename);
1005 if (f.length() == 0 && f.delete()) {
1006 Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
1007 mVideoFilename = null;
1008 }
1009 }
1010 }
1011
Michael Kolb8872c232013-01-29 10:33:22 -08001012 // Prepares media recorder.
1013 private void initializeRecorder() {
Alan Newbergerd41766f2014-04-09 18:25:34 -07001014 Log.i(TAG, "initializeRecorder");
Michael Kolb8872c232013-01-29 10:33:22 -08001015 // If the mCameraDevice is null, then this activity is going to finish
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001016 if (mCameraDevice == null) {
1017 return;
1018 }
Michael Kolb8872c232013-01-29 10:33:22 -08001019
Michael Kolb8872c232013-01-29 10:33:22 -08001020 Intent intent = mActivity.getIntent();
1021 Bundle myExtras = intent.getExtras();
1022
1023 long requestedSizeLimit = 0;
1024 closeVideoFileDescriptor();
ztenghui70bd0242013-10-14 11:15:44 -07001025 mCurrentVideoUriFromMediaSaved = false;
Michael Kolb8872c232013-01-29 10:33:22 -08001026 if (mIsVideoCaptureIntent && myExtras != null) {
1027 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1028 if (saveUri != null) {
1029 try {
1030 mVideoFileDescriptor =
1031 mContentResolver.openFileDescriptor(saveUri, "rw");
1032 mCurrentVideoUri = saveUri;
1033 } catch (java.io.FileNotFoundException ex) {
1034 // invalid uri
1035 Log.e(TAG, ex.toString());
1036 }
1037 }
1038 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1039 }
1040 mMediaRecorder = new MediaRecorder();
1041
Michael Kolb8872c232013-01-29 10:33:22 -08001042 // Unlock the camera object before passing it to media recorder.
Doris Liu6432cd62013-06-13 17:20:31 -07001043 mCameraDevice.unlock();
Doris Liu6432cd62013-06-13 17:20:31 -07001044 mMediaRecorder.setCamera(mCameraDevice.getCamera());
Michael Kolb8872c232013-01-29 10:33:22 -08001045 if (!mCaptureTimeLapse) {
1046 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
1047 }
1048 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
1049 mMediaRecorder.setProfile(mProfile);
Michael Kolbf9542362013-05-30 07:45:41 -07001050 mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight);
Michael Kolb8872c232013-01-29 10:33:22 -08001051 mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
1052 if (mCaptureTimeLapse) {
1053 double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs;
1054 setCaptureRate(mMediaRecorder, fps);
1055 }
1056
1057 setRecordLocation();
1058
1059 // Set output file.
1060 // Try Uri in the intent first. If it doesn't exist, use our own
1061 // instead.
1062 if (mVideoFileDescriptor != null) {
1063 mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1064 } else {
1065 generateVideoFilename(mProfile.fileFormat);
1066 mMediaRecorder.setOutputFile(mVideoFilename);
1067 }
1068
1069 // Set maximum file size.
Angus Kong2dcc0a92013-09-25 13:00:08 -07001070 long maxFileSize = mActivity.getStorageSpaceBytes() - Storage.LOW_STORAGE_THRESHOLD_BYTES;
Michael Kolb8872c232013-01-29 10:33:22 -08001071 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1072 maxFileSize = requestedSizeLimit;
1073 }
1074
1075 try {
1076 mMediaRecorder.setMaxFileSize(maxFileSize);
1077 } catch (RuntimeException exception) {
1078 // We are going to ignore failure of setMaxFileSize here, as
1079 // a) The composer selected may simply not support it, or
1080 // b) The underlying media framework may not handle 64-bit range
1081 // on the size restriction.
1082 }
1083
1084 // See android.hardware.Camera.Parameters.setRotation for
1085 // documentation.
1086 // Note that mOrientation here is the device orientation, which is the opposite of
1087 // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
1088 // which is the orientation the graphics need to rotate in order to render correctly.
1089 int rotation = 0;
1090 if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
Angus Kong20fad242013-11-11 18:23:46 -08001091 CameraInfo info = mActivity.getCameraProvider().getCameraInfo()[mCameraId];
Sascha Haeberling6ccec202014-03-11 09:44:34 -07001092 if (isCameraFrontFacing()) {
Michael Kolb8872c232013-01-29 10:33:22 -08001093 rotation = (info.orientation - mOrientation + 360) % 360;
1094 } else { // back-facing camera
1095 rotation = (info.orientation + mOrientation) % 360;
1096 }
1097 }
1098 mMediaRecorder.setOrientationHint(rotation);
1099
1100 try {
1101 mMediaRecorder.prepare();
1102 } catch (IOException e) {
1103 Log.e(TAG, "prepare failed for " + mVideoFilename, e);
1104 releaseMediaRecorder();
1105 throw new RuntimeException(e);
1106 }
1107
1108 mMediaRecorder.setOnErrorListener(this);
1109 mMediaRecorder.setOnInfoListener(this);
1110 }
1111
Michael Kolb8872c232013-01-29 10:33:22 -08001112 private static void setCaptureRate(MediaRecorder recorder, double fps) {
1113 recorder.setCaptureRate(fps);
1114 }
1115
Michael Kolb8872c232013-01-29 10:33:22 -08001116 private void setRecordLocation() {
Sascha Haeberling638e6f02013-09-18 14:28:51 -07001117 Location loc = mLocationManager.getCurrentLocation();
1118 if (loc != null) {
1119 mMediaRecorder.setLocation((float) loc.getLatitude(),
1120 (float) loc.getLongitude());
Michael Kolb8872c232013-01-29 10:33:22 -08001121 }
1122 }
1123
Michael Kolb8872c232013-01-29 10:33:22 -08001124 private void releaseMediaRecorder() {
Alan Newbergerd41766f2014-04-09 18:25:34 -07001125 Log.i(TAG, "Releasing media recorder.");
Michael Kolb8872c232013-01-29 10:33:22 -08001126 if (mMediaRecorder != null) {
1127 cleanupEmptyFile();
1128 mMediaRecorder.reset();
1129 mMediaRecorder.release();
1130 mMediaRecorder = null;
1131 }
1132 mVideoFilename = null;
1133 }
1134
Michael Kolb8872c232013-01-29 10:33:22 -08001135 private void generateVideoFilename(int outputFileFormat) {
1136 long dateTaken = System.currentTimeMillis();
1137 String title = createName(dateTaken);
1138 // Used when emailing.
1139 String filename = title + convertOutputFormatToFileExt(outputFileFormat);
1140 String mime = convertOutputFormatToMimeType(outputFileFormat);
1141 String path = Storage.DIRECTORY + '/' + filename;
1142 String tmpPath = path + ".tmp";
Ruben Brunk16007962013-04-19 15:27:57 -07001143 mCurrentVideoValues = new ContentValues(9);
Michael Kolb8872c232013-01-29 10:33:22 -08001144 mCurrentVideoValues.put(Video.Media.TITLE, title);
1145 mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
1146 mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
Ruben Brunk16007962013-04-19 15:27:57 -07001147 mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000);
Michael Kolb8872c232013-01-29 10:33:22 -08001148 mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
1149 mCurrentVideoValues.put(Video.Media.DATA, path);
Sam Juddde3e9ab2014-03-17 13:07:22 -07001150 mCurrentVideoValues.put(Video.Media.WIDTH, mProfile.videoFrameWidth);
1151 mCurrentVideoValues.put(Video.Media.HEIGHT, mProfile.videoFrameHeight);
Michael Kolb8872c232013-01-29 10:33:22 -08001152 mCurrentVideoValues.put(Video.Media.RESOLUTION,
1153 Integer.toString(mProfile.videoFrameWidth) + "x" +
1154 Integer.toString(mProfile.videoFrameHeight));
1155 Location loc = mLocationManager.getCurrentLocation();
1156 if (loc != null) {
1157 mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
1158 mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
1159 }
Michael Kolb8872c232013-01-29 10:33:22 -08001160 mVideoFilename = tmpPath;
1161 Log.v(TAG, "New video filename: " + mVideoFilename);
1162 }
1163
Angus Kong83a99ae2013-04-17 15:37:07 -07001164 private void saveVideo() {
Michael Kolb8872c232013-01-29 10:33:22 -08001165 if (mVideoFileDescriptor == null) {
Michael Kolb8872c232013-01-29 10:33:22 -08001166 long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
1167 if (duration > 0) {
1168 if (mCaptureTimeLapse) {
1169 duration = getTimeLapseVideoLength(duration);
1170 }
Michael Kolb8872c232013-01-29 10:33:22 -08001171 } else {
1172 Log.w(TAG, "Video duration <= 0 : " + duration);
1173 }
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08001174 getServices().getMediaSaver().addVideo(mCurrentVideoFilename,
Andy Huibers10c58162014-03-29 14:06:54 -07001175 duration, isCameraFrontFacing(), mCurrentVideoValues,
Angus Kong83a99ae2013-04-17 15:37:07 -07001176 mOnVideoSavedListener, mContentResolver);
Michael Kolb8872c232013-01-29 10:33:22 -08001177 }
1178 mCurrentVideoValues = null;
Michael Kolb8872c232013-01-29 10:33:22 -08001179 }
1180
1181 private void deleteVideoFile(String fileName) {
1182 Log.v(TAG, "Deleting video " + fileName);
1183 File f = new File(fileName);
1184 if (!f.delete()) {
1185 Log.v(TAG, "Could not delete " + fileName);
1186 }
1187 }
1188
Michael Kolb8872c232013-01-29 10:33:22 -08001189 // from MediaRecorder.OnErrorListener
1190 @Override
1191 public void onError(MediaRecorder mr, int what, int extra) {
1192 Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
1193 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1194 // We may have run out of space on the sdcard.
1195 stopVideoRecording();
Spike Spraguee6374b72014-04-25 17:24:32 -07001196 mActivity.updateStorageSpaceAndHint(null);
Michael Kolb8872c232013-01-29 10:33:22 -08001197 }
1198 }
1199
1200 // from MediaRecorder.OnInfoListener
1201 @Override
1202 public void onInfo(MediaRecorder mr, int what, int extra) {
1203 if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001204 if (mMediaRecorderRecording) {
1205 onStopVideoRecording();
1206 }
Michael Kolb8872c232013-01-29 10:33:22 -08001207 } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001208 if (mMediaRecorderRecording) {
1209 onStopVideoRecording();
1210 }
Michael Kolb8872c232013-01-29 10:33:22 -08001211
1212 // Show the toast.
1213 Toast.makeText(mActivity, R.string.video_reach_size_limit,
1214 Toast.LENGTH_LONG).show();
1215 }
1216 }
1217
1218 /*
1219 * Make sure we're not recording music playing in the background, ask the
1220 * MediaPlaybackService to pause playback.
1221 */
1222 private void pauseAudioPlayback() {
Marco Nelissen20694b22013-10-29 15:27:24 -07001223 AudioManager am = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);
1224 am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
Michael Kolb8872c232013-01-29 10:33:22 -08001225 }
1226
1227 // For testing.
1228 public boolean isRecording() {
1229 return mMediaRecorderRecording;
1230 }
1231
1232 private void startVideoRecording() {
Alan Newbergerd41766f2014-04-09 18:25:34 -07001233 Log.i(TAG, "startVideoRecording");
Sascha Haeberling37f36112013-08-06 14:31:52 -07001234 mUI.cancelAnimations();
Doris Liu6432cd62013-06-13 17:20:31 -07001235 mUI.setSwipingEnabled(false);
Doris Liu38c6bc32014-01-16 18:03:18 -08001236 mUI.showFocusUI(false);
Doris Liud6487c92014-02-28 10:35:45 -08001237 mUI.showVideoRecordingHints(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001238
Spike Spraguee6374b72014-04-25 17:24:32 -07001239 mActivity.updateStorageSpaceAndHint(new CameraActivity.OnStorageUpdateDoneListener() {
1240 @Override
1241 public void onStorageUpdateDone(long bytes) {
1242 if (bytes <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1243 Log.w(TAG, "Storage issue, ignore the start request");
1244 } else {
1245 //??
1246 //if (!mCameraDevice.waitDone()) return;
1247 mCurrentVideoUri = null;
Michael Kolb8872c232013-01-29 10:33:22 -08001248
Spike Spraguee6374b72014-04-25 17:24:32 -07001249 initializeRecorder();
1250 if (mMediaRecorder == null) {
1251 Log.e(TAG, "Fail to initialize media recorder");
1252 return;
1253 }
Doris Liu3973deb2013-08-21 13:42:22 -07001254
Spike Spraguee6374b72014-04-25 17:24:32 -07001255 pauseAudioPlayback();
Michael Kolb8872c232013-01-29 10:33:22 -08001256
Spike Spraguee6374b72014-04-25 17:24:32 -07001257 try {
1258 mMediaRecorder.start(); // Recording is now started
1259 } catch (RuntimeException e) {
1260 Log.e(TAG, "Could not start media recorder. ", e);
1261 releaseMediaRecorder();
1262 // If start fails, frameworks will not lock the camera for us.
1263 mCameraDevice.lock();
1264 return;
1265 }
1266 mAppController.getCameraAppUI().setSwipeEnabled(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001267
Spike Spraguee6374b72014-04-25 17:24:32 -07001268 // The parameters might have been altered by MediaRecorder already.
1269 // We need to force mCameraDevice to refresh before getting it.
1270 mCameraDevice.refreshParameters();
1271 // The parameters may have been changed by MediaRecorder upon starting
1272 // recording. We need to alter the parameters if we support camcorder
1273 // zoom. To reduce latency when setting the parameters during zoom, we
1274 // update mParameters here once.
1275 mParameters = mCameraDevice.getParameters();
Michael Kolb8872c232013-01-29 10:33:22 -08001276
Spike Spraguee6374b72014-04-25 17:24:32 -07001277 mMediaRecorderRecording = true;
1278 mActivity.lockOrientation();
1279 mRecordingStartTime = SystemClock.uptimeMillis();
Michael Kolb8872c232013-01-29 10:33:22 -08001280
Spike Spraguee6374b72014-04-25 17:24:32 -07001281 // A special case of mode options closing: during capture it should
1282 // not be possible to change mode state.
1283 mAppController.getCameraAppUI().hideModeOptions();
1284 mAppController.getCameraAppUI().animateBottomBarToVideoStop(R.drawable.ic_stop);
1285 mUI.showRecordingUI(true);
Doris Liu4df91582014-03-21 18:33:57 -07001286
Spike Spraguee6374b72014-04-25 17:24:32 -07001287 setFocusParameters();
1288 updateRecordingTime();
1289 mActivity.enableKeepScreenOn(true);
1290 }
1291 }
1292 });
Michael Kolb8872c232013-01-29 10:33:22 -08001293 }
1294
Sascha Haeberling37f36112013-08-06 14:31:52 -07001295 private Bitmap getVideoThumbnail() {
Michael Kolb8872c232013-01-29 10:33:22 -08001296 Bitmap bitmap = null;
1297 if (mVideoFileDescriptor != null) {
1298 bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
Doris Liu3c2fca32013-02-13 18:28:03 -08001299 mDesiredPreviewWidth);
Doris Liu2a7f44c2013-08-12 15:18:53 -07001300 } else if (mCurrentVideoUri != null) {
1301 try {
1302 mVideoFileDescriptor = mContentResolver.openFileDescriptor(mCurrentVideoUri, "r");
1303 bitmap = Thumbnail.createVideoThumbnailBitmap(
1304 mVideoFileDescriptor.getFileDescriptor(), mDesiredPreviewWidth);
1305 } catch (java.io.FileNotFoundException ex) {
1306 // invalid uri
1307 Log.e(TAG, ex.toString());
1308 }
Michael Kolb8872c232013-01-29 10:33:22 -08001309 }
Doris Liu3973deb2013-08-21 13:42:22 -07001310
Michael Kolb8872c232013-01-29 10:33:22 -08001311 if (bitmap != null) {
1312 // MetadataRetriever already rotates the thumbnail. We should rotate
1313 // it to match the UI orientation (and mirror if it is front-facing camera).
Sascha Haeberling6ccec202014-03-11 09:44:34 -07001314 bitmap = CameraUtil.rotateAndMirror(bitmap, 0, isCameraFrontFacing());
Sascha Haeberling37f36112013-08-06 14:31:52 -07001315 }
1316 return bitmap;
1317 }
1318
1319 private void showCaptureResult() {
1320 mIsInReviewMode = true;
1321 Bitmap bitmap = getVideoThumbnail();
1322 if (bitmap != null) {
Doris Liu6827ce22013-03-12 19:24:28 -07001323 mUI.showReviewImage(bitmap);
Michael Kolb8872c232013-01-29 10:33:22 -08001324 }
Doris Liu6827ce22013-03-12 19:24:28 -07001325 mUI.showReviewControls();
Doris Liu6827ce22013-03-12 19:24:28 -07001326 mUI.showTimeLapseUI(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001327 }
1328
Michael Kolb8872c232013-01-29 10:33:22 -08001329 private boolean stopVideoRecording() {
Alan Newbergerd41766f2014-04-09 18:25:34 -07001330 Log.i(TAG, "stopVideoRecording");
Doris Liu6432cd62013-06-13 17:20:31 -07001331 mUI.setSwipingEnabled(true);
Doris Liu38c6bc32014-01-16 18:03:18 -08001332 mUI.showFocusUI(true);
Doris Liud6487c92014-02-28 10:35:45 -08001333 mUI.showVideoRecordingHints(true);
Michael Kolb8872c232013-01-29 10:33:22 -08001334
1335 boolean fail = false;
1336 if (mMediaRecorderRecording) {
1337 boolean shouldAddToMediaStoreNow = false;
1338
1339 try {
Doris Liu3973deb2013-08-21 13:42:22 -07001340 mMediaRecorder.setOnErrorListener(null);
1341 mMediaRecorder.setOnInfoListener(null);
1342 mMediaRecorder.stop();
1343 shouldAddToMediaStoreNow = true;
Michael Kolb8872c232013-01-29 10:33:22 -08001344 mCurrentVideoFilename = mVideoFilename;
Andy Huibers10c58162014-03-29 14:06:54 -07001345 Log.v(TAG, "stopVideoRecording: current video filename: " + mCurrentVideoFilename);
Michael Kolb8872c232013-01-29 10:33:22 -08001346 } catch (RuntimeException e) {
1347 Log.e(TAG, "stop fail", e);
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001348 if (mVideoFilename != null) {
1349 deleteVideoFile(mVideoFilename);
1350 }
Michael Kolb8872c232013-01-29 10:33:22 -08001351 fail = true;
1352 }
1353 mMediaRecorderRecording = false;
Angus Kong9f1db522013-11-09 16:25:59 -08001354 mActivity.unlockOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001355
1356 // If the activity is paused, this means activity is interrupted
1357 // during recording. Release the camera as soon as possible because
1358 // face unlock or other applications may need to use the camera.
Michael Kolb8872c232013-01-29 10:33:22 -08001359 if (mPaused) {
Doris Liu3973deb2013-08-21 13:42:22 -07001360 closeCamera();
Michael Kolb8872c232013-01-29 10:33:22 -08001361 }
1362
Doris Liufe6596c2013-10-08 11:03:37 -07001363 mUI.showRecordingUI(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001364 // The orientation was fixed during video recording. Now make it
1365 // reflect the device orientation as video recording is stopped.
Doris Liu6827ce22013-03-12 19:24:28 -07001366 mUI.setOrientationIndicator(0, true);
Angus Kong13e87c42013-11-25 10:02:47 -08001367 mActivity.enableKeepScreenOn(false);
Doris Liu2a7f44c2013-08-12 15:18:53 -07001368 if (shouldAddToMediaStoreNow && !fail) {
1369 if (mVideoFileDescriptor == null) {
1370 saveVideo();
1371 } else if (mIsVideoCaptureIntent) {
1372 // if no file save is needed, we can show the post capture UI now
1373 showCaptureResult();
1374 }
Michael Kolb8872c232013-01-29 10:33:22 -08001375 }
1376 }
Doris Liu3973deb2013-08-21 13:42:22 -07001377 // release media recorder
1378 releaseMediaRecorder();
Doris Liu4df91582014-03-21 18:33:57 -07001379
1380 mAppController.getCameraAppUI().showModeOptions();
1381 mAppController.getCameraAppUI().animateBottomBarToFullSize(mShutterIconId);
Doris Liu3973deb2013-08-21 13:42:22 -07001382 if (!mPaused) {
Doris Liu1143ebd2014-01-24 14:11:50 -08001383 setFocusParameters();
Doris Liu3973deb2013-08-21 13:42:22 -07001384 mCameraDevice.lock();
1385 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
1386 stopPreview();
Doris Liu3973deb2013-08-21 13:42:22 -07001387 // Switch back to use SurfaceTexture for preview.
1388 startPreview();
Michael Kolb8872c232013-01-29 10:33:22 -08001389 }
Angus Kong62753ae2014-02-10 10:53:54 -08001390 // Update the parameters here because the parameters might have been altered
1391 // by MediaRecorder.
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001392 mParameters = mCameraDevice.getParameters();
1393 }
Spike Spraguee6374b72014-04-25 17:24:32 -07001394
1395 // Check this in advance of each shot so we don't add to shutter
1396 // latency. It's true that someone else could write to the SD card
1397 // in the mean time and fill it, but that could have happened
1398 // between the shutter press and saving the file too.
1399 mActivity.updateStorageSpaceAndHint(null);
1400
Michael Kolb8872c232013-01-29 10:33:22 -08001401 return fail;
1402 }
1403
Michael Kolb8872c232013-01-29 10:33:22 -08001404 private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
1405 long seconds = milliSeconds / 1000; // round down to compute seconds
1406 long minutes = seconds / 60;
1407 long hours = minutes / 60;
1408 long remainderMinutes = minutes - (hours * 60);
1409 long remainderSeconds = seconds - (minutes * 60);
1410
1411 StringBuilder timeStringBuilder = new StringBuilder();
1412
1413 // Hours
1414 if (hours > 0) {
1415 if (hours < 10) {
1416 timeStringBuilder.append('0');
1417 }
1418 timeStringBuilder.append(hours);
1419
1420 timeStringBuilder.append(':');
1421 }
1422
1423 // Minutes
1424 if (remainderMinutes < 10) {
1425 timeStringBuilder.append('0');
1426 }
1427 timeStringBuilder.append(remainderMinutes);
1428 timeStringBuilder.append(':');
1429
1430 // Seconds
1431 if (remainderSeconds < 10) {
1432 timeStringBuilder.append('0');
1433 }
1434 timeStringBuilder.append(remainderSeconds);
1435
1436 // Centi seconds
1437 if (displayCentiSeconds) {
1438 timeStringBuilder.append('.');
1439 long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
1440 if (remainderCentiSeconds < 10) {
1441 timeStringBuilder.append('0');
1442 }
1443 timeStringBuilder.append(remainderCentiSeconds);
1444 }
1445
1446 return timeStringBuilder.toString();
1447 }
1448
1449 private long getTimeLapseVideoLength(long deltaMs) {
1450 // For better approximation calculate fractional number of frames captured.
1451 // This will update the video time at a higher resolution.
1452 double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
1453 return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
1454 }
1455
1456 private void updateRecordingTime() {
1457 if (!mMediaRecorderRecording) {
1458 return;
1459 }
1460 long now = SystemClock.uptimeMillis();
1461 long delta = now - mRecordingStartTime;
1462
1463 // Starting a minute before reaching the max duration
1464 // limit, we'll countdown the remaining time instead.
1465 boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1466 && delta >= mMaxVideoDurationInMs - 60000);
1467
1468 long deltaAdjusted = delta;
1469 if (countdownRemainingTime) {
1470 deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
1471 }
1472 String text;
1473
1474 long targetNextUpdateDelay;
1475 if (!mCaptureTimeLapse) {
1476 text = millisecondToTimeString(deltaAdjusted, false);
1477 targetNextUpdateDelay = 1000;
1478 } else {
1479 // The length of time lapse video is different from the length
1480 // of the actual wall clock time elapsed. Display the video length
1481 // only in format hh:mm:ss.dd, where dd are the centi seconds.
1482 text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
1483 targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
1484 }
1485
Doris Liu6827ce22013-03-12 19:24:28 -07001486 mUI.setRecordingTime(text);
Michael Kolb8872c232013-01-29 10:33:22 -08001487
1488 if (mRecordingTimeCountsDown != countdownRemainingTime) {
1489 // Avoid setting the color on every update, do it only
1490 // when it needs changing.
1491 mRecordingTimeCountsDown = countdownRemainingTime;
1492
1493 int color = mActivity.getResources().getColor(countdownRemainingTime
1494 ? R.color.recording_time_remaining_text
1495 : R.color.recording_time_elapsed_text);
1496
Doris Liu6827ce22013-03-12 19:24:28 -07001497 mUI.setRecordingTimeTextColor(color);
Michael Kolb8872c232013-01-29 10:33:22 -08001498 }
1499
1500 long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
Angus Kong13e87c42013-11-25 10:02:47 -08001501 mHandler.sendEmptyMessageDelayed(MSG_UPDATE_RECORD_TIME, actualNextUpdateDelay);
Michael Kolb8872c232013-01-29 10:33:22 -08001502 }
1503
1504 private static boolean isSupported(String value, List<String> supported) {
1505 return supported == null ? false : supported.indexOf(value) >= 0;
1506 }
1507
1508 @SuppressWarnings("deprecation")
1509 private void setCameraParameters() {
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001510 SettingsManager settingsManager = mActivity.getSettingsManager();
1511
Michael Kolb8872c232013-01-29 10:33:22 -08001512 mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
Andy Huibers6dcf90c2014-04-14 13:53:12 -07001513 // This is required for Samsung SGH-I337 and probably other Samsung S4 versions
1514 if (Build.BRAND.toLowerCase().contains("samsung")) {
1515 mParameters.set("video-size", mProfile.videoFrameWidth + "x" + mProfile.videoFrameHeight);
1516 }
Angus Kongb50b5cb2013-08-09 14:55:20 -07001517 int[] fpsRange = CameraUtil.getMaxPreviewFpsRange(mParameters);
Doris Liu6432cd62013-06-13 17:20:31 -07001518 if (fpsRange.length > 0) {
1519 mParameters.setPreviewFpsRange(
1520 fpsRange[Parameters.PREVIEW_FPS_MIN_INDEX],
1521 fpsRange[Parameters.PREVIEW_FPS_MAX_INDEX]);
1522 } else {
1523 mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
1524 }
Michael Kolb8872c232013-01-29 10:33:22 -08001525
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001526 enableTorchMode(settingsManager.isCameraBackFacing());
Michael Kolb8872c232013-01-29 10:33:22 -08001527
Michael Kolb8872c232013-01-29 10:33:22 -08001528 // Set zoom.
1529 if (mParameters.isZoomSupported()) {
1530 mParameters.setZoom(mZoomValue);
1531 }
Doris Liua1ec04a2014-01-13 17:29:40 -08001532 updateFocusParameters();
Michael Kolb8872c232013-01-29 10:33:22 -08001533
Angus Kongb50b5cb2013-08-09 14:55:20 -07001534 mParameters.set(CameraUtil.RECORDING_HINT, CameraUtil.TRUE);
Michael Kolb8872c232013-01-29 10:33:22 -08001535
1536 // Enable video stabilization. Convenience methods not available in API
1537 // level <= 14
1538 String vstabSupported = mParameters.get("video-stabilization-supported");
1539 if ("true".equals(vstabSupported)) {
1540 mParameters.set("video-stabilization", "true");
1541 }
1542
1543 // Set picture size.
1544 // The logic here is different from the logic in still-mode camera.
1545 // There we determine the preview size based on the picture size, but
1546 // here we determine the picture size based on the preview size.
1547 List<Size> supported = mParameters.getSupportedPictureSizes();
Angus Kongb50b5cb2013-08-09 14:55:20 -07001548 Size optimalSize = CameraUtil.getOptimalVideoSnapshotPictureSize(supported,
Michael Kolb8872c232013-01-29 10:33:22 -08001549 (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
1550 Size original = mParameters.getPictureSize();
1551 if (!original.equals(optimalSize)) {
1552 mParameters.setPictureSize(optimalSize.width, optimalSize.height);
1553 }
Alan Newbergerd41766f2014-04-09 18:25:34 -07001554 Log.d(TAG, "Video snapshot size is " + optimalSize.width + "x" +
Michael Kolb8872c232013-01-29 10:33:22 -08001555 optimalSize.height);
1556
1557 // Set JPEG quality.
1558 int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1559 CameraProfile.QUALITY_HIGH);
1560 mParameters.setJpegQuality(jpegQuality);
1561
Doris Liu6432cd62013-06-13 17:20:31 -07001562 mCameraDevice.setParameters(mParameters);
Andy Huibers64abfe92014-01-14 22:25:52 -08001563 // Nexus 5 through KitKat 4.4.2 requires a second call to
1564 // .setParameters() for frame rate settings to take effect.
1565 mCameraDevice.setParameters(mParameters);
Sascha Haeberlingf8b877c2013-09-09 17:32:48 -07001566
1567 // Update UI based on the new parameters.
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001568 mUI.updateOnScreenIndicators(mParameters);
Michael Kolb8872c232013-01-29 10:33:22 -08001569 }
1570
Doris Liua1ec04a2014-01-13 17:29:40 -08001571 private void updateFocusParameters() {
1572 // Set continuous autofocus. During recording, we use "continuous-video"
1573 // auto focus mode to ensure smooth focusing. Whereas during preview (i.e.
1574 // before recording starts) we use "continuous-picture" auto focus mode
1575 // for faster but slightly jittery focusing.
1576 List<String> supportedFocus = mParameters.getSupportedFocusModes();
1577 if (mMediaRecorderRecording) {
1578 if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
1579 mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
1580 mFocusManager.overrideFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
1581 } else {
1582 mFocusManager.overrideFocusMode(null);
1583 }
1584 } else {
1585 mFocusManager.overrideFocusMode(null);
1586 if (isSupported(CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE, supportedFocus)) {
1587 mParameters.setFocusMode(mFocusManager.getFocusMode());
1588 if (mFocusAreaSupported) {
1589 mParameters.setFocusAreas(mFocusManager.getFocusAreas());
1590 }
1591 }
1592 }
1593 updateAutoFocusMoveCallback();
1594 }
1595
Michael Kolb8872c232013-01-29 10:33:22 -08001596 @Override
Angus Kong20fad242013-11-11 18:23:46 -08001597 public void resume() {
Spike Sprague51c877c2014-02-18 11:14:12 -08001598 if (isVideoCaptureIntent()) {
1599 mDontResetIntentUiOnResume = mPaused;
1600 }
1601
Angus Kongc4e66562013-11-22 23:03:21 -08001602 mPaused = false;
Angus Kong13e87c42013-11-25 10:02:47 -08001603 installIntentFilter();
Erin Dahlgren667630d2014-04-01 14:03:25 -07001604 mAppController.setShutterEnabled(false);
Angus Kongc4e66562013-11-22 23:03:21 -08001605 mZoomValue = 0;
1606
1607 showVideoSnapshotUI(false);
1608
1609 if (!mPreviewing) {
1610 requestCamera(mCameraId);
1611 } else {
1612 // preview already started
Erin Dahlgren667630d2014-04-01 14:03:25 -07001613 mAppController.setShutterEnabled(true);
Angus Kongc4e66562013-11-22 23:03:21 -08001614 }
1615
Doris Liua1ec04a2014-01-13 17:29:40 -08001616 if (mFocusManager != null) {
1617 // If camera is not open when resume is called, focus manager will not
1618 // be initialized yet, in which case it will start listening to
1619 // preview area size change later in the initialization.
1620 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1621 }
Sascha Haeberlingde303232014-02-07 02:30:53 +01001622
Angus Kongc4e66562013-11-22 23:03:21 -08001623 if (mPreviewing) {
1624 mOnResumeTime = SystemClock.uptimeMillis();
Angus Kong13e87c42013-11-25 10:02:47 -08001625 mHandler.sendEmptyMessageDelayed(MSG_CHECK_DISPLAY_ROTATION, 100);
Angus Kongc4e66562013-11-22 23:03:21 -08001626 }
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -08001627 getServices().getMemoryManager().addListener(this);
Angus Kong20fad242013-11-11 18:23:46 -08001628 }
1629
1630 @Override
1631 public void pause() {
Angus Kongc4e66562013-11-22 23:03:21 -08001632 mPaused = true;
Doris Liua1ec04a2014-01-13 17:29:40 -08001633
1634 if (mFocusManager != null) {
1635 // If camera is not open when resume is called, focus manager will not
1636 // be initialized yet, in which case it will start listening to
1637 // preview area size change later in the initialization.
1638 mAppController.removePreviewAreaSizeChangedListener(mFocusManager);
1639 mFocusManager.removeMessages();
1640 }
Angus Kongc4e66562013-11-22 23:03:21 -08001641 if (mMediaRecorderRecording) {
1642 // Camera will be released in onStopVideoRecording.
1643 onStopVideoRecording();
1644 } else {
1645 stopPreview();
1646 closeCamera();
1647 releaseMediaRecorder();
1648 }
1649
1650 closeVideoFileDescriptor();
Angus Kongc4e66562013-11-22 23:03:21 -08001651
1652 if (mReceiver != null) {
1653 mActivity.unregisterReceiver(mReceiver);
1654 mReceiver = null;
1655 }
Angus Kongc4e66562013-11-22 23:03:21 -08001656
Angus Kong13e87c42013-11-25 10:02:47 -08001657 mHandler.removeMessages(MSG_CHECK_DISPLAY_ROTATION);
1658 mHandler.removeMessages(MSG_SWITCH_CAMERA);
1659 mHandler.removeMessages(MSG_SWITCH_CAMERA_START_ANIMATION);
Angus Kongc4e66562013-11-22 23:03:21 -08001660 mPendingSwitchCameraId = -1;
1661 mSwitchingCamera = false;
1662 mPreferenceRead = false;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -08001663 getServices().getMemoryManager().removeListener(this);
Angus Kong20fad242013-11-11 18:23:46 -08001664 }
1665
1666 @Override
1667 public void destroy() {
1668
1669 }
1670
1671 @Override
Angus Kong2f0e4a32013-12-03 10:02:35 -08001672 public void onLayoutOrientationChanged(boolean isLandscape) {
Michael Kolb8872c232013-01-29 10:33:22 -08001673 setDisplayOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001674 }
1675
Erin Dahlgrena07e94c2013-12-04 18:44:08 -08001676 // TODO: integrate this into the SettingsManager listeners.
Michael Kolb8872c232013-01-29 10:33:22 -08001677 public void onSharedPreferenceChanged() {
Michael Kolb8872c232013-01-29 10:33:22 -08001678
Michael Kolb8872c232013-01-29 10:33:22 -08001679 }
1680
1681 private void switchCamera() {
Sascha Haeberlingf8b877c2013-09-09 17:32:48 -07001682 if (mPaused) {
1683 return;
1684 }
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001685 SettingsManager settingsManager = mActivity.getSettingsManager();
Michael Kolb8872c232013-01-29 10:33:22 -08001686
1687 Log.d(TAG, "Start to switch camera.");
1688 mCameraId = mPendingSwitchCameraId;
1689 mPendingSwitchCameraId = -1;
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001690 settingsManager.set(SettingsManager.SETTING_CAMERA_ID, "" + mCameraId);
Michael Kolb8872c232013-01-29 10:33:22 -08001691
Doris Liua1ec04a2014-01-13 17:29:40 -08001692 if (mFocusManager != null) {
1693 mFocusManager.removeMessages();
1694 }
Michael Kolb8872c232013-01-29 10:33:22 -08001695 closeCamera();
Angus Kong20fad242013-11-11 18:23:46 -08001696 requestCamera(mCameraId);
Michael Kolb8872c232013-01-29 10:33:22 -08001697
Sascha Haeberling6ccec202014-03-11 09:44:34 -07001698 mMirror = isCameraFrontFacing();
Doris Liua1ec04a2014-01-13 17:29:40 -08001699 if (mFocusManager != null) {
1700 mFocusManager.setMirror(mMirror);
1701 }
1702
Michael Kolb8872c232013-01-29 10:33:22 -08001703 // From onResume
Doris Liu6432cd62013-06-13 17:20:31 -07001704 mZoomValue = 0;
Doris Liu6827ce22013-03-12 19:24:28 -07001705 mUI.setOrientationIndicator(0, false);
Michael Kolb8872c232013-01-29 10:33:22 -08001706
Doris Liu6432cd62013-06-13 17:20:31 -07001707 // Start switch camera animation. Post a message because
1708 // onFrameAvailable from the old camera may already exist.
Angus Kong13e87c42013-11-25 10:02:47 -08001709 mHandler.sendEmptyMessage(MSG_SWITCH_CAMERA_START_ANIMATION);
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001710 mUI.updateOnScreenIndicators(mParameters);
Michael Kolb8872c232013-01-29 10:33:22 -08001711 }
1712
Michael Kolb8872c232013-01-29 10:33:22 -08001713 private void initializeVideoSnapshot() {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001714 if (mParameters == null) {
1715 return;
1716 }
Michael Kolb8872c232013-01-29 10:33:22 -08001717 }
1718
1719 void showVideoSnapshotUI(boolean enabled) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001720 if (mParameters == null) {
1721 return;
1722 }
Angus Kongb50b5cb2013-08-09 14:55:20 -07001723 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
Doris Liu6432cd62013-06-13 17:20:31 -07001724 if (enabled) {
Sascha Haeberling37f36112013-08-06 14:31:52 -07001725 mUI.animateFlash();
Michael Kolb8872c232013-01-29 10:33:22 -08001726 } else {
Doris Liu6827ce22013-03-12 19:24:28 -07001727 mUI.showPreviewBorder(enabled);
Michael Kolb8872c232013-01-29 10:33:22 -08001728 }
Erin Dahlgren667630d2014-04-01 14:03:25 -07001729 mAppController.setShutterEnabled(!enabled);
Michael Kolb8872c232013-01-29 10:33:22 -08001730 }
1731 }
1732
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001733 /**
1734 * Used to update the flash mode. Video mode can turn on the flash as torch
1735 * mode, which we would like to turn on and off when we switching in and
1736 * out to the preview.
1737 *
1738 * @param enable Whether torch mode can be enabled.
1739 */
1740 private void enableTorchMode(boolean enable) {
1741 if (mParameters.getFlashMode() == null) {
1742 return;
1743 }
1744
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001745 SettingsManager settingsManager = mActivity.getSettingsManager();
1746
ztenghui7b265a62013-09-09 14:58:44 -07001747 String flashMode;
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001748 if (enable) {
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001749 flashMode = settingsManager.get(SettingsManager.SETTING_VIDEOCAMERA_FLASH_MODE);
ztenghui7b265a62013-09-09 14:58:44 -07001750 } else {
1751 flashMode = Parameters.FLASH_MODE_OFF;
1752 }
1753 List<String> supportedFlash = mParameters.getSupportedFlashModes();
1754 if (isSupported(flashMode, supportedFlash)) {
1755 mParameters.setFlashMode(flashMode);
1756 } else {
1757 flashMode = mParameters.getFlashMode();
1758 if (flashMode == null) {
1759 flashMode = mActivity.getString(
1760 R.string.pref_camera_flashmode_no_flash);
Angus Kongfaaee012013-12-07 00:38:46 -08001761 mParameters.setFlashMode(flashMode);
Michael Kolb8872c232013-01-29 10:33:22 -08001762 }
Michael Kolb8872c232013-01-29 10:33:22 -08001763 }
ztenghui7b265a62013-09-09 14:58:44 -07001764 mCameraDevice.setParameters(mParameters);
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001765 mUI.updateOnScreenIndicators(mParameters);
ztenghui7b265a62013-09-09 14:58:44 -07001766 }
1767
Michael Kolb8872c232013-01-29 10:33:22 -08001768 @Override
Sascha Haeberling8c1a9222014-02-25 09:38:06 -08001769 public void onPreviewVisibilityChanged(int visibility) {
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001770 if (mPreviewing) {
Sascha Haeberling8c1a9222014-02-25 09:38:06 -08001771 enableTorchMode(visibility == ModuleController.VISIBILITY_VISIBLE);
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001772 }
Erin Dahlgren3044d8c2013-10-10 18:23:45 -07001773 }
1774
Angus Kong9ef99252013-07-18 18:04:19 -07001775 private final class JpegPictureCallback implements CameraPictureCallback {
Michael Kolb8872c232013-01-29 10:33:22 -08001776 Location mLocation;
1777
1778 public JpegPictureCallback(Location loc) {
1779 mLocation = loc;
1780 }
1781
1782 @Override
Angus Kong9ef99252013-07-18 18:04:19 -07001783 public void onPictureTaken(byte [] jpegData, CameraProxy camera) {
Doris Liu057b21a2014-04-25 17:35:27 -07001784 Log.i(TAG, "Video snapshot taken.");
Michael Kolb8872c232013-01-29 10:33:22 -08001785 mSnapshotInProgress = false;
1786 showVideoSnapshotUI(false);
1787 storeImage(jpegData, mLocation);
1788 }
1789 }
1790
1791 private void storeImage(final byte[] data, Location loc) {
1792 long dateTaken = System.currentTimeMillis();
Angus Kongb50b5cb2013-08-09 14:55:20 -07001793 String title = CameraUtil.createJpegName(dateTaken);
Angus Kong0d00a892013-03-26 11:40:40 -07001794 ExifInterface exif = Exif.getExif(data);
1795 int orientation = Exif.getOrientation(exif);
Doris Liu6df2d962013-08-20 16:31:29 -07001796
Andy Huibers10c58162014-03-29 14:06:54 -07001797 int zoomIndex = mParameters.getZoom();
1798 float zoomValue = 0.01f * mParameters.getZoomRatios().get(zoomIndex);
1799 UsageStatistics.instance().photoCaptureDoneEvent(
1800 eventprotos.NavigationChange.Mode.VIDEO_STILL, title + ".jpeg", exif,
1801 isCameraFrontFacing(), false, zoomValue);
1802
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08001803 getServices().getMediaSaver().addImage(
Doris Liu6df2d962013-08-20 16:31:29 -07001804 data, title, dateTaken, loc, orientation,
Angus Kong83a99ae2013-04-17 15:37:07 -07001805 exif, mOnPhotoSavedListener, mContentResolver);
Michael Kolb8872c232013-01-29 10:33:22 -08001806 }
1807
Michael Kolb8872c232013-01-29 10:33:22 -08001808 private String convertOutputFormatToMimeType(int outputFileFormat) {
1809 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1810 return "video/mp4";
1811 }
1812 return "video/3gpp";
1813 }
1814
1815 private String convertOutputFormatToFileExt(int outputFileFormat) {
1816 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1817 return ".mp4";
1818 }
1819 return ".3gp";
1820 }
1821
1822 private void closeVideoFileDescriptor() {
1823 if (mVideoFileDescriptor != null) {
1824 try {
1825 mVideoFileDescriptor.close();
1826 } catch (IOException e) {
1827 Log.e(TAG, "Fail to close fd", e);
1828 }
1829 mVideoFileDescriptor = null;
1830 }
1831 }
1832
Michael Kolb8872c232013-01-29 10:33:22 -08001833 @Override
Angus Kong395ee2d2013-07-15 12:42:41 -07001834 public void onPreviewUIReady() {
1835 startPreview();
1836 }
1837
1838 @Override
1839 public void onPreviewUIDestroyed() {
1840 stopPreview();
1841 }
Angus Kong20fad242013-11-11 18:23:46 -08001842
Doris Liu1dfe7822013-12-12 00:02:08 -08001843 @Override
1844 public void startPreCaptureAnimation() {
1845 mAppController.startPreCaptureAnimation();
1846 }
1847
Angus Kong20fad242013-11-11 18:23:46 -08001848 private void requestCamera(int id) {
1849 mActivity.getCameraProvider().requestCamera(id);
1850 }
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -08001851
1852 @Override
1853 public void onMemoryStateChanged(int state) {
Erin Dahlgren667630d2014-04-01 14:03:25 -07001854 mAppController.setShutterEnabled(state == MemoryManager.STATE_OK);
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -08001855 }
1856
1857 @Override
1858 public void onLowMemory() {
1859 // Not much we can do in the video module.
1860 }
1861
Doris Liua1ec04a2014-01-13 17:29:40 -08001862 /***********************FocusOverlayManager Listener****************************/
1863 @Override
1864 public void autoFocus() {
1865 mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
1866 }
1867
1868 @Override
1869 public void cancelAutoFocus() {
1870 mCameraDevice.cancelAutoFocus();
1871 setFocusParameters();
1872 }
1873
1874 @Override
1875 public boolean capture() {
1876 return false;
1877 }
1878
1879 @Override
1880 public void startFaceDetection() {
1881
1882 }
1883
1884 @Override
1885 public void stopFaceDetection() {
1886
1887 }
1888
1889 @Override
1890 public void setFocusParameters() {
1891 updateFocusParameters();
1892 mCameraDevice.setParameters(mParameters);
1893 }
1894
Michael Kolb8872c232013-01-29 10:33:22 -08001895}