blob: bb3125dc49764f469a8f0739acf8b19793f7d873 [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.location.Location;
Marco Nelissen20694b22013-10-29 15:27:24 -070034import android.media.AudioManager;
Michael Kolb8872c232013-01-29 10:33:22 -080035import android.media.CamcorderProfile;
36import android.media.CameraProfile;
37import android.media.MediaRecorder;
38import android.net.Uri;
39import android.os.Build;
40import android.os.Bundle;
41import android.os.Handler;
42import android.os.Message;
43import android.os.ParcelFileDescriptor;
44import android.os.SystemClock;
45import android.provider.MediaStore;
Ruben Brunk16007962013-04-19 15:27:57 -070046import android.provider.MediaStore.MediaColumns;
Michael Kolb8872c232013-01-29 10:33:22 -080047import android.provider.MediaStore.Video;
Michael Kolb8872c232013-01-29 10:33:22 -080048import android.view.KeyEvent;
Michael Kolb8872c232013-01-29 10:33:22 -080049import android.view.OrientationEventListener;
Michael Kolb8872c232013-01-29 10:33:22 -080050import android.view.View;
Michael Kolb8872c232013-01-29 10:33:22 -080051import android.widget.Toast;
52
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080053import com.android.camera.app.AppController;
Spike Sprague39f8a762014-01-13 14:22:18 -080054import com.android.camera.app.CameraAppUI;
Kevin Gabayanffbc43c2013-12-09 11:41:50 -080055import com.android.camera.app.LocationManager;
Angus Kongfd4fc0e2013-11-07 15:38:09 -080056import com.android.camera.app.MediaSaver;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080057import com.android.camera.app.MemoryManager;
58import com.android.camera.app.MemoryManager.MemoryListener;
Sascha Haeberlingf9cba4e2014-04-22 09:11:28 -070059import com.android.camera.cameradevice.CameraManager;
60import com.android.camera.cameradevice.CameraManager.CameraPictureCallback;
61import com.android.camera.cameradevice.CameraManager.CameraProxy;
Angus Kong2bca2102014-03-11 16:27:30 -070062import com.android.camera.debug.Log;
ztenghuia16e7b52013-08-23 11:47:56 -070063import com.android.camera.exif.ExifInterface;
Erin Dahlgrenb1641f52014-01-14 15:58:52 -080064import com.android.camera.hardware.HardwareSpec;
65import com.android.camera.hardware.HardwareSpecImpl;
Angus Kong20fad242013-11-11 18:23:46 -080066import com.android.camera.module.ModuleController;
Erin Dahlgrenbd3da262013-12-02 11:48:13 -080067import com.android.camera.settings.SettingsManager;
Sascha Haeberlingde303232014-02-07 02:30:53 +010068import com.android.camera.settings.SettingsUtil;
Sascha Haeberling638e6f02013-09-18 14:28:51 -070069import com.android.camera.util.ApiHelper;
Angus Kongb50b5cb2013-08-09 14:55:20 -070070import com.android.camera.util.CameraUtil;
Angus Kong63424662014-04-23 10:47:47 -070071import com.android.camera.util.Size;
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 Kong2bca2102014-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;
Spike Sprague3f673792014-04-14 16:16:27 -0700326
Spike Sprague48a38062014-04-29 18:04:02 -0700327 mActivity.updateStorageSpaceAndHint(null);
Spike Sprague3f673792014-04-14 16:16:27 -0700328
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100329 mUI = new VideoUI(mActivity, this, mActivity.getModuleLayoutRoot());
330 mActivity.setPreviewStatusListener(mUI);
Michael Kolb8872c232013-01-29 10:33:22 -0800331
Erin Dahlgrenbd3da262013-12-02 11:48:13 -0800332 SettingsManager settingsManager = mActivity.getSettingsManager();
333 mCameraId = Integer.parseInt(settingsManager.get(SettingsManager.SETTING_CAMERA_ID));
Michael Kolb8872c232013-01-29 10:33:22 -0800334
Michael Kolb8872c232013-01-29 10:33:22 -0800335 /*
336 * To reduce startup time, we start the preview in another thread.
337 * We make sure the preview is started at the end of onCreate.
338 */
Angus Kong20fad242013-11-11 18:23:46 -0800339 requestCamera(mCameraId);
Michael Kolb8872c232013-01-29 10:33:22 -0800340
341 mContentResolver = mActivity.getContentResolver();
342
Michael Kolb8872c232013-01-29 10:33:22 -0800343 // Surface texture is from camera screen nail and startPreview needs it.
344 // This must be done before startPreview.
345 mIsVideoCaptureIntent = isVideoCaptureIntent();
Michael Kolb8872c232013-01-29 10:33:22 -0800346
Michael Kolb8872c232013-01-29 10:33:22 -0800347 mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
Erin Dahlgren21c21a62013-11-19 16:37:38 -0800348 mLocationManager = mActivity.getLocationManager();
Michael Kolb8872c232013-01-29 10:33:22 -0800349
Doris Liu6827ce22013-03-12 19:24:28 -0700350 mUI.setOrientationIndicator(0, false);
Michael Kolb8872c232013-01-29 10:33:22 -0800351 setDisplayOrientation();
352
Doris Liu6827ce22013-03-12 19:24:28 -0700353 mUI.showTimeLapseUI(mCaptureTimeLapse);
Michael Kolb8872c232013-01-29 10:33:22 -0800354 mPendingSwitchCameraId = -1;
Spike Sprague41f68002014-01-24 16:59:25 -0800355
356 mShutterIconId = CameraUtil.getCameraShutterIconId(
357 mAppController.getCurrentModuleIndex(), mAppController.getAndroidContext());
Doris Liu6827ce22013-03-12 19:24:28 -0700358 }
359
Erin Dahlgren4efa8b52013-12-17 18:31:35 -0800360 @Override
361 public boolean isUsingBottomBar() {
362 return true;
363 }
364
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800365 private void initializeControlByIntent() {
366 if (isVideoCaptureIntent()) {
Spike Sprague51c877c2014-02-18 11:14:12 -0800367 if (!mDontResetIntentUiOnResume) {
368 mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
369 }
370 // reset the flag
371 mDontResetIntentUiOnResume = false;
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800372 }
373 }
374
Doris Liu6827ce22013-03-12 19:24:28 -0700375 @Override
376 public void onSingleTapUp(View view, int x, int y) {
Doris Liu057b21a2014-04-25 17:35:27 -0700377 if (mPaused || mCameraDevice == null) {
378 return;
379 }
380 if (mMediaRecorderRecording) {
381 if (!mSnapshotInProgress) {
382 takeASnapshot();
383 }
Doris Liua1ec04a2014-01-13 17:29:40 -0800384 return;
385 }
386 // Check if metering area or focus area is supported.
387 if (!mFocusAreaSupported && !mMeteringAreaSupported) {
388 return;
389 }
390 // Tap to focus.
391 mFocusManager.onSingleTapUp(x, y);
Doris Liu38605742013-08-13 15:01:52 -0700392 }
393
394 private void takeASnapshot() {
Sascha Haeberlinga514b142013-08-13 16:10:01 -0700395 // Only take snapshots if video snapshot is supported by device
Doris Liu46836732014-04-29 18:15:55 -0700396 if(!mParameters.isVideoSnapshotSupported()) {
397 Log.w(TAG, "Cannot take a video snapshot - not supported by hardware");
398 return;
399 }
400 if (!mIsVideoCaptureIntent) {
Erin Dahlgren667630d2014-04-01 14:03:25 -0700401 if (!mMediaRecorderRecording || mPaused || mSnapshotInProgress
Doris Liu057b21a2014-04-25 17:35:27 -0700402 || !mAppController.isShutterEnabled()) {
Doris Liu38605742013-08-13 15:01:52 -0700403 return;
404 }
405
406 // Set rotation and gps data.
Sascha Haeberlinga7cbfc02014-02-14 11:06:03 +0100407 CameraInfo info = mActivity.getCameraProvider().getCameraInfo()[mCameraId];
408 int rotation = CameraUtil.getJpegRotation(info, mOrientation);
Doris Liu38605742013-08-13 15:01:52 -0700409 mParameters.setRotation(rotation);
410 Location loc = mLocationManager.getCurrentLocation();
411 CameraUtil.setGpsParameters(mParameters, loc);
412 mCameraDevice.setParameters(mParameters);
413
Doris Liu057b21a2014-04-25 17:35:27 -0700414 Log.i(TAG, "Video snapshot start");
Doris Liu38605742013-08-13 15:01:52 -0700415 mCameraDevice.takePicture(mHandler,
416 null, null, null, new JpegPictureCallback(loc));
417 showVideoSnapshotUI(true);
418 mSnapshotInProgress = true;
Doris Liu6827ce22013-03-12 19:24:28 -0700419 }
Michael Kolb8872c232013-01-29 10:33:22 -0800420 }
421
Doris Liua1ec04a2014-01-13 17:29:40 -0800422 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
423 private void updateAutoFocusMoveCallback() {
Sascha Haeberling8793eff2014-01-15 16:33:59 -0800424 if (mPaused) {
425 return;
426 }
427
Doris Liua1ec04a2014-01-13 17:29:40 -0800428 if (mParameters.getFocusMode().equals(CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE)) {
429 mCameraDevice.setAutoFocusMoveCallback(mHandler,
430 (CameraManager.CameraAFMoveCallback) mAutoFocusMoveCallback);
431 } else {
432 mCameraDevice.setAutoFocusMoveCallback(null, null);
433 }
434 }
435
436 /**
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700437 * @return Whether the currently active camera is front-facing.
438 */
439 private boolean isCameraFrontFacing() {
440 CameraInfo info = mAppController.getCameraProvider().getCameraInfo()[mCameraId];
441 return info.facing == CameraInfo.CAMERA_FACING_FRONT;
442 }
443
444 /**
Doris Liua1ec04a2014-01-13 17:29:40 -0800445 * The focus manager gets initialized after camera is available.
446 */
447 private void initializeFocusManager() {
448 // Create FocusManager object. startPreview needs it.
449 // if mFocusManager not null, reuse it
450 // otherwise create a new instance
451 if (mFocusManager != null) {
452 mFocusManager.removeMessages();
453 } else {
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700454 mMirror = isCameraFrontFacing();
Doris Liua1ec04a2014-01-13 17:29:40 -0800455 String[] defaultFocusModes = mActivity.getResources().getStringArray(
456 R.array.pref_camera_focusmode_default_array);
457 mFocusManager = new FocusOverlayManager(mActivity.getSettingsManager(),
458 defaultFocusModes,
459 mInitialParams, this, mMirror,
460 mActivity.getMainLooper(), mUI.getFocusUI());
461 }
462 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
463 }
464
Michael Kolb8872c232013-01-29 10:33:22 -0800465 @Override
466 public void onOrientationChanged(int orientation) {
467 // We keep the last known orientation. So if the user first orient
468 // the camera then point the camera to floor or sky, we still have
469 // the correct orientation.
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100470 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
471 return;
472 }
Angus Kongb50b5cb2013-08-09 14:55:20 -0700473 int newOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800474
475 if (mOrientation != newOrientation) {
476 mOrientation = newOrientation;
Michael Kolb8872c232013-01-29 10:33:22 -0800477 }
Doris Liu8dc878f2014-05-05 14:10:34 -0700478 mUI.onOrientationChanged(orientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800479
Michael Kolb8872c232013-01-29 10:33:22 -0800480 }
481
Erin Dahlgren0a6a8d82014-01-09 22:17:38 -0800482 private final ButtonManager.ButtonCallback mFlashCallback =
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -0800483 new ButtonManager.ButtonCallback() {
484 @Override
485 public void onStateChanged(int state) {
486 // Update flash parameters.
487 enableTorchMode(true);
488 }
489 };
490
Erin Dahlgren0a6a8d82014-01-09 22:17:38 -0800491 private final ButtonManager.ButtonCallback mCameraCallback =
Erin Dahlgren18e2ef62013-12-05 14:53:38 -0800492 new ButtonManager.ButtonCallback() {
493 @Override
494 public void onStateChanged(int state) {
Angus Kong97e282a2014-03-04 18:44:49 -0800495 if (mPaused || mAppController.getCameraProvider().waitingForCamera()) {
Erin Dahlgren18e2ef62013-12-05 14:53:38 -0800496 return;
497 }
498 mPendingSwitchCameraId = state;
499 Log.d(TAG, "Start to copy texture.");
500
501 // Disable all camera controls.
502 mSwitchingCamera = true;
503 switchCamera();
504 }
505 };
506
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800507 private final View.OnClickListener mCancelCallback = new View.OnClickListener() {
508 @Override
509 public void onClick(View v) {
510 onReviewCancelClicked(v);
511 }
512 };
513
514 private final View.OnClickListener mDoneCallback = new View.OnClickListener() {
515 @Override
516 public void onClick(View v) {
517 onReviewDoneClicked(v);
518 }
519 };
520 private final View.OnClickListener mReviewCallback = new View.OnClickListener() {
521 @Override
522 public void onClick(View v) {
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800523 onReviewPlayClicked(v);
524 }
525 };
526
Angus Kong20fad242013-11-11 18:23:46 -0800527 @Override
Erin Dahlgren1ca516f2014-03-28 12:44:04 -0700528 public void hardResetSettings(SettingsManager settingsManager) {
529 // VideoModule does not need to hard reset any settings.
530 }
531
532 @Override
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800533 public HardwareSpec getHardwareSpec() {
Erin Dahlgren49ab9222014-01-28 17:40:28 -0800534 return (mParameters != null ? new HardwareSpecImpl(mParameters) : null);
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800535 }
536
537 @Override
538 public CameraAppUI.BottomBarUISpec getBottomBarSpec() {
539 CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
540
541 bottomBarSpec.enableCamera = true;
542 bottomBarSpec.cameraCallback = mCameraCallback;
543 bottomBarSpec.enableTorchFlash = true;
544 bottomBarSpec.flashCallback = mFlashCallback;
545 bottomBarSpec.hideHdr = true;
Spike Spragueab3adde2014-03-10 12:19:08 -0700546 bottomBarSpec.enableGridLines = true;
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800547
548 if (isVideoCaptureIntent()) {
549 bottomBarSpec.showCancel = true;
550 bottomBarSpec.cancelCallback = mCancelCallback;
551 bottomBarSpec.showDone = true;
552 bottomBarSpec.doneCallback = mDoneCallback;
553 bottomBarSpec.showReview = true;
554 bottomBarSpec.reviewCallback = mReviewCallback;
555 }
556
557 return bottomBarSpec;
Erin Dahlgren0a6a8d82014-01-09 22:17:38 -0800558 }
559
560 @Override
Angus Kong20fad242013-11-11 18:23:46 -0800561 public void onCameraAvailable(CameraProxy cameraProxy) {
562 mCameraDevice = cameraProxy;
Doris Liua1ec04a2014-01-13 17:29:40 -0800563 mInitialParams = mCameraDevice.getParameters();
564 mFocusAreaSupported = CameraUtil.isFocusAreaSupported(mInitialParams);
565 mMeteringAreaSupported = CameraUtil.isMeteringAreaSupported(mInitialParams);
Angus Kong20fad242013-11-11 18:23:46 -0800566 readVideoPreferences();
567 resizeForPreviewAspectRatio();
Doris Liua1ec04a2014-01-13 17:29:40 -0800568 initializeFocusManager();
Doris Liu5dd85a12014-04-15 11:45:55 -0700569 // TODO: Having focus overlay manager caching the parameters is prone to error,
570 // we should consider passing the parameters to focus overlay to ensure the
571 // parameters are up to date.
572 mFocusManager.setParameters(mInitialParams);
Doris Liu5a367542014-01-17 17:21:42 -0800573
Angus Kong20fad242013-11-11 18:23:46 -0800574 startPreview();
575 initializeVideoSnapshot();
Angus Kong20fad242013-11-11 18:23:46 -0800576 mUI.initializeZoom(mParameters);
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800577 initializeControlByIntent();
Angus Kong20fad242013-11-11 18:23:46 -0800578 }
579
Michael Kolb8872c232013-01-29 10:33:22 -0800580 private void startPlayVideoActivity() {
581 Intent intent = new Intent(Intent.ACTION_VIEW);
582 intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
583 try {
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700584 mActivity
585 .startActivityForResult(intent, CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
Michael Kolb8872c232013-01-29 10:33:22 -0800586 } catch (ActivityNotFoundException ex) {
587 Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
588 }
589 }
590
ztenghui7b265a62013-09-09 14:58:44 -0700591 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800592 @OnClickAttr
593 public void onReviewPlayClicked(View v) {
594 startPlayVideoActivity();
595 }
596
ztenghui7b265a62013-09-09 14:58:44 -0700597 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800598 @OnClickAttr
599 public void onReviewDoneClicked(View v) {
Doris Liu69ef5ea2013-05-07 13:48:10 -0700600 mIsInReviewMode = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800601 doReturnToCaller(true);
602 }
603
ztenghui7b265a62013-09-09 14:58:44 -0700604 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800605 @OnClickAttr
606 public void onReviewCancelClicked(View v) {
ztenghuiaf3a1972013-09-17 16:51:15 -0700607 // TODO: It should be better to not even insert the URI at all before we
608 // confirm done in review, which means we need to handle temporary video
609 // files in a quite different way than we currently had.
ztenghui70bd0242013-10-14 11:15:44 -0700610 // Make sure we don't delete the Uri sent from the video capture intent.
611 if (mCurrentVideoUriFromMediaSaved) {
ztenghuidfe5b152013-10-08 17:07:15 -0700612 mContentResolver.delete(mCurrentVideoUri, null, null);
613 }
ztenghui638bf9a2013-10-09 10:52:33 -0700614 mIsInReviewMode = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800615 doReturnToCaller(false);
616 }
617
Doris Liu69ef5ea2013-05-07 13:48:10 -0700618 @Override
619 public boolean isInReviewMode() {
620 return mIsInReviewMode;
621 }
622
Michael Kolb8872c232013-01-29 10:33:22 -0800623 private void onStopVideoRecording() {
Sascha Haeberling8793eff2014-01-15 16:33:59 -0800624 mAppController.getCameraAppUI().setSwipeEnabled(true);
Michael Kolb8872c232013-01-29 10:33:22 -0800625 boolean recordFail = stopVideoRecording();
626 if (mIsVideoCaptureIntent) {
Doris Liu3973deb2013-08-21 13:42:22 -0700627 if (mQuickCapture) {
628 doReturnToCaller(!recordFail);
629 } else if (!recordFail) {
630 showCaptureResult();
Michael Kolb8872c232013-01-29 10:33:22 -0800631 }
632 } else if (!recordFail){
633 // Start capture animation.
634 if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
635 // The capture animation is disabled on ICS because we use SurfaceView
636 // for preview during recording. When the recording is done, we switch
637 // back to use SurfaceTexture for preview and we need to stop then start
638 // the preview. This will cause the preview flicker since the preview
639 // will not be continuous for a short period of time.
Sascha Haeberling4f91ab52013-05-21 11:26:13 -0700640
Sascha Haeberling37f36112013-08-06 14:31:52 -0700641 mUI.animateFlash();
Michael Kolb8872c232013-01-29 10:33:22 -0800642 }
643 }
644 }
645
Doris Liu2a7f44c2013-08-12 15:18:53 -0700646 public void onVideoSaved() {
647 if (mIsVideoCaptureIntent) {
648 showCaptureResult();
649 }
650 }
651
Michael Kolb8872c232013-01-29 10:33:22 -0800652 public void onProtectiveCurtainClick(View v) {
653 // Consume clicks
654 }
655
656 @Override
657 public void onShutterButtonClick() {
Sascha Haeberling8793eff2014-01-15 16:33:59 -0800658 if (mSwitchingCamera) {
659 return;
660 }
Michael Kolb8872c232013-01-29 10:33:22 -0800661 boolean stop = mMediaRecorderRecording;
662
663 if (stop) {
664 onStopVideoRecording();
665 } else {
666 startVideoRecording();
667 }
Erin Dahlgren667630d2014-04-01 14:03:25 -0700668 mAppController.setShutterEnabled(false);
Doris Liua1ec04a2014-01-13 17:29:40 -0800669 mFocusManager.onShutterUp();
Michael Kolb8872c232013-01-29 10:33:22 -0800670
671 // Keep the shutter button disabled when in video capture intent
672 // mode and recording is stopped. It'll be re-enabled when
673 // re-take button is clicked.
674 if (!(mIsVideoCaptureIntent && stop)) {
Angus Kong13e87c42013-11-25 10:02:47 -0800675 mHandler.sendEmptyMessageDelayed(MSG_ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
Michael Kolb8872c232013-01-29 10:33:22 -0800676 }
677 }
678
679 @Override
680 public void onShutterButtonFocus(boolean pressed) {
Doris Liuf55f3c42013-11-20 00:24:46 -0800681 // TODO: Remove this when old camera controls are removed from the UI.
Michael Kolb8872c232013-01-29 10:33:22 -0800682 }
683
684 private void readVideoPreferences() {
685 // The preference stores values from ListPreference and is thus string type for all values.
686 // We need to convert it to int manually.
Erin Dahlgrenbd3da262013-12-02 11:48:13 -0800687 SettingsManager settingsManager = mActivity.getSettingsManager();
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700688 if (!settingsManager.isSet(SettingsManager.SETTING_VIDEO_QUALITY_BACK)) {
689 settingsManager.setDefault(SettingsManager.SETTING_VIDEO_QUALITY_BACK);
Doris Liu3f7e0042013-07-31 11:25:09 -0700690 }
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700691 if (!settingsManager.isSet(SettingsManager.SETTING_VIDEO_QUALITY_FRONT)) {
692 settingsManager.setDefault(SettingsManager.SETTING_VIDEO_QUALITY_FRONT);
693 }
694 String videoQuality = settingsManager
695 .get(isCameraFrontFacing() ? SettingsManager.SETTING_VIDEO_QUALITY_FRONT
696 : SettingsManager.SETTING_VIDEO_QUALITY_BACK);
Sascha Haeberlingde303232014-02-07 02:30:53 +0100697 int quality = SettingsUtil.getVideoQuality(videoQuality, mCameraId);
698 Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality);
Michael Kolb8872c232013-01-29 10:33:22 -0800699
700 // Set video quality.
701 Intent intent = mActivity.getIntent();
702 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
703 int extraVideoQuality =
704 intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
705 if (extraVideoQuality > 0) {
706 quality = CamcorderProfile.QUALITY_HIGH;
707 } else { // 0 is mms.
708 quality = CamcorderProfile.QUALITY_LOW;
709 }
710 }
711
712 // Set video duration limit. The limit is read from the preference,
713 // unless it is specified in the intent.
714 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
715 int seconds =
716 intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
717 mMaxVideoDurationInMs = 1000 * seconds;
718 } else {
Sascha Haeberling4044ab72014-04-02 16:25:03 -0700719 mMaxVideoDurationInMs = SettingsManager.getMaxVideoDuration(mActivity
720 .getAndroidContext());
Michael Kolb8872c232013-01-29 10:33:22 -0800721 }
722
Sascha Haeberling8c000c22014-03-17 13:56:05 -0700723 // TODO: Uncomment this block to re-enable time-lapse.
724 /* // Read time lapse recording interval.
Erin Dahlgrenbd3da262013-12-02 11:48:13 -0800725 String frameIntervalStr = settingsManager.get(
726 SettingsManager.SETTING_VIDEO_TIME_LAPSE_FRAME_INTERVAL);
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700727 mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
728 mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
Michael Kolb8872c232013-01-29 10:33:22 -0800729 // TODO: This should be checked instead directly +1000.
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100730 if (mCaptureTimeLapse) {
731 quality += 1000;
Sascha Haeberling8c000c22014-03-17 13:56:05 -0700732 } */
Andy Huibers329e1012014-02-04 08:03:35 -0800733
734 // If quality is not supported, request QUALITY_HIGH which is always supported.
735 if (CamcorderProfile.hasProfile(mCameraId, quality) == false) {
736 quality = CamcorderProfile.QUALITY_HIGH;
737 }
Michael Kolb8872c232013-01-29 10:33:22 -0800738 mProfile = CamcorderProfile.get(mCameraId, quality);
Angus Kong395ee2d2013-07-15 12:42:41 -0700739 mPreferenceRead = true;
ztenghuief01a312013-10-14 15:25:16 -0700740 if (mCameraDevice == null) {
741 return;
742 }
Doris Liu6432cd62013-06-13 17:20:31 -0700743 mParameters = mCameraDevice.getParameters();
Angus Kong5f8c30e2014-03-06 17:15:08 -0800744 Point desiredPreviewSize = getDesiredPreviewSize(mAppController.getAndroidContext(),
745 mParameters, mProfile, mUI.getPreviewScreenSize());
746 mDesiredPreviewWidth = desiredPreviewSize.x;
747 mDesiredPreviewHeight = desiredPreviewSize.y;
Doris Liu6432cd62013-06-13 17:20:31 -0700748 mUI.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800749 Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
750 ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
751 }
752
Angus Kong5f8c30e2014-03-06 17:15:08 -0800753 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
754 /**
755 * Calculates the preview size and stores it in mDesiredPreviewWidth and
756 * mDesiredPreviewHeight. This function checks {@link
757 * android.hardware.Camera.Parameters#getPreferredPreviewSizeForVideo()}
758 * but also considers the current preview area size on screen and make sure
759 * the final preview size will not be smaller than 1/2 of the current
760 * on screen preview area in terms of their short sides.
761 *
762 * @return The preferred preview size or {@code null} if the camera is not
763 * opened yet.
764 */
765 private static Point getDesiredPreviewSize(Context context, Parameters parameters,
766 CamcorderProfile profile, Point previewScreenSize) {
767 if (parameters.getSupportedVideoSizes() == null) {
768 // Driver doesn't support separate outputs for preview and video.
769 return new Point(profile.videoFrameWidth, profile.videoFrameHeight);
770 }
771
772 final int previewScreenShortSide = (previewScreenSize.x < previewScreenSize.y ?
773 previewScreenSize.x : previewScreenSize.y);
Angus Kong63424662014-04-23 10:47:47 -0700774 List<Size> sizes = Size.buildListFromCameraSizes(parameters.getSupportedPreviewSizes());
775 Size preferred = new Size(parameters.getPreferredPreviewSizeForVideo());
Angus Kong00b7b102014-04-24 15:46:52 -0700776 final int preferredPreviewSizeShortSide = (preferred.width() < preferred.height() ?
777 preferred.width() : preferred.height());
Angus Kong5f8c30e2014-03-06 17:15:08 -0800778 if (preferredPreviewSizeShortSide * 2 < previewScreenShortSide) {
Angus Kong63424662014-04-23 10:47:47 -0700779 preferred = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
Angus Kong5f8c30e2014-03-06 17:15:08 -0800780 }
Angus Kong00b7b102014-04-24 15:46:52 -0700781 int product = preferred.width() * preferred.height();
Angus Kong5f8c30e2014-03-06 17:15:08 -0800782 Iterator<Size> it = sizes.iterator();
783 // Remove the preview sizes that are not preferred.
784 while (it.hasNext()) {
785 Size size = it.next();
Angus Kong00b7b102014-04-24 15:46:52 -0700786 if (size.width() * size.height() > product) {
Angus Kong5f8c30e2014-03-06 17:15:08 -0800787 it.remove();
788 }
789 }
790 Size optimalSize = CameraUtil.getOptimalPreviewSize(context, sizes,
791 (double) profile.videoFrameWidth / profile.videoFrameHeight);
Angus Kong00b7b102014-04-24 15:46:52 -0700792 return new Point(optimalSize.width(), optimalSize.height());
Angus Kong5f8c30e2014-03-06 17:15:08 -0800793 }
794
Michael Kolb8872c232013-01-29 10:33:22 -0800795 private void resizeForPreviewAspectRatio() {
Doris Liue038c162013-12-13 23:06:11 -0800796 mUI.setAspectRatio((float) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800797 }
798
Angus Kong13e87c42013-11-25 10:02:47 -0800799 private void installIntentFilter() {
Michael Kolb8872c232013-01-29 10:33:22 -0800800 // install an intent filter to receive SD card related events.
801 IntentFilter intentFilter =
802 new IntentFilter(Intent.ACTION_MEDIA_EJECT);
803 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
804 intentFilter.addDataScheme("file");
805 mReceiver = new MyBroadcastReceiver();
806 mActivity.registerReceiver(mReceiver, intentFilter);
807 }
808
Michael Kolb8872c232013-01-29 10:33:22 -0800809 private void setDisplayOrientation() {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700810 mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
811 mCameraDisplayOrientation = CameraUtil.getDisplayOrientation(mDisplayRotation, mCameraId);
Doris Liu6432cd62013-06-13 17:20:31 -0700812 // Change the camera display orientation
813 if (mCameraDevice != null) {
814 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800815 }
Doris Liua1ec04a2014-01-13 17:29:40 -0800816 if (mFocusManager != null) {
817 mFocusManager.setDisplayOrientation(mCameraDisplayOrientation);
818 }
Doris Liu6432cd62013-06-13 17:20:31 -0700819 }
820
821 @Override
822 public void updateCameraOrientation() {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100823 if (mMediaRecorderRecording) {
824 return;
825 }
Angus Kongb50b5cb2013-08-09 14:55:20 -0700826 if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
Doris Liu6432cd62013-06-13 17:20:31 -0700827 setDisplayOrientation();
828 }
Michael Kolb8872c232013-01-29 10:33:22 -0800829 }
830
Doris Liu6827ce22013-03-12 19:24:28 -0700831 @Override
Doris Liu70da9182013-12-17 18:41:15 -0800832 public void updatePreviewAspectRatio(float aspectRatio) {
833 mAppController.updatePreviewAspectRatio(aspectRatio);
834 }
835
836 @Override
Doris Liu6827ce22013-03-12 19:24:28 -0700837 public int onZoomChanged(int index) {
838 // Not useful to change zoom value when the activity is paused.
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100839 if (mPaused) {
840 return index;
841 }
Doris Liu6827ce22013-03-12 19:24:28 -0700842 mZoomValue = index;
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100843 if (mParameters == null || mCameraDevice == null) {
844 return index;
845 }
Doris Liu6827ce22013-03-12 19:24:28 -0700846 // Set zoom parameters asynchronously
847 mParameters.setZoom(mZoomValue);
Doris Liu6432cd62013-06-13 17:20:31 -0700848 mCameraDevice.setParameters(mParameters);
849 Parameters p = mCameraDevice.getParameters();
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100850 if (p != null) {
851 return p.getZoom();
852 }
Doris Liu6827ce22013-03-12 19:24:28 -0700853 return index;
854 }
Angus Kong395ee2d2013-07-15 12:42:41 -0700855
Michael Kolb8872c232013-01-29 10:33:22 -0800856 private void startPreview() {
Alan Newbergerd41766f2014-04-09 18:25:34 -0700857 Log.i(TAG, "startPreview");
Michael Kolb8872c232013-01-29 10:33:22 -0800858
Erin Dahlgrend8de0772014-02-03 10:12:27 -0800859 SurfaceTexture surfaceTexture = mActivity.getCameraAppUI().getSurfaceTexture();
ztenghuief01a312013-10-14 15:25:16 -0700860 if (!mPreferenceRead || surfaceTexture == null || mPaused == true ||
861 mCameraDevice == null) {
862 return;
863 }
Angus Kong395ee2d2013-07-15 12:42:41 -0700864
Angus Kong2bca2102014-03-11 16:27:30 -0700865 mCameraDevice.setErrorCallback(mHandler, mErrorCallback);
Michael Kolb8872c232013-01-29 10:33:22 -0800866 if (mPreviewing == true) {
867 stopPreview();
Michael Kolb8872c232013-01-29 10:33:22 -0800868 }
869
Michael Kolb8872c232013-01-29 10:33:22 -0800870 setDisplayOrientation();
Doris Liu6432cd62013-06-13 17:20:31 -0700871 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800872 setCameraParameters();
873
Doris Liua1ec04a2014-01-13 17:29:40 -0800874 if (mFocusManager != null) {
875 // If the focus mode is continuous autofocus, call cancelAutoFocus
876 // to resume it because it may have been paused by autoFocus call.
877 String focusMode = mFocusManager.getFocusMode();
878 if (CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE.equals(focusMode)) {
879 mCameraDevice.cancelAutoFocus();
880 }
881 }
Doris Liu5a367542014-01-17 17:21:42 -0800882
883 // This is to notify app controller that preview will start next, so app
884 // controller can set preview callbacks if needed. This has to happen before
885 // preview is started as a workaround of the framework issue related to preview
886 // callbacks that causes preview stretch and crash. (More details see b/12210027
887 // and b/12591410
888 mAppController.onPreviewReadyToStart();
Michael Kolb8872c232013-01-29 10:33:22 -0800889 try {
Doris Liu3973deb2013-08-21 13:42:22 -0700890 mCameraDevice.setPreviewTexture(surfaceTexture);
891 mCameraDevice.startPreview();
892 mPreviewing = true;
893 onPreviewStarted();
Michael Kolb8872c232013-01-29 10:33:22 -0800894 } catch (Throwable ex) {
895 closeCamera();
896 throw new RuntimeException("startPreview failed", ex);
Michael Kolb8872c232013-01-29 10:33:22 -0800897 }
Michael Kolbb1aeb392013-03-11 12:37:40 -0700898 }
899
900 private void onPreviewStarted() {
Erin Dahlgren667630d2014-04-01 14:03:25 -0700901 mAppController.setShutterEnabled(true);
Doris Liu2b906b82013-12-10 16:34:08 -0800902 mAppController.onPreviewStarted();
Doris Liua1ec04a2014-01-13 17:29:40 -0800903 if (mFocusManager != null) {
904 mFocusManager.onPreviewStarted();
905 }
Michael Kolb8872c232013-01-29 10:33:22 -0800906 }
907
Doris Liu6827ce22013-03-12 19:24:28 -0700908 @Override
Sameer Padaladb81ce62014-03-21 15:33:56 -0700909 public void onPreviewInitialDataReceived() {
Sameer Padaladb81ce62014-03-21 15:33:56 -0700910 }
911
912 @Override
Doris Liu6827ce22013-03-12 19:24:28 -0700913 public void stopPreview() {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100914 if (!mPreviewing) {
915 return;
916 }
Doris Liu6432cd62013-06-13 17:20:31 -0700917 mCameraDevice.stopPreview();
Doris Liua1ec04a2014-01-13 17:29:40 -0800918 if (mFocusManager != null) {
919 mFocusManager.onPreviewStopped();
920 }
Michael Kolb8872c232013-01-29 10:33:22 -0800921 mPreviewing = false;
922 }
923
Michael Kolb8872c232013-01-29 10:33:22 -0800924 private void closeCamera() {
Alan Newbergerd41766f2014-04-09 18:25:34 -0700925 Log.i(TAG, "closeCamera");
Doris Liu6432cd62013-06-13 17:20:31 -0700926 if (mCameraDevice == null) {
Michael Kolb8872c232013-01-29 10:33:22 -0800927 Log.d(TAG, "already stopped.");
928 return;
929 }
Doris Liu6432cd62013-06-13 17:20:31 -0700930 mCameraDevice.setZoomChangeListener(null);
Angus Kong2bca2102014-03-11 16:27:30 -0700931 mCameraDevice.setErrorCallback(null, null);
Angus Kong20fad242013-11-11 18:23:46 -0800932 mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
Angus Kong395ee2d2013-07-15 12:42:41 -0700933 mCameraDevice = null;
Michael Kolb8872c232013-01-29 10:33:22 -0800934 mPreviewing = false;
935 mSnapshotInProgress = false;
Doris Liua1ec04a2014-01-13 17:29:40 -0800936 if (mFocusManager != null) {
937 mFocusManager.onCameraReleased();
938 }
Michael Kolb8872c232013-01-29 10:33:22 -0800939 }
940
Michael Kolb8872c232013-01-29 10:33:22 -0800941 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800942 public boolean onBackPressed() {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100943 if (mPaused) {
944 return true;
945 }
Michael Kolb8872c232013-01-29 10:33:22 -0800946 if (mMediaRecorderRecording) {
947 onStopVideoRecording();
948 return true;
Michael Kolb8872c232013-01-29 10:33:22 -0800949 } else {
Doris Liuf9e4f8f2013-12-04 18:04:22 -0800950 return false;
Michael Kolb8872c232013-01-29 10:33:22 -0800951 }
952 }
953
954 @Override
955 public boolean onKeyDown(int keyCode, KeyEvent event) {
956 // Do not handle any key if the activity is paused.
957 if (mPaused) {
958 return true;
959 }
960
961 switch (keyCode) {
962 case KeyEvent.KEYCODE_CAMERA:
963 if (event.getRepeatCount() == 0) {
Erin Dahlgren667630d2014-04-01 14:03:25 -0700964 onShutterButtonClick();
Michael Kolb8872c232013-01-29 10:33:22 -0800965 return true;
966 }
Michael Kolb8872c232013-01-29 10:33:22 -0800967 case KeyEvent.KEYCODE_DPAD_CENTER:
968 if (event.getRepeatCount() == 0) {
Erin Dahlgren667630d2014-04-01 14:03:25 -0700969 onShutterButtonClick();
Michael Kolb8872c232013-01-29 10:33:22 -0800970 return true;
971 }
Michael Kolb8872c232013-01-29 10:33:22 -0800972 case KeyEvent.KEYCODE_MENU:
Erin Dahlgren15691af2014-03-14 14:10:57 -0700973 // Consume menu button presses during capture.
974 return mMediaRecorderRecording;
Michael Kolb8872c232013-01-29 10:33:22 -0800975 }
976 return false;
977 }
978
979 @Override
980 public boolean onKeyUp(int keyCode, KeyEvent event) {
981 switch (keyCode) {
982 case KeyEvent.KEYCODE_CAMERA:
Erin Dahlgren667630d2014-04-01 14:03:25 -0700983 onShutterButtonClick();
Michael Kolb8872c232013-01-29 10:33:22 -0800984 return true;
Erin Dahlgren15691af2014-03-14 14:10:57 -0700985 case KeyEvent.KEYCODE_MENU:
986 // Consume menu button presses during capture.
987 return mMediaRecorderRecording;
Michael Kolb8872c232013-01-29 10:33:22 -0800988 }
989 return false;
990 }
991
Doris Liu6827ce22013-03-12 19:24:28 -0700992 @Override
993 public boolean isVideoCaptureIntent() {
Michael Kolb8872c232013-01-29 10:33:22 -0800994 String action = mActivity.getIntent().getAction();
995 return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
996 }
997
998 private void doReturnToCaller(boolean valid) {
999 Intent resultIntent = new Intent();
1000 int resultCode;
1001 if (valid) {
1002 resultCode = Activity.RESULT_OK;
1003 resultIntent.setData(mCurrentVideoUri);
1004 } else {
1005 resultCode = Activity.RESULT_CANCELED;
1006 }
1007 mActivity.setResultEx(resultCode, resultIntent);
1008 mActivity.finish();
1009 }
1010
1011 private void cleanupEmptyFile() {
1012 if (mVideoFilename != null) {
1013 File f = new File(mVideoFilename);
1014 if (f.length() == 0 && f.delete()) {
1015 Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
1016 mVideoFilename = null;
1017 }
1018 }
1019 }
1020
Michael Kolb8872c232013-01-29 10:33:22 -08001021 // Prepares media recorder.
1022 private void initializeRecorder() {
Alan Newbergerd41766f2014-04-09 18:25:34 -07001023 Log.i(TAG, "initializeRecorder");
Michael Kolb8872c232013-01-29 10:33:22 -08001024 // If the mCameraDevice is null, then this activity is going to finish
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001025 if (mCameraDevice == null) {
1026 return;
1027 }
Michael Kolb8872c232013-01-29 10:33:22 -08001028
Michael Kolb8872c232013-01-29 10:33:22 -08001029 Intent intent = mActivity.getIntent();
1030 Bundle myExtras = intent.getExtras();
1031
1032 long requestedSizeLimit = 0;
1033 closeVideoFileDescriptor();
ztenghui70bd0242013-10-14 11:15:44 -07001034 mCurrentVideoUriFromMediaSaved = false;
Michael Kolb8872c232013-01-29 10:33:22 -08001035 if (mIsVideoCaptureIntent && myExtras != null) {
1036 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1037 if (saveUri != null) {
1038 try {
1039 mVideoFileDescriptor =
1040 mContentResolver.openFileDescriptor(saveUri, "rw");
1041 mCurrentVideoUri = saveUri;
1042 } catch (java.io.FileNotFoundException ex) {
1043 // invalid uri
1044 Log.e(TAG, ex.toString());
1045 }
1046 }
1047 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1048 }
1049 mMediaRecorder = new MediaRecorder();
1050
Michael Kolb8872c232013-01-29 10:33:22 -08001051 // Unlock the camera object before passing it to media recorder.
Doris Liu6432cd62013-06-13 17:20:31 -07001052 mCameraDevice.unlock();
Doris Liu6432cd62013-06-13 17:20:31 -07001053 mMediaRecorder.setCamera(mCameraDevice.getCamera());
Michael Kolb8872c232013-01-29 10:33:22 -08001054 if (!mCaptureTimeLapse) {
1055 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
1056 }
1057 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
1058 mMediaRecorder.setProfile(mProfile);
Michael Kolbf9542362013-05-30 07:45:41 -07001059 mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight);
Michael Kolb8872c232013-01-29 10:33:22 -08001060 mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
1061 if (mCaptureTimeLapse) {
1062 double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs;
1063 setCaptureRate(mMediaRecorder, fps);
1064 }
1065
1066 setRecordLocation();
1067
1068 // Set output file.
1069 // Try Uri in the intent first. If it doesn't exist, use our own
1070 // instead.
1071 if (mVideoFileDescriptor != null) {
1072 mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1073 } else {
1074 generateVideoFilename(mProfile.fileFormat);
1075 mMediaRecorder.setOutputFile(mVideoFilename);
1076 }
1077
1078 // Set maximum file size.
Angus Kong2dcc0a92013-09-25 13:00:08 -07001079 long maxFileSize = mActivity.getStorageSpaceBytes() - Storage.LOW_STORAGE_THRESHOLD_BYTES;
Michael Kolb8872c232013-01-29 10:33:22 -08001080 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1081 maxFileSize = requestedSizeLimit;
1082 }
1083
1084 try {
1085 mMediaRecorder.setMaxFileSize(maxFileSize);
1086 } catch (RuntimeException exception) {
1087 // We are going to ignore failure of setMaxFileSize here, as
1088 // a) The composer selected may simply not support it, or
1089 // b) The underlying media framework may not handle 64-bit range
1090 // on the size restriction.
1091 }
1092
1093 // See android.hardware.Camera.Parameters.setRotation for
1094 // documentation.
1095 // Note that mOrientation here is the device orientation, which is the opposite of
1096 // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
1097 // which is the orientation the graphics need to rotate in order to render correctly.
1098 int rotation = 0;
1099 if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
Angus Kong20fad242013-11-11 18:23:46 -08001100 CameraInfo info = mActivity.getCameraProvider().getCameraInfo()[mCameraId];
Sascha Haeberling6ccec202014-03-11 09:44:34 -07001101 if (isCameraFrontFacing()) {
Michael Kolb8872c232013-01-29 10:33:22 -08001102 rotation = (info.orientation - mOrientation + 360) % 360;
1103 } else { // back-facing camera
1104 rotation = (info.orientation + mOrientation) % 360;
1105 }
1106 }
1107 mMediaRecorder.setOrientationHint(rotation);
1108
1109 try {
1110 mMediaRecorder.prepare();
1111 } catch (IOException e) {
1112 Log.e(TAG, "prepare failed for " + mVideoFilename, e);
1113 releaseMediaRecorder();
1114 throw new RuntimeException(e);
1115 }
1116
1117 mMediaRecorder.setOnErrorListener(this);
1118 mMediaRecorder.setOnInfoListener(this);
1119 }
1120
Michael Kolb8872c232013-01-29 10:33:22 -08001121 private static void setCaptureRate(MediaRecorder recorder, double fps) {
1122 recorder.setCaptureRate(fps);
1123 }
1124
Michael Kolb8872c232013-01-29 10:33:22 -08001125 private void setRecordLocation() {
Sascha Haeberling638e6f02013-09-18 14:28:51 -07001126 Location loc = mLocationManager.getCurrentLocation();
1127 if (loc != null) {
1128 mMediaRecorder.setLocation((float) loc.getLatitude(),
1129 (float) loc.getLongitude());
Michael Kolb8872c232013-01-29 10:33:22 -08001130 }
1131 }
1132
Michael Kolb8872c232013-01-29 10:33:22 -08001133 private void releaseMediaRecorder() {
Alan Newbergerd41766f2014-04-09 18:25:34 -07001134 Log.i(TAG, "Releasing media recorder.");
Michael Kolb8872c232013-01-29 10:33:22 -08001135 if (mMediaRecorder != null) {
1136 cleanupEmptyFile();
1137 mMediaRecorder.reset();
1138 mMediaRecorder.release();
1139 mMediaRecorder = null;
1140 }
1141 mVideoFilename = null;
1142 }
1143
Michael Kolb8872c232013-01-29 10:33:22 -08001144 private void generateVideoFilename(int outputFileFormat) {
1145 long dateTaken = System.currentTimeMillis();
1146 String title = createName(dateTaken);
1147 // Used when emailing.
1148 String filename = title + convertOutputFormatToFileExt(outputFileFormat);
1149 String mime = convertOutputFormatToMimeType(outputFileFormat);
1150 String path = Storage.DIRECTORY + '/' + filename;
1151 String tmpPath = path + ".tmp";
Ruben Brunk16007962013-04-19 15:27:57 -07001152 mCurrentVideoValues = new ContentValues(9);
Michael Kolb8872c232013-01-29 10:33:22 -08001153 mCurrentVideoValues.put(Video.Media.TITLE, title);
1154 mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
1155 mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
Ruben Brunk16007962013-04-19 15:27:57 -07001156 mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000);
Michael Kolb8872c232013-01-29 10:33:22 -08001157 mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
1158 mCurrentVideoValues.put(Video.Media.DATA, path);
Sam Juddde3e9ab2014-03-17 13:07:22 -07001159 mCurrentVideoValues.put(Video.Media.WIDTH, mProfile.videoFrameWidth);
1160 mCurrentVideoValues.put(Video.Media.HEIGHT, mProfile.videoFrameHeight);
Michael Kolb8872c232013-01-29 10:33:22 -08001161 mCurrentVideoValues.put(Video.Media.RESOLUTION,
1162 Integer.toString(mProfile.videoFrameWidth) + "x" +
1163 Integer.toString(mProfile.videoFrameHeight));
1164 Location loc = mLocationManager.getCurrentLocation();
1165 if (loc != null) {
1166 mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
1167 mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
1168 }
Michael Kolb8872c232013-01-29 10:33:22 -08001169 mVideoFilename = tmpPath;
1170 Log.v(TAG, "New video filename: " + mVideoFilename);
1171 }
1172
Angus Kong83a99ae2013-04-17 15:37:07 -07001173 private void saveVideo() {
Michael Kolb8872c232013-01-29 10:33:22 -08001174 if (mVideoFileDescriptor == null) {
Michael Kolb8872c232013-01-29 10:33:22 -08001175 long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
1176 if (duration > 0) {
1177 if (mCaptureTimeLapse) {
1178 duration = getTimeLapseVideoLength(duration);
1179 }
Michael Kolb8872c232013-01-29 10:33:22 -08001180 } else {
1181 Log.w(TAG, "Video duration <= 0 : " + duration);
1182 }
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08001183 getServices().getMediaSaver().addVideo(mCurrentVideoFilename,
Andy Huibers10c58162014-03-29 14:06:54 -07001184 duration, isCameraFrontFacing(), mCurrentVideoValues,
Angus Kong83a99ae2013-04-17 15:37:07 -07001185 mOnVideoSavedListener, mContentResolver);
Michael Kolb8872c232013-01-29 10:33:22 -08001186 }
1187 mCurrentVideoValues = null;
Michael Kolb8872c232013-01-29 10:33:22 -08001188 }
1189
1190 private void deleteVideoFile(String fileName) {
1191 Log.v(TAG, "Deleting video " + fileName);
1192 File f = new File(fileName);
1193 if (!f.delete()) {
1194 Log.v(TAG, "Could not delete " + fileName);
1195 }
1196 }
1197
Michael Kolb8872c232013-01-29 10:33:22 -08001198 // from MediaRecorder.OnErrorListener
1199 @Override
1200 public void onError(MediaRecorder mr, int what, int extra) {
1201 Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
1202 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1203 // We may have run out of space on the sdcard.
1204 stopVideoRecording();
Spike Spraguee6374b72014-04-25 17:24:32 -07001205 mActivity.updateStorageSpaceAndHint(null);
Michael Kolb8872c232013-01-29 10:33:22 -08001206 }
1207 }
1208
1209 // from MediaRecorder.OnInfoListener
1210 @Override
1211 public void onInfo(MediaRecorder mr, int what, int extra) {
1212 if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001213 if (mMediaRecorderRecording) {
1214 onStopVideoRecording();
1215 }
Michael Kolb8872c232013-01-29 10:33:22 -08001216 } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001217 if (mMediaRecorderRecording) {
1218 onStopVideoRecording();
1219 }
Michael Kolb8872c232013-01-29 10:33:22 -08001220
1221 // Show the toast.
1222 Toast.makeText(mActivity, R.string.video_reach_size_limit,
1223 Toast.LENGTH_LONG).show();
1224 }
1225 }
1226
1227 /*
1228 * Make sure we're not recording music playing in the background, ask the
1229 * MediaPlaybackService to pause playback.
1230 */
1231 private void pauseAudioPlayback() {
Marco Nelissen20694b22013-10-29 15:27:24 -07001232 AudioManager am = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);
1233 am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
Michael Kolb8872c232013-01-29 10:33:22 -08001234 }
1235
1236 // For testing.
1237 public boolean isRecording() {
1238 return mMediaRecorderRecording;
1239 }
1240
1241 private void startVideoRecording() {
Alan Newbergerd41766f2014-04-09 18:25:34 -07001242 Log.i(TAG, "startVideoRecording");
Sascha Haeberling37f36112013-08-06 14:31:52 -07001243 mUI.cancelAnimations();
Doris Liu6432cd62013-06-13 17:20:31 -07001244 mUI.setSwipingEnabled(false);
Doris Liu38c6bc32014-01-16 18:03:18 -08001245 mUI.showFocusUI(false);
Doris Liud6487c92014-02-28 10:35:45 -08001246 mUI.showVideoRecordingHints(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001247
Spike Spraguee6374b72014-04-25 17:24:32 -07001248 mActivity.updateStorageSpaceAndHint(new CameraActivity.OnStorageUpdateDoneListener() {
1249 @Override
1250 public void onStorageUpdateDone(long bytes) {
1251 if (bytes <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1252 Log.w(TAG, "Storage issue, ignore the start request");
1253 } else {
1254 //??
1255 //if (!mCameraDevice.waitDone()) return;
1256 mCurrentVideoUri = null;
Michael Kolb8872c232013-01-29 10:33:22 -08001257
Spike Spraguee6374b72014-04-25 17:24:32 -07001258 initializeRecorder();
1259 if (mMediaRecorder == null) {
1260 Log.e(TAG, "Fail to initialize media recorder");
1261 return;
1262 }
Doris Liu3973deb2013-08-21 13:42:22 -07001263
Spike Spraguee6374b72014-04-25 17:24:32 -07001264 pauseAudioPlayback();
Michael Kolb8872c232013-01-29 10:33:22 -08001265
Spike Spraguee6374b72014-04-25 17:24:32 -07001266 try {
1267 mMediaRecorder.start(); // Recording is now started
1268 } catch (RuntimeException e) {
1269 Log.e(TAG, "Could not start media recorder. ", e);
1270 releaseMediaRecorder();
1271 // If start fails, frameworks will not lock the camera for us.
1272 mCameraDevice.lock();
1273 return;
1274 }
1275 mAppController.getCameraAppUI().setSwipeEnabled(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001276
Spike Spraguee6374b72014-04-25 17:24:32 -07001277 // The parameters might have been altered by MediaRecorder already.
1278 // We need to force mCameraDevice to refresh before getting it.
1279 mCameraDevice.refreshParameters();
1280 // The parameters may have been changed by MediaRecorder upon starting
1281 // recording. We need to alter the parameters if we support camcorder
1282 // zoom. To reduce latency when setting the parameters during zoom, we
1283 // update mParameters here once.
1284 mParameters = mCameraDevice.getParameters();
Michael Kolb8872c232013-01-29 10:33:22 -08001285
Spike Spraguee6374b72014-04-25 17:24:32 -07001286 mMediaRecorderRecording = true;
1287 mActivity.lockOrientation();
1288 mRecordingStartTime = SystemClock.uptimeMillis();
Michael Kolb8872c232013-01-29 10:33:22 -08001289
Spike Spraguee6374b72014-04-25 17:24:32 -07001290 // A special case of mode options closing: during capture it should
1291 // not be possible to change mode state.
1292 mAppController.getCameraAppUI().hideModeOptions();
1293 mAppController.getCameraAppUI().animateBottomBarToVideoStop(R.drawable.ic_stop);
1294 mUI.showRecordingUI(true);
Doris Liu4df91582014-03-21 18:33:57 -07001295
Spike Spraguee6374b72014-04-25 17:24:32 -07001296 setFocusParameters();
1297 updateRecordingTime();
1298 mActivity.enableKeepScreenOn(true);
1299 }
1300 }
1301 });
Michael Kolb8872c232013-01-29 10:33:22 -08001302 }
1303
Sascha Haeberling37f36112013-08-06 14:31:52 -07001304 private Bitmap getVideoThumbnail() {
Michael Kolb8872c232013-01-29 10:33:22 -08001305 Bitmap bitmap = null;
1306 if (mVideoFileDescriptor != null) {
1307 bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
Doris Liu3c2fca32013-02-13 18:28:03 -08001308 mDesiredPreviewWidth);
Doris Liu2a7f44c2013-08-12 15:18:53 -07001309 } else if (mCurrentVideoUri != null) {
1310 try {
1311 mVideoFileDescriptor = mContentResolver.openFileDescriptor(mCurrentVideoUri, "r");
1312 bitmap = Thumbnail.createVideoThumbnailBitmap(
1313 mVideoFileDescriptor.getFileDescriptor(), mDesiredPreviewWidth);
1314 } catch (java.io.FileNotFoundException ex) {
1315 // invalid uri
1316 Log.e(TAG, ex.toString());
1317 }
Michael Kolb8872c232013-01-29 10:33:22 -08001318 }
Doris Liu3973deb2013-08-21 13:42:22 -07001319
Michael Kolb8872c232013-01-29 10:33:22 -08001320 if (bitmap != null) {
1321 // MetadataRetriever already rotates the thumbnail. We should rotate
1322 // it to match the UI orientation (and mirror if it is front-facing camera).
Sascha Haeberling6ccec202014-03-11 09:44:34 -07001323 bitmap = CameraUtil.rotateAndMirror(bitmap, 0, isCameraFrontFacing());
Sascha Haeberling37f36112013-08-06 14:31:52 -07001324 }
1325 return bitmap;
1326 }
1327
1328 private void showCaptureResult() {
1329 mIsInReviewMode = true;
1330 Bitmap bitmap = getVideoThumbnail();
1331 if (bitmap != null) {
Doris Liu6827ce22013-03-12 19:24:28 -07001332 mUI.showReviewImage(bitmap);
Michael Kolb8872c232013-01-29 10:33:22 -08001333 }
Doris Liu6827ce22013-03-12 19:24:28 -07001334 mUI.showReviewControls();
Doris Liu6827ce22013-03-12 19:24:28 -07001335 mUI.showTimeLapseUI(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001336 }
1337
Michael Kolb8872c232013-01-29 10:33:22 -08001338 private boolean stopVideoRecording() {
Alan Newbergerd41766f2014-04-09 18:25:34 -07001339 Log.i(TAG, "stopVideoRecording");
Doris Liu6432cd62013-06-13 17:20:31 -07001340 mUI.setSwipingEnabled(true);
Doris Liu38c6bc32014-01-16 18:03:18 -08001341 mUI.showFocusUI(true);
Doris Liud6487c92014-02-28 10:35:45 -08001342 mUI.showVideoRecordingHints(true);
Michael Kolb8872c232013-01-29 10:33:22 -08001343
1344 boolean fail = false;
1345 if (mMediaRecorderRecording) {
1346 boolean shouldAddToMediaStoreNow = false;
1347
1348 try {
Doris Liu3973deb2013-08-21 13:42:22 -07001349 mMediaRecorder.setOnErrorListener(null);
1350 mMediaRecorder.setOnInfoListener(null);
1351 mMediaRecorder.stop();
1352 shouldAddToMediaStoreNow = true;
Michael Kolb8872c232013-01-29 10:33:22 -08001353 mCurrentVideoFilename = mVideoFilename;
Andy Huibers10c58162014-03-29 14:06:54 -07001354 Log.v(TAG, "stopVideoRecording: current video filename: " + mCurrentVideoFilename);
Michael Kolb8872c232013-01-29 10:33:22 -08001355 } catch (RuntimeException e) {
1356 Log.e(TAG, "stop fail", e);
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001357 if (mVideoFilename != null) {
1358 deleteVideoFile(mVideoFilename);
1359 }
Michael Kolb8872c232013-01-29 10:33:22 -08001360 fail = true;
1361 }
1362 mMediaRecorderRecording = false;
Angus Kong9f1db522013-11-09 16:25:59 -08001363 mActivity.unlockOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001364
1365 // If the activity is paused, this means activity is interrupted
1366 // during recording. Release the camera as soon as possible because
1367 // face unlock or other applications may need to use the camera.
Michael Kolb8872c232013-01-29 10:33:22 -08001368 if (mPaused) {
Doris Liu3973deb2013-08-21 13:42:22 -07001369 closeCamera();
Michael Kolb8872c232013-01-29 10:33:22 -08001370 }
1371
Doris Liufe6596c2013-10-08 11:03:37 -07001372 mUI.showRecordingUI(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001373 // The orientation was fixed during video recording. Now make it
1374 // reflect the device orientation as video recording is stopped.
Doris Liu6827ce22013-03-12 19:24:28 -07001375 mUI.setOrientationIndicator(0, true);
Angus Kong13e87c42013-11-25 10:02:47 -08001376 mActivity.enableKeepScreenOn(false);
Doris Liu2a7f44c2013-08-12 15:18:53 -07001377 if (shouldAddToMediaStoreNow && !fail) {
1378 if (mVideoFileDescriptor == null) {
1379 saveVideo();
1380 } else if (mIsVideoCaptureIntent) {
1381 // if no file save is needed, we can show the post capture UI now
1382 showCaptureResult();
1383 }
Michael Kolb8872c232013-01-29 10:33:22 -08001384 }
1385 }
Doris Liu3973deb2013-08-21 13:42:22 -07001386 // release media recorder
1387 releaseMediaRecorder();
Doris Liu4df91582014-03-21 18:33:57 -07001388
1389 mAppController.getCameraAppUI().showModeOptions();
1390 mAppController.getCameraAppUI().animateBottomBarToFullSize(mShutterIconId);
Doris Liu3973deb2013-08-21 13:42:22 -07001391 if (!mPaused) {
Doris Liu1143ebd2014-01-24 14:11:50 -08001392 setFocusParameters();
Doris Liu3973deb2013-08-21 13:42:22 -07001393 mCameraDevice.lock();
1394 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
1395 stopPreview();
Doris Liu3973deb2013-08-21 13:42:22 -07001396 // Switch back to use SurfaceTexture for preview.
1397 startPreview();
Michael Kolb8872c232013-01-29 10:33:22 -08001398 }
Angus Kong62753ae2014-02-10 10:53:54 -08001399 // Update the parameters here because the parameters might have been altered
1400 // by MediaRecorder.
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001401 mParameters = mCameraDevice.getParameters();
1402 }
Spike Sprague3f673792014-04-14 16:16:27 -07001403
Spike Spraguee6374b72014-04-25 17:24:32 -07001404 // Check this in advance of each shot so we don't add to shutter
1405 // latency. It's true that someone else could write to the SD card
1406 // in the mean time and fill it, but that could have happened
1407 // between the shutter press and saving the file too.
1408 mActivity.updateStorageSpaceAndHint(null);
Spike Sprague3f673792014-04-14 16:16:27 -07001409
Michael Kolb8872c232013-01-29 10:33:22 -08001410 return fail;
1411 }
1412
Michael Kolb8872c232013-01-29 10:33:22 -08001413 private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
1414 long seconds = milliSeconds / 1000; // round down to compute seconds
1415 long minutes = seconds / 60;
1416 long hours = minutes / 60;
1417 long remainderMinutes = minutes - (hours * 60);
1418 long remainderSeconds = seconds - (minutes * 60);
1419
1420 StringBuilder timeStringBuilder = new StringBuilder();
1421
1422 // Hours
1423 if (hours > 0) {
1424 if (hours < 10) {
1425 timeStringBuilder.append('0');
1426 }
1427 timeStringBuilder.append(hours);
1428
1429 timeStringBuilder.append(':');
1430 }
1431
1432 // Minutes
1433 if (remainderMinutes < 10) {
1434 timeStringBuilder.append('0');
1435 }
1436 timeStringBuilder.append(remainderMinutes);
1437 timeStringBuilder.append(':');
1438
1439 // Seconds
1440 if (remainderSeconds < 10) {
1441 timeStringBuilder.append('0');
1442 }
1443 timeStringBuilder.append(remainderSeconds);
1444
1445 // Centi seconds
1446 if (displayCentiSeconds) {
1447 timeStringBuilder.append('.');
1448 long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
1449 if (remainderCentiSeconds < 10) {
1450 timeStringBuilder.append('0');
1451 }
1452 timeStringBuilder.append(remainderCentiSeconds);
1453 }
1454
1455 return timeStringBuilder.toString();
1456 }
1457
1458 private long getTimeLapseVideoLength(long deltaMs) {
1459 // For better approximation calculate fractional number of frames captured.
1460 // This will update the video time at a higher resolution.
1461 double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
1462 return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
1463 }
1464
1465 private void updateRecordingTime() {
1466 if (!mMediaRecorderRecording) {
1467 return;
1468 }
1469 long now = SystemClock.uptimeMillis();
1470 long delta = now - mRecordingStartTime;
1471
1472 // Starting a minute before reaching the max duration
1473 // limit, we'll countdown the remaining time instead.
1474 boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1475 && delta >= mMaxVideoDurationInMs - 60000);
1476
1477 long deltaAdjusted = delta;
1478 if (countdownRemainingTime) {
1479 deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
1480 }
1481 String text;
1482
1483 long targetNextUpdateDelay;
1484 if (!mCaptureTimeLapse) {
1485 text = millisecondToTimeString(deltaAdjusted, false);
1486 targetNextUpdateDelay = 1000;
1487 } else {
1488 // The length of time lapse video is different from the length
1489 // of the actual wall clock time elapsed. Display the video length
1490 // only in format hh:mm:ss.dd, where dd are the centi seconds.
1491 text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
1492 targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
1493 }
1494
Doris Liu6827ce22013-03-12 19:24:28 -07001495 mUI.setRecordingTime(text);
Michael Kolb8872c232013-01-29 10:33:22 -08001496
1497 if (mRecordingTimeCountsDown != countdownRemainingTime) {
1498 // Avoid setting the color on every update, do it only
1499 // when it needs changing.
1500 mRecordingTimeCountsDown = countdownRemainingTime;
1501
1502 int color = mActivity.getResources().getColor(countdownRemainingTime
1503 ? R.color.recording_time_remaining_text
1504 : R.color.recording_time_elapsed_text);
1505
Doris Liu6827ce22013-03-12 19:24:28 -07001506 mUI.setRecordingTimeTextColor(color);
Michael Kolb8872c232013-01-29 10:33:22 -08001507 }
1508
1509 long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
Angus Kong13e87c42013-11-25 10:02:47 -08001510 mHandler.sendEmptyMessageDelayed(MSG_UPDATE_RECORD_TIME, actualNextUpdateDelay);
Michael Kolb8872c232013-01-29 10:33:22 -08001511 }
1512
1513 private static boolean isSupported(String value, List<String> supported) {
1514 return supported == null ? false : supported.indexOf(value) >= 0;
1515 }
1516
1517 @SuppressWarnings("deprecation")
1518 private void setCameraParameters() {
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001519 SettingsManager settingsManager = mActivity.getSettingsManager();
1520
Michael Kolb8872c232013-01-29 10:33:22 -08001521 mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
Andy Huibers6dcf90c2014-04-14 13:53:12 -07001522 // This is required for Samsung SGH-I337 and probably other Samsung S4 versions
1523 if (Build.BRAND.toLowerCase().contains("samsung")) {
1524 mParameters.set("video-size", mProfile.videoFrameWidth + "x" + mProfile.videoFrameHeight);
1525 }
Angus Kongb50b5cb2013-08-09 14:55:20 -07001526 int[] fpsRange = CameraUtil.getMaxPreviewFpsRange(mParameters);
Doris Liu6432cd62013-06-13 17:20:31 -07001527 if (fpsRange.length > 0) {
1528 mParameters.setPreviewFpsRange(
1529 fpsRange[Parameters.PREVIEW_FPS_MIN_INDEX],
1530 fpsRange[Parameters.PREVIEW_FPS_MAX_INDEX]);
1531 } else {
1532 mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
1533 }
Michael Kolb8872c232013-01-29 10:33:22 -08001534
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001535 enableTorchMode(settingsManager.isCameraBackFacing());
Michael Kolb8872c232013-01-29 10:33:22 -08001536
Michael Kolb8872c232013-01-29 10:33:22 -08001537 // Set zoom.
1538 if (mParameters.isZoomSupported()) {
1539 mParameters.setZoom(mZoomValue);
1540 }
Doris Liua1ec04a2014-01-13 17:29:40 -08001541 updateFocusParameters();
Michael Kolb8872c232013-01-29 10:33:22 -08001542
Angus Kongb50b5cb2013-08-09 14:55:20 -07001543 mParameters.set(CameraUtil.RECORDING_HINT, CameraUtil.TRUE);
Michael Kolb8872c232013-01-29 10:33:22 -08001544
1545 // Enable video stabilization. Convenience methods not available in API
1546 // level <= 14
1547 String vstabSupported = mParameters.get("video-stabilization-supported");
1548 if ("true".equals(vstabSupported)) {
1549 mParameters.set("video-stabilization", "true");
1550 }
1551
1552 // Set picture size.
1553 // The logic here is different from the logic in still-mode camera.
1554 // There we determine the preview size based on the picture size, but
1555 // here we determine the picture size based on the preview size.
Angus Kong63424662014-04-23 10:47:47 -07001556 List<Size> supported =
1557 Size.buildListFromCameraSizes(mParameters.getSupportedPictureSizes());
Angus Kongb50b5cb2013-08-09 14:55:20 -07001558 Size optimalSize = CameraUtil.getOptimalVideoSnapshotPictureSize(supported,
Michael Kolb8872c232013-01-29 10:33:22 -08001559 (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
Angus Kong63424662014-04-23 10:47:47 -07001560 Size original = new Size(mParameters.getPictureSize());
Michael Kolb8872c232013-01-29 10:33:22 -08001561 if (!original.equals(optimalSize)) {
Angus Kong00b7b102014-04-24 15:46:52 -07001562 mParameters.setPictureSize(optimalSize.width(), optimalSize.height());
Michael Kolb8872c232013-01-29 10:33:22 -08001563 }
Angus Kong00b7b102014-04-24 15:46:52 -07001564 Log.d(TAG, "Video snapshot size is " + optimalSize);
Michael Kolb8872c232013-01-29 10:33:22 -08001565
1566 // Set JPEG quality.
1567 int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1568 CameraProfile.QUALITY_HIGH);
1569 mParameters.setJpegQuality(jpegQuality);
1570
Doris Liu6432cd62013-06-13 17:20:31 -07001571 mCameraDevice.setParameters(mParameters);
Andy Huibers64abfe92014-01-14 22:25:52 -08001572 // Nexus 5 through KitKat 4.4.2 requires a second call to
1573 // .setParameters() for frame rate settings to take effect.
1574 mCameraDevice.setParameters(mParameters);
Sascha Haeberlingf8b877c2013-09-09 17:32:48 -07001575
1576 // Update UI based on the new parameters.
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001577 mUI.updateOnScreenIndicators(mParameters);
Michael Kolb8872c232013-01-29 10:33:22 -08001578 }
1579
Doris Liua1ec04a2014-01-13 17:29:40 -08001580 private void updateFocusParameters() {
1581 // Set continuous autofocus. During recording, we use "continuous-video"
1582 // auto focus mode to ensure smooth focusing. Whereas during preview (i.e.
1583 // before recording starts) we use "continuous-picture" auto focus mode
1584 // for faster but slightly jittery focusing.
1585 List<String> supportedFocus = mParameters.getSupportedFocusModes();
1586 if (mMediaRecorderRecording) {
1587 if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
1588 mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
1589 mFocusManager.overrideFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
1590 } else {
1591 mFocusManager.overrideFocusMode(null);
1592 }
1593 } else {
1594 mFocusManager.overrideFocusMode(null);
1595 if (isSupported(CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE, supportedFocus)) {
1596 mParameters.setFocusMode(mFocusManager.getFocusMode());
1597 if (mFocusAreaSupported) {
1598 mParameters.setFocusAreas(mFocusManager.getFocusAreas());
1599 }
1600 }
1601 }
1602 updateAutoFocusMoveCallback();
1603 }
1604
Michael Kolb8872c232013-01-29 10:33:22 -08001605 @Override
Angus Kong20fad242013-11-11 18:23:46 -08001606 public void resume() {
Spike Sprague51c877c2014-02-18 11:14:12 -08001607 if (isVideoCaptureIntent()) {
1608 mDontResetIntentUiOnResume = mPaused;
1609 }
1610
Angus Kongc4e66562013-11-22 23:03:21 -08001611 mPaused = false;
Angus Kong13e87c42013-11-25 10:02:47 -08001612 installIntentFilter();
Erin Dahlgren667630d2014-04-01 14:03:25 -07001613 mAppController.setShutterEnabled(false);
Angus Kongc4e66562013-11-22 23:03:21 -08001614 mZoomValue = 0;
1615
1616 showVideoSnapshotUI(false);
1617
1618 if (!mPreviewing) {
1619 requestCamera(mCameraId);
1620 } else {
1621 // preview already started
Erin Dahlgren667630d2014-04-01 14:03:25 -07001622 mAppController.setShutterEnabled(true);
Angus Kongc4e66562013-11-22 23:03:21 -08001623 }
1624
Doris Liua1ec04a2014-01-13 17:29:40 -08001625 if (mFocusManager != null) {
1626 // If camera is not open when resume is called, focus manager will not
1627 // be initialized yet, in which case it will start listening to
1628 // preview area size change later in the initialization.
1629 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1630 }
Sascha Haeberlingde303232014-02-07 02:30:53 +01001631
Angus Kongc4e66562013-11-22 23:03:21 -08001632 if (mPreviewing) {
1633 mOnResumeTime = SystemClock.uptimeMillis();
Angus Kong13e87c42013-11-25 10:02:47 -08001634 mHandler.sendEmptyMessageDelayed(MSG_CHECK_DISPLAY_ROTATION, 100);
Angus Kongc4e66562013-11-22 23:03:21 -08001635 }
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -08001636 getServices().getMemoryManager().addListener(this);
Angus Kong20fad242013-11-11 18:23:46 -08001637 }
1638
1639 @Override
1640 public void pause() {
Angus Kongc4e66562013-11-22 23:03:21 -08001641 mPaused = true;
Doris Liua1ec04a2014-01-13 17:29:40 -08001642
1643 if (mFocusManager != null) {
1644 // If camera is not open when resume is called, focus manager will not
1645 // be initialized yet, in which case it will start listening to
1646 // preview area size change later in the initialization.
1647 mAppController.removePreviewAreaSizeChangedListener(mFocusManager);
1648 mFocusManager.removeMessages();
1649 }
Angus Kongc4e66562013-11-22 23:03:21 -08001650 if (mMediaRecorderRecording) {
1651 // Camera will be released in onStopVideoRecording.
1652 onStopVideoRecording();
1653 } else {
1654 stopPreview();
1655 closeCamera();
1656 releaseMediaRecorder();
1657 }
1658
1659 closeVideoFileDescriptor();
Angus Kongc4e66562013-11-22 23:03:21 -08001660
1661 if (mReceiver != null) {
1662 mActivity.unregisterReceiver(mReceiver);
1663 mReceiver = null;
1664 }
Angus Kongc4e66562013-11-22 23:03:21 -08001665
Angus Kong13e87c42013-11-25 10:02:47 -08001666 mHandler.removeMessages(MSG_CHECK_DISPLAY_ROTATION);
1667 mHandler.removeMessages(MSG_SWITCH_CAMERA);
1668 mHandler.removeMessages(MSG_SWITCH_CAMERA_START_ANIMATION);
Angus Kongc4e66562013-11-22 23:03:21 -08001669 mPendingSwitchCameraId = -1;
1670 mSwitchingCamera = false;
1671 mPreferenceRead = false;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -08001672 getServices().getMemoryManager().removeListener(this);
Angus Kong20fad242013-11-11 18:23:46 -08001673 }
1674
1675 @Override
1676 public void destroy() {
1677
1678 }
1679
1680 @Override
Angus Kong2f0e4a32013-12-03 10:02:35 -08001681 public void onLayoutOrientationChanged(boolean isLandscape) {
Michael Kolb8872c232013-01-29 10:33:22 -08001682 setDisplayOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001683 }
1684
Erin Dahlgrena07e94c2013-12-04 18:44:08 -08001685 // TODO: integrate this into the SettingsManager listeners.
Michael Kolb8872c232013-01-29 10:33:22 -08001686 public void onSharedPreferenceChanged() {
Michael Kolb8872c232013-01-29 10:33:22 -08001687
Michael Kolb8872c232013-01-29 10:33:22 -08001688 }
1689
1690 private void switchCamera() {
Sascha Haeberlingf8b877c2013-09-09 17:32:48 -07001691 if (mPaused) {
1692 return;
1693 }
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001694 SettingsManager settingsManager = mActivity.getSettingsManager();
Michael Kolb8872c232013-01-29 10:33:22 -08001695
1696 Log.d(TAG, "Start to switch camera.");
1697 mCameraId = mPendingSwitchCameraId;
1698 mPendingSwitchCameraId = -1;
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001699 settingsManager.set(SettingsManager.SETTING_CAMERA_ID, "" + mCameraId);
Michael Kolb8872c232013-01-29 10:33:22 -08001700
Doris Liua1ec04a2014-01-13 17:29:40 -08001701 if (mFocusManager != null) {
1702 mFocusManager.removeMessages();
1703 }
Michael Kolb8872c232013-01-29 10:33:22 -08001704 closeCamera();
Angus Kong20fad242013-11-11 18:23:46 -08001705 requestCamera(mCameraId);
Michael Kolb8872c232013-01-29 10:33:22 -08001706
Sascha Haeberling6ccec202014-03-11 09:44:34 -07001707 mMirror = isCameraFrontFacing();
Doris Liua1ec04a2014-01-13 17:29:40 -08001708 if (mFocusManager != null) {
1709 mFocusManager.setMirror(mMirror);
1710 }
1711
Michael Kolb8872c232013-01-29 10:33:22 -08001712 // From onResume
Doris Liu6432cd62013-06-13 17:20:31 -07001713 mZoomValue = 0;
Doris Liu6827ce22013-03-12 19:24:28 -07001714 mUI.setOrientationIndicator(0, false);
Michael Kolb8872c232013-01-29 10:33:22 -08001715
Doris Liu6432cd62013-06-13 17:20:31 -07001716 // Start switch camera animation. Post a message because
1717 // onFrameAvailable from the old camera may already exist.
Angus Kong13e87c42013-11-25 10:02:47 -08001718 mHandler.sendEmptyMessage(MSG_SWITCH_CAMERA_START_ANIMATION);
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001719 mUI.updateOnScreenIndicators(mParameters);
Michael Kolb8872c232013-01-29 10:33:22 -08001720 }
1721
Michael Kolb8872c232013-01-29 10:33:22 -08001722 private void initializeVideoSnapshot() {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001723 if (mParameters == null) {
1724 return;
1725 }
Michael Kolb8872c232013-01-29 10:33:22 -08001726 }
1727
1728 void showVideoSnapshotUI(boolean enabled) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001729 if (mParameters == null) {
1730 return;
1731 }
Angus Kongb50b5cb2013-08-09 14:55:20 -07001732 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
Doris Liu6432cd62013-06-13 17:20:31 -07001733 if (enabled) {
Sascha Haeberling37f36112013-08-06 14:31:52 -07001734 mUI.animateFlash();
Michael Kolb8872c232013-01-29 10:33:22 -08001735 } else {
Doris Liu6827ce22013-03-12 19:24:28 -07001736 mUI.showPreviewBorder(enabled);
Michael Kolb8872c232013-01-29 10:33:22 -08001737 }
Erin Dahlgren667630d2014-04-01 14:03:25 -07001738 mAppController.setShutterEnabled(!enabled);
Michael Kolb8872c232013-01-29 10:33:22 -08001739 }
1740 }
1741
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001742 /**
1743 * Used to update the flash mode. Video mode can turn on the flash as torch
1744 * mode, which we would like to turn on and off when we switching in and
1745 * out to the preview.
1746 *
1747 * @param enable Whether torch mode can be enabled.
1748 */
1749 private void enableTorchMode(boolean enable) {
1750 if (mParameters.getFlashMode() == null) {
1751 return;
1752 }
1753
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001754 SettingsManager settingsManager = mActivity.getSettingsManager();
1755
ztenghui7b265a62013-09-09 14:58:44 -07001756 String flashMode;
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001757 if (enable) {
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001758 flashMode = settingsManager.get(SettingsManager.SETTING_VIDEOCAMERA_FLASH_MODE);
ztenghui7b265a62013-09-09 14:58:44 -07001759 } else {
1760 flashMode = Parameters.FLASH_MODE_OFF;
1761 }
1762 List<String> supportedFlash = mParameters.getSupportedFlashModes();
1763 if (isSupported(flashMode, supportedFlash)) {
1764 mParameters.setFlashMode(flashMode);
1765 } else {
1766 flashMode = mParameters.getFlashMode();
1767 if (flashMode == null) {
1768 flashMode = mActivity.getString(
1769 R.string.pref_camera_flashmode_no_flash);
Angus Kongfaaee012013-12-07 00:38:46 -08001770 mParameters.setFlashMode(flashMode);
Michael Kolb8872c232013-01-29 10:33:22 -08001771 }
Michael Kolb8872c232013-01-29 10:33:22 -08001772 }
ztenghui7b265a62013-09-09 14:58:44 -07001773 mCameraDevice.setParameters(mParameters);
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001774 mUI.updateOnScreenIndicators(mParameters);
ztenghui7b265a62013-09-09 14:58:44 -07001775 }
1776
Michael Kolb8872c232013-01-29 10:33:22 -08001777 @Override
Sascha Haeberling8c1a9222014-02-25 09:38:06 -08001778 public void onPreviewVisibilityChanged(int visibility) {
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001779 if (mPreviewing) {
Sascha Haeberling8c1a9222014-02-25 09:38:06 -08001780 enableTorchMode(visibility == ModuleController.VISIBILITY_VISIBLE);
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001781 }
Erin Dahlgren3044d8c2013-10-10 18:23:45 -07001782 }
1783
Angus Kong9ef99252013-07-18 18:04:19 -07001784 private final class JpegPictureCallback implements CameraPictureCallback {
Michael Kolb8872c232013-01-29 10:33:22 -08001785 Location mLocation;
1786
1787 public JpegPictureCallback(Location loc) {
1788 mLocation = loc;
1789 }
1790
1791 @Override
Angus Kong9ef99252013-07-18 18:04:19 -07001792 public void onPictureTaken(byte [] jpegData, CameraProxy camera) {
Doris Liu057b21a2014-04-25 17:35:27 -07001793 Log.i(TAG, "Video snapshot taken.");
Michael Kolb8872c232013-01-29 10:33:22 -08001794 mSnapshotInProgress = false;
1795 showVideoSnapshotUI(false);
1796 storeImage(jpegData, mLocation);
1797 }
1798 }
1799
1800 private void storeImage(final byte[] data, Location loc) {
1801 long dateTaken = System.currentTimeMillis();
Angus Kongb50b5cb2013-08-09 14:55:20 -07001802 String title = CameraUtil.createJpegName(dateTaken);
Angus Kong0d00a892013-03-26 11:40:40 -07001803 ExifInterface exif = Exif.getExif(data);
1804 int orientation = Exif.getOrientation(exif);
Doris Liu6df2d962013-08-20 16:31:29 -07001805
Andy Huibers10c58162014-03-29 14:06:54 -07001806 int zoomIndex = mParameters.getZoom();
1807 float zoomValue = 0.01f * mParameters.getZoomRatios().get(zoomIndex);
1808 UsageStatistics.instance().photoCaptureDoneEvent(
1809 eventprotos.NavigationChange.Mode.VIDEO_STILL, title + ".jpeg", exif,
1810 isCameraFrontFacing(), false, zoomValue);
1811
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08001812 getServices().getMediaSaver().addImage(
Doris Liu6df2d962013-08-20 16:31:29 -07001813 data, title, dateTaken, loc, orientation,
Angus Kong83a99ae2013-04-17 15:37:07 -07001814 exif, mOnPhotoSavedListener, mContentResolver);
Michael Kolb8872c232013-01-29 10:33:22 -08001815 }
1816
Michael Kolb8872c232013-01-29 10:33:22 -08001817 private String convertOutputFormatToMimeType(int outputFileFormat) {
1818 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1819 return "video/mp4";
1820 }
1821 return "video/3gpp";
1822 }
1823
1824 private String convertOutputFormatToFileExt(int outputFileFormat) {
1825 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1826 return ".mp4";
1827 }
1828 return ".3gp";
1829 }
1830
1831 private void closeVideoFileDescriptor() {
1832 if (mVideoFileDescriptor != null) {
1833 try {
1834 mVideoFileDescriptor.close();
1835 } catch (IOException e) {
1836 Log.e(TAG, "Fail to close fd", e);
1837 }
1838 mVideoFileDescriptor = null;
1839 }
1840 }
1841
Michael Kolb8872c232013-01-29 10:33:22 -08001842 @Override
Angus Kong395ee2d2013-07-15 12:42:41 -07001843 public void onPreviewUIReady() {
1844 startPreview();
1845 }
1846
1847 @Override
1848 public void onPreviewUIDestroyed() {
1849 stopPreview();
1850 }
Angus Kong20fad242013-11-11 18:23:46 -08001851
Doris Liu1dfe7822013-12-12 00:02:08 -08001852 @Override
1853 public void startPreCaptureAnimation() {
1854 mAppController.startPreCaptureAnimation();
1855 }
1856
Angus Kong20fad242013-11-11 18:23:46 -08001857 private void requestCamera(int id) {
1858 mActivity.getCameraProvider().requestCamera(id);
1859 }
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -08001860
1861 @Override
1862 public void onMemoryStateChanged(int state) {
Erin Dahlgren667630d2014-04-01 14:03:25 -07001863 mAppController.setShutterEnabled(state == MemoryManager.STATE_OK);
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -08001864 }
1865
1866 @Override
1867 public void onLowMemory() {
1868 // Not much we can do in the video module.
1869 }
1870
Doris Liua1ec04a2014-01-13 17:29:40 -08001871 /***********************FocusOverlayManager Listener****************************/
1872 @Override
1873 public void autoFocus() {
1874 mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
1875 }
1876
1877 @Override
1878 public void cancelAutoFocus() {
1879 mCameraDevice.cancelAutoFocus();
1880 setFocusParameters();
1881 }
1882
1883 @Override
1884 public boolean capture() {
1885 return false;
1886 }
1887
1888 @Override
1889 public void startFaceDetection() {
1890
1891 }
1892
1893 @Override
1894 public void stopFaceDetection() {
1895
1896 }
1897
1898 @Override
1899 public void setFocusParameters() {
1900 updateFocusParameters();
1901 mCameraDevice.setParameters(mParameters);
1902 }
1903
Michael Kolb8872c232013-01-29 10:33:22 -08001904}