blob: 69e9881d60f826e7ed19c9d2c062ebd18bd7e7ea [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;
Sameer Padaladb81ce62014-03-21 15:33:56 -070052import android.view.ViewGroup;
Michael Kolb8872c232013-01-29 10:33:22 -080053import android.widget.Toast;
54
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080055import com.android.camera.app.AppController;
Spike Sprague39f8a762014-01-13 14:22:18 -080056import com.android.camera.app.CameraAppUI;
Doris Liua1ec04a2014-01-13 17:29:40 -080057import com.android.camera.app.CameraManager;
Angus Kong20fad242013-11-11 18:23:46 -080058import com.android.camera.app.CameraManager.CameraPictureCallback;
59import com.android.camera.app.CameraManager.CameraProxy;
Kevin Gabayanffbc43c2013-12-09 11:41:50 -080060import com.android.camera.app.LocationManager;
Angus Kongfd4fc0e2013-11-07 15:38:09 -080061import com.android.camera.app.MediaSaver;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080062import com.android.camera.app.MemoryManager;
63import com.android.camera.app.MemoryManager.MemoryListener;
Angus Kong2bca2102014-03-11 16:27:30 -070064import com.android.camera.debug.Log;
ztenghuia16e7b52013-08-23 11:47:56 -070065import com.android.camera.exif.ExifInterface;
Erin Dahlgrenb1641f52014-01-14 15:58:52 -080066import com.android.camera.hardware.HardwareSpec;
67import com.android.camera.hardware.HardwareSpecImpl;
Angus Kong20fad242013-11-11 18:23:46 -080068import com.android.camera.module.ModuleController;
Erin Dahlgrenbd3da262013-12-02 11:48:13 -080069import com.android.camera.settings.SettingsManager;
Sascha Haeberlingde303232014-02-07 02:30:53 +010070import com.android.camera.settings.SettingsUtil;
Sascha Haeberling638e6f02013-09-18 14:28:51 -070071import com.android.camera.util.AccessibilityUtils;
72import com.android.camera.util.ApiHelper;
Angus Kongb50b5cb2013-08-09 14:55:20 -070073import com.android.camera.util.CameraUtil;
Sameer Padaladb81ce62014-03-21 15:33:56 -070074import com.android.camera.util.SmartCameraHelper;
Sascha Haeberling8e963a52013-08-06 11:43:02 -070075import com.android.camera.util.UsageStatistics;
76import com.android.camera2.R;
Seth Raphael5e09d012013-12-18 13:45:03 -080077import com.google.common.logging.eventprotos;
Michael Kolb8872c232013-01-29 10:33:22 -080078
Angus Kongb50b5cb2013-08-09 14:55:20 -070079import java.io.File;
80import java.io.IOException;
81import java.text.SimpleDateFormat;
82import java.util.Date;
83import java.util.Iterator;
84import java.util.List;
85
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080086public class VideoModule extends CameraModule
87 implements ModuleController,
88 VideoController,
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080089 MemoryListener,
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080090 MediaRecorder.OnErrorListener,
Doris Liua1ec04a2014-01-13 17:29:40 -080091 MediaRecorder.OnInfoListener, FocusOverlayManager.Listener {
Michael Kolb8872c232013-01-29 10:33:22 -080092
Angus Kong2bca2102014-03-11 16:27:30 -070093 private static final Log.Tag TAG = new Log.Tag("VideoModule");
Michael Kolb8872c232013-01-29 10:33:22 -080094
Angus Kong13e87c42013-11-25 10:02:47 -080095 // Messages defined for the UI thread handler.
96 private static final int MSG_CHECK_DISPLAY_ROTATION = 4;
97 private static final int MSG_UPDATE_RECORD_TIME = 5;
98 private static final int MSG_ENABLE_SHUTTER_BUTTON = 6;
Angus Kong13e87c42013-11-25 10:02:47 -080099 private static final int MSG_SWITCH_CAMERA = 8;
100 private static final int MSG_SWITCH_CAMERA_START_ANIMATION = 9;
Michael Kolb8872c232013-01-29 10:33:22 -0800101
102 private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
103
104 /**
105 * An unpublished intent flag requesting to start recording straight away
106 * and return as soon as recording is stopped.
107 * TODO: consider publishing by moving into MediaStore.
108 */
109 private static final String EXTRA_QUICK_CAPTURE =
110 "android.intent.extra.quickCapture";
111
Michael Kolb8872c232013-01-29 10:33:22 -0800112 // module fields
113 private CameraActivity mActivity;
Michael Kolb8872c232013-01-29 10:33:22 -0800114 private boolean mPaused;
Spike Sprague51c877c2014-02-18 11:14:12 -0800115
116 // if, during and intent capture, the activity is paused (e.g. when app switching or reviewing a
117 // shot video), we don't want the bottom bar intent ui to reset to the capture button
118 private boolean mDontResetIntentUiOnResume;
119
Michael Kolb8872c232013-01-29 10:33:22 -0800120 private int mCameraId;
121 private Parameters mParameters;
122
Doris Liu6432cd62013-06-13 17:20:31 -0700123 private boolean mIsInReviewMode;
Michael Kolb8872c232013-01-29 10:33:22 -0800124 private boolean mSnapshotInProgress = false;
125
Michael Kolb8872c232013-01-29 10:33:22 -0800126 private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
127
Angus Kong395ee2d2013-07-15 12:42:41 -0700128 // Preference must be read before starting preview. We check this before starting
129 // preview.
130 private boolean mPreferenceRead;
Michael Kolb8872c232013-01-29 10:33:22 -0800131
Michael Kolb8872c232013-01-29 10:33:22 -0800132 private boolean mIsVideoCaptureIntent;
133 private boolean mQuickCapture;
134
135 private MediaRecorder mMediaRecorder;
Michael Kolb8872c232013-01-29 10:33:22 -0800136
137 private boolean mSwitchingCamera;
138 private boolean mMediaRecorderRecording = false;
139 private long mRecordingStartTime;
140 private boolean mRecordingTimeCountsDown = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800141 private long mOnResumeTime;
142 // The video file that the hardware camera is about to record into
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800143 // (or is recording into.
Michael Kolb8872c232013-01-29 10:33:22 -0800144 private String mVideoFilename;
145 private ParcelFileDescriptor mVideoFileDescriptor;
146
147 // The video file that has already been recorded, and that is being
148 // examined by the user.
149 private String mCurrentVideoFilename;
150 private Uri mCurrentVideoUri;
ztenghui70bd0242013-10-14 11:15:44 -0700151 private boolean mCurrentVideoUriFromMediaSaved;
Michael Kolb8872c232013-01-29 10:33:22 -0800152 private ContentValues mCurrentVideoValues;
153
154 private CamcorderProfile mProfile;
155
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800156 // The video duration limit. 0 means no limit.
Michael Kolb8872c232013-01-29 10:33:22 -0800157 private int mMaxVideoDurationInMs;
158
159 // Time Lapse parameters.
Sascha Haeberling8c000c22014-03-17 13:56:05 -0700160 private final boolean mCaptureTimeLapse = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800161 // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
Sascha Haeberling8c000c22014-03-17 13:56:05 -0700162 private final int mTimeBetweenTimeLapseFrameCaptureMs = 0;
Michael Kolb8872c232013-01-29 10:33:22 -0800163
164 boolean mPreviewing = false; // True if preview is started.
165 // The display rotation in degrees. This is only valid when mPreviewing is
166 // true.
167 private int mDisplayRotation;
168 private int mCameraDisplayOrientation;
Doris Liu2b906b82013-12-10 16:34:08 -0800169 private AppController mAppController;
Michael Kolb8872c232013-01-29 10:33:22 -0800170
Doris Liu6827ce22013-03-12 19:24:28 -0700171 private int mDesiredPreviewWidth;
172 private int mDesiredPreviewHeight;
Michael Kolb8872c232013-01-29 10:33:22 -0800173 private ContentResolver mContentResolver;
174
175 private LocationManager mLocationManager;
176
Michael Kolb8872c232013-01-29 10:33:22 -0800177 private int mPendingSwitchCameraId;
Michael Kolb8872c232013-01-29 10:33:22 -0800178 private final Handler mHandler = new MainHandler();
Doris Liu6827ce22013-03-12 19:24:28 -0700179 private VideoUI mUI;
Doris Liu6432cd62013-06-13 17:20:31 -0700180 private CameraProxy mCameraDevice;
181
Michael Kolb8872c232013-01-29 10:33:22 -0800182 // The degrees of the device rotated clockwise from its natural orientation.
183 private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
184
185 private int mZoomValue; // The current zoom value.
Doris Liu6827ce22013-03-12 19:24:28 -0700186
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800187 private final MediaSaver.OnMediaSavedListener mOnVideoSavedListener =
188 new MediaSaver.OnMediaSavedListener() {
Angus Kong83a99ae2013-04-17 15:37:07 -0700189 @Override
190 public void onMediaSaved(Uri uri) {
191 if (uri != null) {
Doris Liu2a7f44c2013-08-12 15:18:53 -0700192 mCurrentVideoUri = uri;
ztenghui70bd0242013-10-14 11:15:44 -0700193 mCurrentVideoUriFromMediaSaved = true;
Doris Liu2a7f44c2013-08-12 15:18:53 -0700194 onVideoSaved();
Sascha Haeberling37f36112013-08-06 14:31:52 -0700195 mActivity.notifyNewMedia(uri);
Angus Kong83a99ae2013-04-17 15:37:07 -0700196 }
197 }
198 };
199
Angus Kongfd4fc0e2013-11-07 15:38:09 -0800200 private final MediaSaver.OnMediaSavedListener mOnPhotoSavedListener =
201 new MediaSaver.OnMediaSavedListener() {
Angus Kong86d36312013-01-31 18:22:44 -0800202 @Override
203 public void onMediaSaved(Uri uri) {
204 if (uri != null) {
Sascha Haeberling37f36112013-08-06 14:31:52 -0700205 mActivity.notifyNewMedia(uri);
Angus Kong86d36312013-01-31 18:22:44 -0800206 }
207 }
208 };
Doris Liua1ec04a2014-01-13 17:29:40 -0800209 private FocusOverlayManager mFocusManager;
210 private boolean mMirror;
211 private Parameters mInitialParams;
212 private boolean mFocusAreaSupported;
213 private boolean mMeteringAreaSupported;
214
215 private final CameraManager.CameraAFCallback mAutoFocusCallback =
216 new CameraManager.CameraAFCallback() {
217 @Override
218 public void onAutoFocus(boolean focused, CameraProxy camera) {
219 if (mPaused) {
220 return;
221 }
222 mFocusManager.onAutoFocus(focused, false);
223 }
224 };
225
226 private final Object mAutoFocusMoveCallback =
227 ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
228 ? new CameraManager.CameraAFMoveCallback() {
229 @Override
230 public void onAutoFocusMoving(boolean moving, CameraProxy camera) {
231 mFocusManager.onAutoFocusMoving(moving);
232 }
233 } : null;
Angus Kong86d36312013-01-31 18:22:44 -0800234
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800235 /**
236 * This Handler is used to post message back onto the main thread of the
237 * application.
238 */
Michael Kolb8872c232013-01-29 10:33:22 -0800239 private class MainHandler extends Handler {
240 @Override
241 public void handleMessage(Message msg) {
242 switch (msg.what) {
243
Angus Kong13e87c42013-11-25 10:02:47 -0800244 case MSG_ENABLE_SHUTTER_BUTTON:
Doris Liu6827ce22013-03-12 19:24:28 -0700245 mUI.enableShutter(true);
Michael Kolb8872c232013-01-29 10:33:22 -0800246 break;
247
Angus Kong13e87c42013-11-25 10:02:47 -0800248 case MSG_UPDATE_RECORD_TIME: {
Michael Kolb8872c232013-01-29 10:33:22 -0800249 updateRecordingTime();
250 break;
251 }
252
Angus Kong13e87c42013-11-25 10:02:47 -0800253 case MSG_CHECK_DISPLAY_ROTATION: {
Michael Kolb8872c232013-01-29 10:33:22 -0800254 // Restart the preview if display rotation has changed.
255 // Sometimes this happens when the device is held upside
256 // down and camera app is opened. Rotation animation will
257 // take some time and the rotation value we have got may be
258 // wrong. Framework does not have a callback for this now.
Angus Kongb50b5cb2013-08-09 14:55:20 -0700259 if ((CameraUtil.getDisplayRotation(mActivity) != mDisplayRotation)
Michael Kolb8872c232013-01-29 10:33:22 -0800260 && !mMediaRecorderRecording && !mSwitchingCamera) {
261 startPreview();
262 }
263 if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
Angus Kong13e87c42013-11-25 10:02:47 -0800264 mHandler.sendEmptyMessageDelayed(MSG_CHECK_DISPLAY_ROTATION, 100);
Michael Kolb8872c232013-01-29 10:33:22 -0800265 }
266 break;
267 }
268
Angus Kong13e87c42013-11-25 10:02:47 -0800269 case MSG_SWITCH_CAMERA: {
Michael Kolb8872c232013-01-29 10:33:22 -0800270 switchCamera();
271 break;
272 }
273
Angus Kong13e87c42013-11-25 10:02:47 -0800274 case MSG_SWITCH_CAMERA_START_ANIMATION: {
Doris Liu6432cd62013-06-13 17:20:31 -0700275 //TODO:
276 //((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
Michael Kolb8872c232013-01-29 10:33:22 -0800277
278 // Enable all camera controls.
279 mSwitchingCamera = false;
280 break;
281 }
282
Michael Kolb8872c232013-01-29 10:33:22 -0800283 default:
284 Log.v(TAG, "Unhandled message: " + msg.what);
285 break;
286 }
287 }
288 }
289
290 private BroadcastReceiver mReceiver = null;
291
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800292 /** Whether shutter is enabled. */
293 private boolean mShutterEnabled;
294
Michael Kolb8872c232013-01-29 10:33:22 -0800295 private class MyBroadcastReceiver extends BroadcastReceiver {
296 @Override
297 public void onReceive(Context context, Intent intent) {
298 String action = intent.getAction();
299 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
300 stopVideoRecording();
301 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
302 Toast.makeText(mActivity,
303 mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
304 }
305 }
306 }
307
Spike Sprague41f68002014-01-24 16:59:25 -0800308 private int mShutterIconId;
309
310
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800311 /**
312 * Construct a new video module.
313 */
Angus Kongc4e66562013-11-22 23:03:21 -0800314 public VideoModule(AppController app) {
315 super(app);
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800316 }
317
Michael Kolb8872c232013-01-29 10:33:22 -0800318 private String createName(long dateTaken) {
319 Date date = new Date(dateTaken);
320 SimpleDateFormat dateFormat = new SimpleDateFormat(
321 mActivity.getString(R.string.video_file_name_format));
322
323 return dateFormat.format(date);
324 }
325
Michael Kolb8872c232013-01-29 10:33:22 -0800326 @Override
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100327 public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
328 mActivity = activity;
329 // TODO: Need to look at the controller interface to see if we can get
330 // rid of passing in the activity directly.
331 mAppController = mActivity;
332 mUI = new VideoUI(mActivity, this, mActivity.getModuleLayoutRoot());
333 mActivity.setPreviewStatusListener(mUI);
Michael Kolb8872c232013-01-29 10:33:22 -0800334
Erin Dahlgrenbd3da262013-12-02 11:48:13 -0800335 SettingsManager settingsManager = mActivity.getSettingsManager();
336 mCameraId = Integer.parseInt(settingsManager.get(SettingsManager.SETTING_CAMERA_ID));
Michael Kolb8872c232013-01-29 10:33:22 -0800337
Michael Kolb8872c232013-01-29 10:33:22 -0800338 /*
339 * To reduce startup time, we start the preview in another thread.
340 * We make sure the preview is started at the end of onCreate.
341 */
Angus Kong20fad242013-11-11 18:23:46 -0800342 requestCamera(mCameraId);
Michael Kolb8872c232013-01-29 10:33:22 -0800343
344 mContentResolver = mActivity.getContentResolver();
345
Michael Kolb8872c232013-01-29 10:33:22 -0800346 // Surface texture is from camera screen nail and startPreview needs it.
347 // This must be done before startPreview.
348 mIsVideoCaptureIntent = isVideoCaptureIntent();
Michael Kolb8872c232013-01-29 10:33:22 -0800349
Michael Kolb8872c232013-01-29 10:33:22 -0800350 mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
Erin Dahlgren21c21a62013-11-19 16:37:38 -0800351 mLocationManager = mActivity.getLocationManager();
Michael Kolb8872c232013-01-29 10:33:22 -0800352
Doris Liu6827ce22013-03-12 19:24:28 -0700353 mUI.setOrientationIndicator(0, false);
Michael Kolb8872c232013-01-29 10:33:22 -0800354 setDisplayOrientation();
355
Doris Liu6827ce22013-03-12 19:24:28 -0700356 mUI.showTimeLapseUI(mCaptureTimeLapse);
Michael Kolb8872c232013-01-29 10:33:22 -0800357 mPendingSwitchCameraId = -1;
Spike Sprague41f68002014-01-24 16:59:25 -0800358
359 mShutterIconId = CameraUtil.getCameraShutterIconId(
360 mAppController.getCurrentModuleIndex(), mAppController.getAndroidContext());
361
Doris Liu6827ce22013-03-12 19:24:28 -0700362 }
363
Erin Dahlgren4efa8b52013-12-17 18:31:35 -0800364 @Override
365 public boolean isUsingBottomBar() {
366 return true;
367 }
368
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800369 private void initializeControlByIntent() {
370 if (isVideoCaptureIntent()) {
Spike Sprague51c877c2014-02-18 11:14:12 -0800371 if (!mDontResetIntentUiOnResume) {
372 mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
373 }
374 // reset the flag
375 mDontResetIntentUiOnResume = false;
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800376 }
377 }
378
Doris Liu6827ce22013-03-12 19:24:28 -0700379 @Override
380 public void onSingleTapUp(View view, int x, int y) {
Erin Dahlgren11213162014-03-12 16:31:12 -0700381 if (mMediaRecorderRecording || mPaused || mCameraDevice == null) {
Doris Liua1ec04a2014-01-13 17:29:40 -0800382 return;
383 }
384 // Check if metering area or focus area is supported.
385 if (!mFocusAreaSupported && !mMeteringAreaSupported) {
386 return;
387 }
388 // Tap to focus.
389 mFocusManager.onSingleTapUp(x, y);
Doris Liu38605742013-08-13 15:01:52 -0700390 }
391
392 private void takeASnapshot() {
Sascha Haeberlinga514b142013-08-13 16:10:01 -0700393 // Only take snapshots if video snapshot is supported by device
Doris Liu38605742013-08-13 15:01:52 -0700394 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800395 if (!mMediaRecorderRecording || mPaused || mSnapshotInProgress || mShutterEnabled) {
Doris Liu38605742013-08-13 15:01:52 -0700396 return;
397 }
398
399 // Set rotation and gps data.
Sascha Haeberlinga7cbfc02014-02-14 11:06:03 +0100400 CameraInfo info = mActivity.getCameraProvider().getCameraInfo()[mCameraId];
401 int rotation = CameraUtil.getJpegRotation(info, mOrientation);
Doris Liu38605742013-08-13 15:01:52 -0700402 mParameters.setRotation(rotation);
403 Location loc = mLocationManager.getCurrentLocation();
404 CameraUtil.setGpsParameters(mParameters, loc);
405 mCameraDevice.setParameters(mParameters);
406
407 Log.v(TAG, "Video snapshot start");
408 mCameraDevice.takePicture(mHandler,
409 null, null, null, new JpegPictureCallback(loc));
410 showVideoSnapshotUI(true);
411 mSnapshotInProgress = true;
Seth Raphael5e09d012013-12-18 13:45:03 -0800412 UsageStatistics.captureEvent(eventprotos.NavigationChange.Mode.VIDEO_STILL,
Andy Huibersf7d4eba2014-03-25 19:06:52 -0700413 null, null, null);
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 Dahlgrenb1641f52014-01-14 15:58:52 -0800522 public HardwareSpec getHardwareSpec() {
Erin Dahlgren49ab9222014-01-28 17:40:28 -0800523 return (mParameters != null ? new HardwareSpecImpl(mParameters) : null);
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800524 }
525
526 @Override
527 public CameraAppUI.BottomBarUISpec getBottomBarSpec() {
528 CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
529
530 bottomBarSpec.enableCamera = true;
531 bottomBarSpec.cameraCallback = mCameraCallback;
532 bottomBarSpec.enableTorchFlash = true;
533 bottomBarSpec.flashCallback = mFlashCallback;
534 bottomBarSpec.hideHdr = true;
Spike Spragueab3adde2014-03-10 12:19:08 -0700535 bottomBarSpec.enableGridLines = true;
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800536
537 if (isVideoCaptureIntent()) {
538 bottomBarSpec.showCancel = true;
539 bottomBarSpec.cancelCallback = mCancelCallback;
540 bottomBarSpec.showDone = true;
541 bottomBarSpec.doneCallback = mDoneCallback;
542 bottomBarSpec.showReview = true;
543 bottomBarSpec.reviewCallback = mReviewCallback;
544 }
545
546 return bottomBarSpec;
Erin Dahlgren0a6a8d82014-01-09 22:17:38 -0800547 }
548
549 @Override
Angus Kong20fad242013-11-11 18:23:46 -0800550 public void onCameraAvailable(CameraProxy cameraProxy) {
551 mCameraDevice = cameraProxy;
Doris Liua1ec04a2014-01-13 17:29:40 -0800552 mInitialParams = mCameraDevice.getParameters();
553 mFocusAreaSupported = CameraUtil.isFocusAreaSupported(mInitialParams);
554 mMeteringAreaSupported = CameraUtil.isMeteringAreaSupported(mInitialParams);
Angus Kong20fad242013-11-11 18:23:46 -0800555 readVideoPreferences();
556 resizeForPreviewAspectRatio();
Doris Liua1ec04a2014-01-13 17:29:40 -0800557 initializeFocusManager();
Doris Liu5a367542014-01-17 17:21:42 -0800558
Angus Kong20fad242013-11-11 18:23:46 -0800559 startPreview();
560 initializeVideoSnapshot();
Angus Kong20fad242013-11-11 18:23:46 -0800561 mUI.initializeZoom(mParameters);
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800562 initializeControlByIntent();
Angus Kong20fad242013-11-11 18:23:46 -0800563 }
564
Michael Kolb8872c232013-01-29 10:33:22 -0800565 private void startPlayVideoActivity() {
566 Intent intent = new Intent(Intent.ACTION_VIEW);
567 intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
568 try {
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700569 mActivity
570 .startActivityForResult(intent, CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
Michael Kolb8872c232013-01-29 10:33:22 -0800571 } catch (ActivityNotFoundException ex) {
572 Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
573 }
574 }
575
ztenghui7b265a62013-09-09 14:58:44 -0700576 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800577 @OnClickAttr
578 public void onReviewPlayClicked(View v) {
579 startPlayVideoActivity();
580 }
581
ztenghui7b265a62013-09-09 14:58:44 -0700582 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800583 @OnClickAttr
584 public void onReviewDoneClicked(View v) {
Doris Liu69ef5ea2013-05-07 13:48:10 -0700585 mIsInReviewMode = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800586 doReturnToCaller(true);
587 }
588
ztenghui7b265a62013-09-09 14:58:44 -0700589 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800590 @OnClickAttr
591 public void onReviewCancelClicked(View v) {
ztenghuiaf3a1972013-09-17 16:51:15 -0700592 // TODO: It should be better to not even insert the URI at all before we
593 // confirm done in review, which means we need to handle temporary video
594 // files in a quite different way than we currently had.
ztenghui70bd0242013-10-14 11:15:44 -0700595 // Make sure we don't delete the Uri sent from the video capture intent.
596 if (mCurrentVideoUriFromMediaSaved) {
ztenghuidfe5b152013-10-08 17:07:15 -0700597 mContentResolver.delete(mCurrentVideoUri, null, null);
598 }
ztenghui638bf9a2013-10-09 10:52:33 -0700599 mIsInReviewMode = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800600 doReturnToCaller(false);
601 }
602
Doris Liu69ef5ea2013-05-07 13:48:10 -0700603 @Override
604 public boolean isInReviewMode() {
605 return mIsInReviewMode;
606 }
607
Michael Kolb8872c232013-01-29 10:33:22 -0800608 private void onStopVideoRecording() {
Sascha Haeberling8793eff2014-01-15 16:33:59 -0800609 mAppController.getCameraAppUI().setSwipeEnabled(true);
Michael Kolb8872c232013-01-29 10:33:22 -0800610 boolean recordFail = stopVideoRecording();
611 if (mIsVideoCaptureIntent) {
Doris Liu3973deb2013-08-21 13:42:22 -0700612 if (mQuickCapture) {
613 doReturnToCaller(!recordFail);
614 } else if (!recordFail) {
615 showCaptureResult();
Michael Kolb8872c232013-01-29 10:33:22 -0800616 }
617 } else if (!recordFail){
618 // Start capture animation.
619 if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
620 // The capture animation is disabled on ICS because we use SurfaceView
621 // for preview during recording. When the recording is done, we switch
622 // back to use SurfaceTexture for preview and we need to stop then start
623 // the preview. This will cause the preview flicker since the preview
624 // will not be continuous for a short period of time.
Sascha Haeberling4f91ab52013-05-21 11:26:13 -0700625
Sascha Haeberling37f36112013-08-06 14:31:52 -0700626 mUI.animateFlash();
Michael Kolb8872c232013-01-29 10:33:22 -0800627 }
628 }
629 }
630
Doris Liu2a7f44c2013-08-12 15:18:53 -0700631 public void onVideoSaved() {
632 if (mIsVideoCaptureIntent) {
633 showCaptureResult();
634 }
635 }
636
Michael Kolb8872c232013-01-29 10:33:22 -0800637 public void onProtectiveCurtainClick(View v) {
638 // Consume clicks
639 }
640
641 @Override
642 public void onShutterButtonClick() {
Sascha Haeberling8793eff2014-01-15 16:33:59 -0800643 if (mSwitchingCamera) {
644 return;
645 }
Michael Kolb8872c232013-01-29 10:33:22 -0800646 boolean stop = mMediaRecorderRecording;
647
648 if (stop) {
649 onStopVideoRecording();
650 } else {
651 startVideoRecording();
652 }
Doris Liu6827ce22013-03-12 19:24:28 -0700653 mUI.enableShutter(false);
Doris Liua1ec04a2014-01-13 17:29:40 -0800654 mFocusManager.onShutterUp();
Michael Kolb8872c232013-01-29 10:33:22 -0800655
656 // Keep the shutter button disabled when in video capture intent
657 // mode and recording is stopped. It'll be re-enabled when
658 // re-take button is clicked.
659 if (!(mIsVideoCaptureIntent && stop)) {
Angus Kong13e87c42013-11-25 10:02:47 -0800660 mHandler.sendEmptyMessageDelayed(MSG_ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
Michael Kolb8872c232013-01-29 10:33:22 -0800661 }
662 }
663
664 @Override
665 public void onShutterButtonFocus(boolean pressed) {
Doris Liuf55f3c42013-11-20 00:24:46 -0800666 // TODO: Remove this when old camera controls are removed from the UI.
Michael Kolb8872c232013-01-29 10:33:22 -0800667 }
668
669 private void readVideoPreferences() {
670 // The preference stores values from ListPreference and is thus string type for all values.
671 // We need to convert it to int manually.
Erin Dahlgrenbd3da262013-12-02 11:48:13 -0800672 SettingsManager settingsManager = mActivity.getSettingsManager();
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700673 if (!settingsManager.isSet(SettingsManager.SETTING_VIDEO_QUALITY_BACK)) {
674 settingsManager.setDefault(SettingsManager.SETTING_VIDEO_QUALITY_BACK);
Doris Liu3f7e0042013-07-31 11:25:09 -0700675 }
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700676 if (!settingsManager.isSet(SettingsManager.SETTING_VIDEO_QUALITY_FRONT)) {
677 settingsManager.setDefault(SettingsManager.SETTING_VIDEO_QUALITY_FRONT);
678 }
679 String videoQuality = settingsManager
680 .get(isCameraFrontFacing() ? SettingsManager.SETTING_VIDEO_QUALITY_FRONT
681 : SettingsManager.SETTING_VIDEO_QUALITY_BACK);
Sascha Haeberlingde303232014-02-07 02:30:53 +0100682 int quality = SettingsUtil.getVideoQuality(videoQuality, mCameraId);
683 Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality);
Michael Kolb8872c232013-01-29 10:33:22 -0800684
685 // Set video quality.
686 Intent intent = mActivity.getIntent();
687 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
688 int extraVideoQuality =
689 intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
690 if (extraVideoQuality > 0) {
691 quality = CamcorderProfile.QUALITY_HIGH;
692 } else { // 0 is mms.
693 quality = CamcorderProfile.QUALITY_LOW;
694 }
695 }
696
697 // Set video duration limit. The limit is read from the preference,
698 // unless it is specified in the intent.
699 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
700 int seconds =
701 intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
702 mMaxVideoDurationInMs = 1000 * seconds;
703 } else {
704 mMaxVideoDurationInMs = CameraSettings.getMaxVideoDuration(mActivity);
705 }
706
Sascha Haeberling8c000c22014-03-17 13:56:05 -0700707 // TODO: Uncomment this block to re-enable time-lapse.
708 /* // Read time lapse recording interval.
Erin Dahlgrenbd3da262013-12-02 11:48:13 -0800709 String frameIntervalStr = settingsManager.get(
710 SettingsManager.SETTING_VIDEO_TIME_LAPSE_FRAME_INTERVAL);
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700711 mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
712 mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
Michael Kolb8872c232013-01-29 10:33:22 -0800713 // TODO: This should be checked instead directly +1000.
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100714 if (mCaptureTimeLapse) {
715 quality += 1000;
Sascha Haeberling8c000c22014-03-17 13:56:05 -0700716 } */
Andy Huibers329e1012014-02-04 08:03:35 -0800717
718 // If quality is not supported, request QUALITY_HIGH which is always supported.
719 if (CamcorderProfile.hasProfile(mCameraId, quality) == false) {
720 quality = CamcorderProfile.QUALITY_HIGH;
721 }
Michael Kolb8872c232013-01-29 10:33:22 -0800722 mProfile = CamcorderProfile.get(mCameraId, quality);
Angus Kong395ee2d2013-07-15 12:42:41 -0700723 mPreferenceRead = true;
ztenghuief01a312013-10-14 15:25:16 -0700724 if (mCameraDevice == null) {
725 return;
726 }
Doris Liu6432cd62013-06-13 17:20:31 -0700727 mParameters = mCameraDevice.getParameters();
Angus Kong5f8c30e2014-03-06 17:15:08 -0800728 Point desiredPreviewSize = getDesiredPreviewSize(mAppController.getAndroidContext(),
729 mParameters, mProfile, mUI.getPreviewScreenSize());
730 mDesiredPreviewWidth = desiredPreviewSize.x;
731 mDesiredPreviewHeight = desiredPreviewSize.y;
Doris Liu6432cd62013-06-13 17:20:31 -0700732 mUI.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800733 Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
734 ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
735 }
736
Angus Kong5f8c30e2014-03-06 17:15:08 -0800737 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
738 /**
739 * Calculates the preview size and stores it in mDesiredPreviewWidth and
740 * mDesiredPreviewHeight. This function checks {@link
741 * android.hardware.Camera.Parameters#getPreferredPreviewSizeForVideo()}
742 * but also considers the current preview area size on screen and make sure
743 * the final preview size will not be smaller than 1/2 of the current
744 * on screen preview area in terms of their short sides.
745 *
746 * @return The preferred preview size or {@code null} if the camera is not
747 * opened yet.
748 */
749 private static Point getDesiredPreviewSize(Context context, Parameters parameters,
750 CamcorderProfile profile, Point previewScreenSize) {
751 if (parameters.getSupportedVideoSizes() == null) {
752 // Driver doesn't support separate outputs for preview and video.
753 return new Point(profile.videoFrameWidth, profile.videoFrameHeight);
754 }
755
756 final int previewScreenShortSide = (previewScreenSize.x < previewScreenSize.y ?
757 previewScreenSize.x : previewScreenSize.y);
758 List<Size> sizes = parameters.getSupportedPreviewSizes();
759 Size preferred = parameters.getPreferredPreviewSizeForVideo();
760 final int preferredPreviewSizeShortSide = (preferred.width < preferred.height ?
761 preferred.width : preferred.height);
762 if (preferredPreviewSizeShortSide * 2 < previewScreenShortSide) {
763 preferred.width = profile.videoFrameWidth;
764 preferred.height = profile.videoFrameHeight;
765 }
766 int product = preferred.width * preferred.height;
767 Iterator<Size> it = sizes.iterator();
768 // Remove the preview sizes that are not preferred.
769 while (it.hasNext()) {
770 Size size = it.next();
771 if (size.width * size.height > product) {
772 it.remove();
773 }
774 }
775 Size optimalSize = CameraUtil.getOptimalPreviewSize(context, sizes,
776 (double) profile.videoFrameWidth / profile.videoFrameHeight);
777 return new Point(optimalSize.width, optimalSize.height);
778 }
779
Michael Kolb8872c232013-01-29 10:33:22 -0800780 private void resizeForPreviewAspectRatio() {
Doris Liue038c162013-12-13 23:06:11 -0800781 mUI.setAspectRatio((float) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800782 }
783
Angus Kong13e87c42013-11-25 10:02:47 -0800784 private void installIntentFilter() {
Michael Kolb8872c232013-01-29 10:33:22 -0800785 // install an intent filter to receive SD card related events.
786 IntentFilter intentFilter =
787 new IntentFilter(Intent.ACTION_MEDIA_EJECT);
788 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
789 intentFilter.addDataScheme("file");
790 mReceiver = new MyBroadcastReceiver();
791 mActivity.registerReceiver(mReceiver, intentFilter);
792 }
793
Michael Kolb8872c232013-01-29 10:33:22 -0800794 private void setDisplayOrientation() {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700795 mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
796 mCameraDisplayOrientation = CameraUtil.getDisplayOrientation(mDisplayRotation, mCameraId);
Doris Liu6432cd62013-06-13 17:20:31 -0700797 // Change the camera display orientation
798 if (mCameraDevice != null) {
799 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800800 }
Doris Liua1ec04a2014-01-13 17:29:40 -0800801 if (mFocusManager != null) {
802 mFocusManager.setDisplayOrientation(mCameraDisplayOrientation);
803 }
Doris Liu6432cd62013-06-13 17:20:31 -0700804 }
805
806 @Override
807 public void updateCameraOrientation() {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100808 if (mMediaRecorderRecording) {
809 return;
810 }
Angus Kongb50b5cb2013-08-09 14:55:20 -0700811 if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
Doris Liu6432cd62013-06-13 17:20:31 -0700812 setDisplayOrientation();
813 }
Michael Kolb8872c232013-01-29 10:33:22 -0800814 }
815
Doris Liu6827ce22013-03-12 19:24:28 -0700816 @Override
Doris Liu70da9182013-12-17 18:41:15 -0800817 public void updatePreviewAspectRatio(float aspectRatio) {
818 mAppController.updatePreviewAspectRatio(aspectRatio);
819 }
820
821 @Override
Doris Liu6827ce22013-03-12 19:24:28 -0700822 public int onZoomChanged(int index) {
823 // Not useful to change zoom value when the activity is paused.
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100824 if (mPaused) {
825 return index;
826 }
Doris Liu6827ce22013-03-12 19:24:28 -0700827 mZoomValue = index;
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100828 if (mParameters == null || mCameraDevice == null) {
829 return index;
830 }
Doris Liu6827ce22013-03-12 19:24:28 -0700831 // Set zoom parameters asynchronously
832 mParameters.setZoom(mZoomValue);
Doris Liu6432cd62013-06-13 17:20:31 -0700833 mCameraDevice.setParameters(mParameters);
834 Parameters p = mCameraDevice.getParameters();
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100835 if (p != null) {
836 return p.getZoom();
837 }
Doris Liu6827ce22013-03-12 19:24:28 -0700838 return index;
839 }
Angus Kong395ee2d2013-07-15 12:42:41 -0700840
Michael Kolb8872c232013-01-29 10:33:22 -0800841 private void startPreview() {
842 Log.v(TAG, "startPreview");
843
Erin Dahlgrend8de0772014-02-03 10:12:27 -0800844 SurfaceTexture surfaceTexture = mActivity.getCameraAppUI().getSurfaceTexture();
ztenghuief01a312013-10-14 15:25:16 -0700845 if (!mPreferenceRead || surfaceTexture == null || mPaused == true ||
846 mCameraDevice == null) {
847 return;
848 }
Angus Kong395ee2d2013-07-15 12:42:41 -0700849
Angus Kong2bca2102014-03-11 16:27:30 -0700850 mCameraDevice.setErrorCallback(mHandler, mErrorCallback);
Michael Kolb8872c232013-01-29 10:33:22 -0800851 if (mPreviewing == true) {
852 stopPreview();
Michael Kolb8872c232013-01-29 10:33:22 -0800853 }
854
Michael Kolb8872c232013-01-29 10:33:22 -0800855 setDisplayOrientation();
Doris Liu6432cd62013-06-13 17:20:31 -0700856 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800857 setCameraParameters();
858
Doris Liua1ec04a2014-01-13 17:29:40 -0800859 if (mFocusManager != null) {
860 // If the focus mode is continuous autofocus, call cancelAutoFocus
861 // to resume it because it may have been paused by autoFocus call.
862 String focusMode = mFocusManager.getFocusMode();
863 if (CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE.equals(focusMode)) {
864 mCameraDevice.cancelAutoFocus();
865 }
866 }
Doris Liu5a367542014-01-17 17:21:42 -0800867
868 // This is to notify app controller that preview will start next, so app
869 // controller can set preview callbacks if needed. This has to happen before
870 // preview is started as a workaround of the framework issue related to preview
871 // callbacks that causes preview stretch and crash. (More details see b/12210027
872 // and b/12591410
873 mAppController.onPreviewReadyToStart();
Michael Kolb8872c232013-01-29 10:33:22 -0800874 try {
Doris Liu3973deb2013-08-21 13:42:22 -0700875 mCameraDevice.setPreviewTexture(surfaceTexture);
876 mCameraDevice.startPreview();
877 mPreviewing = true;
878 onPreviewStarted();
Michael Kolb8872c232013-01-29 10:33:22 -0800879 } catch (Throwable ex) {
880 closeCamera();
881 throw new RuntimeException("startPreview failed", ex);
Michael Kolb8872c232013-01-29 10:33:22 -0800882 }
Michael Kolbb1aeb392013-03-11 12:37:40 -0700883 }
884
885 private void onPreviewStarted() {
Doris Liu6827ce22013-03-12 19:24:28 -0700886 mUI.enableShutter(true);
Doris Liu2b906b82013-12-10 16:34:08 -0800887 mAppController.onPreviewStarted();
Doris Liua1ec04a2014-01-13 17:29:40 -0800888 if (mFocusManager != null) {
889 mFocusManager.onPreviewStarted();
890 }
Michael Kolb8872c232013-01-29 10:33:22 -0800891 }
892
Doris Liu6827ce22013-03-12 19:24:28 -0700893 @Override
Sameer Padaladb81ce62014-03-21 15:33:56 -0700894 public void onPreviewInitialDataReceived() {
895 SmartCameraHelper.register(mCameraDevice, mParameters.getPreviewSize(), mActivity,
896 (ViewGroup) mActivity.findViewById(R.id.camera_app_root));
897 }
898
899 @Override
Doris Liu6827ce22013-03-12 19:24:28 -0700900 public void stopPreview() {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100901 if (!mPreviewing) {
902 return;
903 }
Doris Liu6432cd62013-06-13 17:20:31 -0700904 mCameraDevice.stopPreview();
Doris Liua1ec04a2014-01-13 17:29:40 -0800905 if (mFocusManager != null) {
906 mFocusManager.onPreviewStopped();
907 }
Michael Kolb8872c232013-01-29 10:33:22 -0800908 mPreviewing = false;
Sameer Padaladb81ce62014-03-21 15:33:56 -0700909 SmartCameraHelper.tearDown();
Michael Kolb8872c232013-01-29 10:33:22 -0800910 }
911
Michael Kolb8872c232013-01-29 10:33:22 -0800912 private void closeCamera() {
Michael Kolb8872c232013-01-29 10:33:22 -0800913 Log.v(TAG, "closeCamera");
Doris Liu6432cd62013-06-13 17:20:31 -0700914 if (mCameraDevice == null) {
Michael Kolb8872c232013-01-29 10:33:22 -0800915 Log.d(TAG, "already stopped.");
916 return;
917 }
Doris Liu6432cd62013-06-13 17:20:31 -0700918 mCameraDevice.setZoomChangeListener(null);
Angus Kong2bca2102014-03-11 16:27:30 -0700919 mCameraDevice.setErrorCallback(null, null);
Angus Kong20fad242013-11-11 18:23:46 -0800920 mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
Angus Kong395ee2d2013-07-15 12:42:41 -0700921 mCameraDevice = null;
Michael Kolb8872c232013-01-29 10:33:22 -0800922 mPreviewing = false;
923 mSnapshotInProgress = false;
Doris Liua1ec04a2014-01-13 17:29:40 -0800924 if (mFocusManager != null) {
925 mFocusManager.onCameraReleased();
926 }
Michael Kolb8872c232013-01-29 10:33:22 -0800927 }
928
Michael Kolb8872c232013-01-29 10:33:22 -0800929 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800930 public boolean onBackPressed() {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100931 if (mPaused) {
932 return true;
933 }
Michael Kolb8872c232013-01-29 10:33:22 -0800934 if (mMediaRecorderRecording) {
935 onStopVideoRecording();
936 return true;
Michael Kolb8872c232013-01-29 10:33:22 -0800937 } else {
Doris Liuf9e4f8f2013-12-04 18:04:22 -0800938 return false;
Michael Kolb8872c232013-01-29 10:33:22 -0800939 }
940 }
941
942 @Override
943 public boolean onKeyDown(int keyCode, KeyEvent event) {
944 // Do not handle any key if the activity is paused.
945 if (mPaused) {
946 return true;
947 }
948
949 switch (keyCode) {
950 case KeyEvent.KEYCODE_CAMERA:
951 if (event.getRepeatCount() == 0) {
Doris Liu6827ce22013-03-12 19:24:28 -0700952 mUI.clickShutter();
Michael Kolb8872c232013-01-29 10:33:22 -0800953 return true;
954 }
Michael Kolb8872c232013-01-29 10:33:22 -0800955 case KeyEvent.KEYCODE_DPAD_CENTER:
956 if (event.getRepeatCount() == 0) {
Doris Liu6827ce22013-03-12 19:24:28 -0700957 mUI.clickShutter();
Michael Kolb8872c232013-01-29 10:33:22 -0800958 return true;
959 }
Michael Kolb8872c232013-01-29 10:33:22 -0800960 case KeyEvent.KEYCODE_MENU:
Erin Dahlgren15691af2014-03-14 14:10:57 -0700961 // Consume menu button presses during capture.
962 return mMediaRecorderRecording;
Michael Kolb8872c232013-01-29 10:33:22 -0800963 }
964 return false;
965 }
966
967 @Override
968 public boolean onKeyUp(int keyCode, KeyEvent event) {
969 switch (keyCode) {
970 case KeyEvent.KEYCODE_CAMERA:
Doris Liu6827ce22013-03-12 19:24:28 -0700971 mUI.pressShutter(false);
Michael Kolb8872c232013-01-29 10:33:22 -0800972 return true;
Erin Dahlgren15691af2014-03-14 14:10:57 -0700973 case KeyEvent.KEYCODE_MENU:
974 // Consume menu button presses during capture.
975 return mMediaRecorderRecording;
Michael Kolb8872c232013-01-29 10:33:22 -0800976 }
977 return false;
978 }
979
Doris Liu6827ce22013-03-12 19:24:28 -0700980 @Override
981 public boolean isVideoCaptureIntent() {
Michael Kolb8872c232013-01-29 10:33:22 -0800982 String action = mActivity.getIntent().getAction();
983 return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
984 }
985
986 private void doReturnToCaller(boolean valid) {
987 Intent resultIntent = new Intent();
988 int resultCode;
989 if (valid) {
990 resultCode = Activity.RESULT_OK;
991 resultIntent.setData(mCurrentVideoUri);
992 } else {
993 resultCode = Activity.RESULT_CANCELED;
994 }
995 mActivity.setResultEx(resultCode, resultIntent);
996 mActivity.finish();
997 }
998
999 private void cleanupEmptyFile() {
1000 if (mVideoFilename != null) {
1001 File f = new File(mVideoFilename);
1002 if (f.length() == 0 && f.delete()) {
1003 Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
1004 mVideoFilename = null;
1005 }
1006 }
1007 }
1008
Michael Kolb8872c232013-01-29 10:33:22 -08001009 // Prepares media recorder.
1010 private void initializeRecorder() {
1011 Log.v(TAG, "initializeRecorder");
1012 // If the mCameraDevice is null, then this activity is going to finish
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001013 if (mCameraDevice == null) {
1014 return;
1015 }
Michael Kolb8872c232013-01-29 10:33:22 -08001016
Michael Kolb8872c232013-01-29 10:33:22 -08001017 Intent intent = mActivity.getIntent();
1018 Bundle myExtras = intent.getExtras();
1019
1020 long requestedSizeLimit = 0;
1021 closeVideoFileDescriptor();
ztenghui70bd0242013-10-14 11:15:44 -07001022 mCurrentVideoUriFromMediaSaved = false;
Michael Kolb8872c232013-01-29 10:33:22 -08001023 if (mIsVideoCaptureIntent && myExtras != null) {
1024 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1025 if (saveUri != null) {
1026 try {
1027 mVideoFileDescriptor =
1028 mContentResolver.openFileDescriptor(saveUri, "rw");
1029 mCurrentVideoUri = saveUri;
1030 } catch (java.io.FileNotFoundException ex) {
1031 // invalid uri
1032 Log.e(TAG, ex.toString());
1033 }
1034 }
1035 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1036 }
1037 mMediaRecorder = new MediaRecorder();
1038
Michael Kolb8872c232013-01-29 10:33:22 -08001039 // Unlock the camera object before passing it to media recorder.
Doris Liu6432cd62013-06-13 17:20:31 -07001040 mCameraDevice.unlock();
Doris Liu6432cd62013-06-13 17:20:31 -07001041 mMediaRecorder.setCamera(mCameraDevice.getCamera());
Michael Kolb8872c232013-01-29 10:33:22 -08001042 if (!mCaptureTimeLapse) {
1043 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
1044 }
1045 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
1046 mMediaRecorder.setProfile(mProfile);
Michael Kolbf9542362013-05-30 07:45:41 -07001047 mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight);
Michael Kolb8872c232013-01-29 10:33:22 -08001048 mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
1049 if (mCaptureTimeLapse) {
1050 double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs;
1051 setCaptureRate(mMediaRecorder, fps);
1052 }
1053
1054 setRecordLocation();
1055
1056 // Set output file.
1057 // Try Uri in the intent first. If it doesn't exist, use our own
1058 // instead.
1059 if (mVideoFileDescriptor != null) {
1060 mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1061 } else {
1062 generateVideoFilename(mProfile.fileFormat);
1063 mMediaRecorder.setOutputFile(mVideoFilename);
1064 }
1065
1066 // Set maximum file size.
Angus Kong2dcc0a92013-09-25 13:00:08 -07001067 long maxFileSize = mActivity.getStorageSpaceBytes() - Storage.LOW_STORAGE_THRESHOLD_BYTES;
Michael Kolb8872c232013-01-29 10:33:22 -08001068 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1069 maxFileSize = requestedSizeLimit;
1070 }
1071
1072 try {
1073 mMediaRecorder.setMaxFileSize(maxFileSize);
1074 } catch (RuntimeException exception) {
1075 // We are going to ignore failure of setMaxFileSize here, as
1076 // a) The composer selected may simply not support it, or
1077 // b) The underlying media framework may not handle 64-bit range
1078 // on the size restriction.
1079 }
1080
1081 // See android.hardware.Camera.Parameters.setRotation for
1082 // documentation.
1083 // Note that mOrientation here is the device orientation, which is the opposite of
1084 // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
1085 // which is the orientation the graphics need to rotate in order to render correctly.
1086 int rotation = 0;
1087 if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
Angus Kong20fad242013-11-11 18:23:46 -08001088 CameraInfo info = mActivity.getCameraProvider().getCameraInfo()[mCameraId];
Sascha Haeberling6ccec202014-03-11 09:44:34 -07001089 if (isCameraFrontFacing()) {
Michael Kolb8872c232013-01-29 10:33:22 -08001090 rotation = (info.orientation - mOrientation + 360) % 360;
1091 } else { // back-facing camera
1092 rotation = (info.orientation + mOrientation) % 360;
1093 }
1094 }
1095 mMediaRecorder.setOrientationHint(rotation);
1096
1097 try {
1098 mMediaRecorder.prepare();
1099 } catch (IOException e) {
1100 Log.e(TAG, "prepare failed for " + mVideoFilename, e);
1101 releaseMediaRecorder();
1102 throw new RuntimeException(e);
1103 }
1104
1105 mMediaRecorder.setOnErrorListener(this);
1106 mMediaRecorder.setOnInfoListener(this);
1107 }
1108
Michael Kolb8872c232013-01-29 10:33:22 -08001109 private static void setCaptureRate(MediaRecorder recorder, double fps) {
1110 recorder.setCaptureRate(fps);
1111 }
1112
Michael Kolb8872c232013-01-29 10:33:22 -08001113 private void setRecordLocation() {
Sascha Haeberling638e6f02013-09-18 14:28:51 -07001114 Location loc = mLocationManager.getCurrentLocation();
1115 if (loc != null) {
1116 mMediaRecorder.setLocation((float) loc.getLatitude(),
1117 (float) loc.getLongitude());
Michael Kolb8872c232013-01-29 10:33:22 -08001118 }
1119 }
1120
Michael Kolb8872c232013-01-29 10:33:22 -08001121 private void releaseMediaRecorder() {
1122 Log.v(TAG, "Releasing media recorder.");
1123 if (mMediaRecorder != null) {
1124 cleanupEmptyFile();
1125 mMediaRecorder.reset();
1126 mMediaRecorder.release();
1127 mMediaRecorder = null;
1128 }
1129 mVideoFilename = null;
1130 }
1131
Michael Kolb8872c232013-01-29 10:33:22 -08001132 private void generateVideoFilename(int outputFileFormat) {
1133 long dateTaken = System.currentTimeMillis();
1134 String title = createName(dateTaken);
1135 // Used when emailing.
1136 String filename = title + convertOutputFormatToFileExt(outputFileFormat);
1137 String mime = convertOutputFormatToMimeType(outputFileFormat);
1138 String path = Storage.DIRECTORY + '/' + filename;
1139 String tmpPath = path + ".tmp";
Ruben Brunk16007962013-04-19 15:27:57 -07001140 mCurrentVideoValues = new ContentValues(9);
Michael Kolb8872c232013-01-29 10:33:22 -08001141 mCurrentVideoValues.put(Video.Media.TITLE, title);
1142 mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
1143 mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
Ruben Brunk16007962013-04-19 15:27:57 -07001144 mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000);
Michael Kolb8872c232013-01-29 10:33:22 -08001145 mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
1146 mCurrentVideoValues.put(Video.Media.DATA, path);
Sam Juddde3e9ab2014-03-17 13:07:22 -07001147 mCurrentVideoValues.put(Video.Media.WIDTH, mProfile.videoFrameWidth);
1148 mCurrentVideoValues.put(Video.Media.HEIGHT, mProfile.videoFrameHeight);
Michael Kolb8872c232013-01-29 10:33:22 -08001149 mCurrentVideoValues.put(Video.Media.RESOLUTION,
1150 Integer.toString(mProfile.videoFrameWidth) + "x" +
1151 Integer.toString(mProfile.videoFrameHeight));
1152 Location loc = mLocationManager.getCurrentLocation();
1153 if (loc != null) {
1154 mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
1155 mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
1156 }
Michael Kolb8872c232013-01-29 10:33:22 -08001157 mVideoFilename = tmpPath;
1158 Log.v(TAG, "New video filename: " + mVideoFilename);
1159 }
1160
Angus Kong83a99ae2013-04-17 15:37:07 -07001161 private void saveVideo() {
Michael Kolb8872c232013-01-29 10:33:22 -08001162 if (mVideoFileDescriptor == null) {
Michael Kolb8872c232013-01-29 10:33:22 -08001163 long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
1164 if (duration > 0) {
1165 if (mCaptureTimeLapse) {
1166 duration = getTimeLapseVideoLength(duration);
1167 }
Michael Kolb8872c232013-01-29 10:33:22 -08001168 } else {
1169 Log.w(TAG, "Video duration <= 0 : " + duration);
1170 }
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08001171 getServices().getMediaSaver().addVideo(mCurrentVideoFilename,
Angus Kong83a99ae2013-04-17 15:37:07 -07001172 duration, mCurrentVideoValues,
1173 mOnVideoSavedListener, mContentResolver);
Michael Kolb8872c232013-01-29 10:33:22 -08001174 }
1175 mCurrentVideoValues = null;
Michael Kolb8872c232013-01-29 10:33:22 -08001176 }
1177
1178 private void deleteVideoFile(String fileName) {
1179 Log.v(TAG, "Deleting video " + fileName);
1180 File f = new File(fileName);
1181 if (!f.delete()) {
1182 Log.v(TAG, "Could not delete " + fileName);
1183 }
1184 }
1185
Michael Kolb8872c232013-01-29 10:33:22 -08001186 // from MediaRecorder.OnErrorListener
1187 @Override
1188 public void onError(MediaRecorder mr, int what, int extra) {
1189 Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
1190 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1191 // We may have run out of space on the sdcard.
1192 stopVideoRecording();
1193 mActivity.updateStorageSpaceAndHint();
1194 }
1195 }
1196
1197 // from MediaRecorder.OnInfoListener
1198 @Override
1199 public void onInfo(MediaRecorder mr, int what, int extra) {
1200 if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001201 if (mMediaRecorderRecording) {
1202 onStopVideoRecording();
1203 }
Michael Kolb8872c232013-01-29 10:33:22 -08001204 } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001205 if (mMediaRecorderRecording) {
1206 onStopVideoRecording();
1207 }
Michael Kolb8872c232013-01-29 10:33:22 -08001208
1209 // Show the toast.
1210 Toast.makeText(mActivity, R.string.video_reach_size_limit,
1211 Toast.LENGTH_LONG).show();
1212 }
1213 }
1214
1215 /*
1216 * Make sure we're not recording music playing in the background, ask the
1217 * MediaPlaybackService to pause playback.
1218 */
1219 private void pauseAudioPlayback() {
Marco Nelissen20694b22013-10-29 15:27:24 -07001220 AudioManager am = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);
1221 am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
Michael Kolb8872c232013-01-29 10:33:22 -08001222 }
1223
1224 // For testing.
1225 public boolean isRecording() {
1226 return mMediaRecorderRecording;
1227 }
1228
1229 private void startVideoRecording() {
1230 Log.v(TAG, "startVideoRecording");
Sascha Haeberling37f36112013-08-06 14:31:52 -07001231 mUI.cancelAnimations();
Doris Liu6432cd62013-06-13 17:20:31 -07001232 mUI.setSwipingEnabled(false);
Doris Liu38c6bc32014-01-16 18:03:18 -08001233 mUI.showFocusUI(false);
Doris Liud6487c92014-02-28 10:35:45 -08001234 mUI.showVideoRecordingHints(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001235
1236 mActivity.updateStorageSpaceAndHint();
Angus Kong2dcc0a92013-09-25 13:00:08 -07001237 if (mActivity.getStorageSpaceBytes() <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
Michael Kolb8872c232013-01-29 10:33:22 -08001238 Log.v(TAG, "Storage issue, ignore the start request");
1239 return;
1240 }
1241
Angus Kong9ef99252013-07-18 18:04:19 -07001242 //??
1243 //if (!mCameraDevice.waitDone()) return;
Michael Kolb8872c232013-01-29 10:33:22 -08001244 mCurrentVideoUri = null;
Doris Liu3973deb2013-08-21 13:42:22 -07001245
1246 initializeRecorder();
1247 if (mMediaRecorder == null) {
1248 Log.e(TAG, "Fail to initialize media recorder");
1249 return;
Michael Kolb8872c232013-01-29 10:33:22 -08001250 }
1251
1252 pauseAudioPlayback();
1253
Doris Liu3973deb2013-08-21 13:42:22 -07001254 try {
1255 mMediaRecorder.start(); // Recording is now started
1256 } catch (RuntimeException e) {
1257 Log.e(TAG, "Could not start media recorder. ", e);
1258 releaseMediaRecorder();
1259 // If start fails, frameworks will not lock the camera for us.
1260 mCameraDevice.lock();
1261 return;
Michael Kolb8872c232013-01-29 10:33:22 -08001262 }
Sascha Haeberling8793eff2014-01-15 16:33:59 -08001263 mAppController.getCameraAppUI().setSwipeEnabled(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001264
1265 // Make sure the video recording has started before announcing
1266 // this in accessibility.
Doris Liu6432cd62013-06-13 17:20:31 -07001267 AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
Michael Kolb8872c232013-01-29 10:33:22 -08001268 mActivity.getString(R.string.video_recording_started));
1269
Angus Kong104e0012013-04-03 16:56:18 -07001270 // The parameters might have been altered by MediaRecorder already.
1271 // We need to force mCameraDevice to refresh before getting it.
Doris Liu6432cd62013-06-13 17:20:31 -07001272 mCameraDevice.refreshParameters();
Michael Kolb8872c232013-01-29 10:33:22 -08001273 // The parameters may have been changed by MediaRecorder upon starting
1274 // recording. We need to alter the parameters if we support camcorder
1275 // zoom. To reduce latency when setting the parameters during zoom, we
1276 // update mParameters here once.
Sascha Haeberling638e6f02013-09-18 14:28:51 -07001277 mParameters = mCameraDevice.getParameters();
Michael Kolb8872c232013-01-29 10:33:22 -08001278
Michael Kolb8872c232013-01-29 10:33:22 -08001279 mMediaRecorderRecording = true;
Angus Kong9f1db522013-11-09 16:25:59 -08001280 mActivity.lockOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001281 mRecordingStartTime = SystemClock.uptimeMillis();
Doris Liu4df91582014-03-21 18:33:57 -07001282
1283 // A special case of mode options closing: during capture it should
1284 // not be possible to change mode state.
1285 mAppController.getCameraAppUI().hideModeOptions();
1286 mAppController.getCameraAppUI().animateBottomBarToVideoStop(R.drawable.ic_stop);
Doris Liufe6596c2013-10-08 11:03:37 -07001287 mUI.showRecordingUI(true);
Michael Kolb8872c232013-01-29 10:33:22 -08001288
Doris Liua1ec04a2014-01-13 17:29:40 -08001289 setFocusParameters();
Michael Kolb8872c232013-01-29 10:33:22 -08001290 updateRecordingTime();
Angus Kong13e87c42013-11-25 10:02:47 -08001291 mActivity.enableKeepScreenOn(true);
Michael Kolb8872c232013-01-29 10:33:22 -08001292 }
1293
Sascha Haeberling37f36112013-08-06 14:31:52 -07001294 private Bitmap getVideoThumbnail() {
Michael Kolb8872c232013-01-29 10:33:22 -08001295 Bitmap bitmap = null;
1296 if (mVideoFileDescriptor != null) {
1297 bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
Doris Liu3c2fca32013-02-13 18:28:03 -08001298 mDesiredPreviewWidth);
Doris Liu2a7f44c2013-08-12 15:18:53 -07001299 } else if (mCurrentVideoUri != null) {
1300 try {
1301 mVideoFileDescriptor = mContentResolver.openFileDescriptor(mCurrentVideoUri, "r");
1302 bitmap = Thumbnail.createVideoThumbnailBitmap(
1303 mVideoFileDescriptor.getFileDescriptor(), mDesiredPreviewWidth);
1304 } catch (java.io.FileNotFoundException ex) {
1305 // invalid uri
1306 Log.e(TAG, ex.toString());
1307 }
Michael Kolb8872c232013-01-29 10:33:22 -08001308 }
Doris Liu3973deb2013-08-21 13:42:22 -07001309
Michael Kolb8872c232013-01-29 10:33:22 -08001310 if (bitmap != null) {
1311 // MetadataRetriever already rotates the thumbnail. We should rotate
1312 // it to match the UI orientation (and mirror if it is front-facing camera).
Sascha Haeberling6ccec202014-03-11 09:44:34 -07001313 bitmap = CameraUtil.rotateAndMirror(bitmap, 0, isCameraFrontFacing());
Sascha Haeberling37f36112013-08-06 14:31:52 -07001314 }
1315 return bitmap;
1316 }
1317
1318 private void showCaptureResult() {
1319 mIsInReviewMode = true;
1320 Bitmap bitmap = getVideoThumbnail();
1321 if (bitmap != null) {
Doris Liu6827ce22013-03-12 19:24:28 -07001322 mUI.showReviewImage(bitmap);
Michael Kolb8872c232013-01-29 10:33:22 -08001323 }
Doris Liu6827ce22013-03-12 19:24:28 -07001324 mUI.showReviewControls();
Doris Liu6827ce22013-03-12 19:24:28 -07001325 mUI.showTimeLapseUI(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001326 }
1327
Michael Kolb8872c232013-01-29 10:33:22 -08001328 private boolean stopVideoRecording() {
1329 Log.v(TAG, "stopVideoRecording");
Doris Liu6432cd62013-06-13 17:20:31 -07001330 mUI.setSwipingEnabled(true);
Doris Liu38c6bc32014-01-16 18:03:18 -08001331 mUI.showFocusUI(true);
Doris Liud6487c92014-02-28 10:35:45 -08001332 mUI.showVideoRecordingHints(true);
Michael Kolb8872c232013-01-29 10:33:22 -08001333
1334 boolean fail = false;
1335 if (mMediaRecorderRecording) {
1336 boolean shouldAddToMediaStoreNow = false;
1337
1338 try {
Doris Liu3973deb2013-08-21 13:42:22 -07001339 mMediaRecorder.setOnErrorListener(null);
1340 mMediaRecorder.setOnInfoListener(null);
1341 mMediaRecorder.stop();
1342 shouldAddToMediaStoreNow = true;
Michael Kolb8872c232013-01-29 10:33:22 -08001343 mCurrentVideoFilename = mVideoFilename;
1344 Log.v(TAG, "stopVideoRecording: Setting current video filename: "
1345 + mCurrentVideoFilename);
Seth Raphael5e09d012013-12-18 13:45:03 -08001346 float duration = (SystemClock.uptimeMillis() - mRecordingStartTime) / 1000;
Spike Sprague51c877c2014-02-18 11:14:12 -08001347 String statisticFilename = (mCurrentVideoFilename == null
1348 ? "INTENT"
1349 : mCurrentVideoFilename);
Seth Raphael5e09d012013-12-18 13:45:03 -08001350 UsageStatistics.captureEvent(eventprotos.NavigationChange.Mode.VIDEO_CAPTURE,
Spike Sprague51c877c2014-02-18 11:14:12 -08001351 statisticFilename, mParameters, duration);
Doris Liu6432cd62013-06-13 17:20:31 -07001352 AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
Angus Kong13e87c42013-11-25 10:02:47 -08001353 mActivity.getAndroidContext().getString(R.string
Seth Raphael5e09d012013-12-18 13:45:03 -08001354 .video_recording_stopped));
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 }
Michael Kolb8872c232013-01-29 10:33:22 -08001403 return fail;
1404 }
1405
Michael Kolb8872c232013-01-29 10:33:22 -08001406 private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
1407 long seconds = milliSeconds / 1000; // round down to compute seconds
1408 long minutes = seconds / 60;
1409 long hours = minutes / 60;
1410 long remainderMinutes = minutes - (hours * 60);
1411 long remainderSeconds = seconds - (minutes * 60);
1412
1413 StringBuilder timeStringBuilder = new StringBuilder();
1414
1415 // Hours
1416 if (hours > 0) {
1417 if (hours < 10) {
1418 timeStringBuilder.append('0');
1419 }
1420 timeStringBuilder.append(hours);
1421
1422 timeStringBuilder.append(':');
1423 }
1424
1425 // Minutes
1426 if (remainderMinutes < 10) {
1427 timeStringBuilder.append('0');
1428 }
1429 timeStringBuilder.append(remainderMinutes);
1430 timeStringBuilder.append(':');
1431
1432 // Seconds
1433 if (remainderSeconds < 10) {
1434 timeStringBuilder.append('0');
1435 }
1436 timeStringBuilder.append(remainderSeconds);
1437
1438 // Centi seconds
1439 if (displayCentiSeconds) {
1440 timeStringBuilder.append('.');
1441 long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
1442 if (remainderCentiSeconds < 10) {
1443 timeStringBuilder.append('0');
1444 }
1445 timeStringBuilder.append(remainderCentiSeconds);
1446 }
1447
1448 return timeStringBuilder.toString();
1449 }
1450
1451 private long getTimeLapseVideoLength(long deltaMs) {
1452 // For better approximation calculate fractional number of frames captured.
1453 // This will update the video time at a higher resolution.
1454 double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
1455 return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
1456 }
1457
1458 private void updateRecordingTime() {
1459 if (!mMediaRecorderRecording) {
1460 return;
1461 }
1462 long now = SystemClock.uptimeMillis();
1463 long delta = now - mRecordingStartTime;
1464
1465 // Starting a minute before reaching the max duration
1466 // limit, we'll countdown the remaining time instead.
1467 boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1468 && delta >= mMaxVideoDurationInMs - 60000);
1469
1470 long deltaAdjusted = delta;
1471 if (countdownRemainingTime) {
1472 deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
1473 }
1474 String text;
1475
1476 long targetNextUpdateDelay;
1477 if (!mCaptureTimeLapse) {
1478 text = millisecondToTimeString(deltaAdjusted, false);
1479 targetNextUpdateDelay = 1000;
1480 } else {
1481 // The length of time lapse video is different from the length
1482 // of the actual wall clock time elapsed. Display the video length
1483 // only in format hh:mm:ss.dd, where dd are the centi seconds.
1484 text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
1485 targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
1486 }
1487
Doris Liu6827ce22013-03-12 19:24:28 -07001488 mUI.setRecordingTime(text);
Michael Kolb8872c232013-01-29 10:33:22 -08001489
1490 if (mRecordingTimeCountsDown != countdownRemainingTime) {
1491 // Avoid setting the color on every update, do it only
1492 // when it needs changing.
1493 mRecordingTimeCountsDown = countdownRemainingTime;
1494
1495 int color = mActivity.getResources().getColor(countdownRemainingTime
1496 ? R.color.recording_time_remaining_text
1497 : R.color.recording_time_elapsed_text);
1498
Doris Liu6827ce22013-03-12 19:24:28 -07001499 mUI.setRecordingTimeTextColor(color);
Michael Kolb8872c232013-01-29 10:33:22 -08001500 }
1501
1502 long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
Angus Kong13e87c42013-11-25 10:02:47 -08001503 mHandler.sendEmptyMessageDelayed(MSG_UPDATE_RECORD_TIME, actualNextUpdateDelay);
Michael Kolb8872c232013-01-29 10:33:22 -08001504 }
1505
1506 private static boolean isSupported(String value, List<String> supported) {
1507 return supported == null ? false : supported.indexOf(value) >= 0;
1508 }
1509
1510 @SuppressWarnings("deprecation")
1511 private void setCameraParameters() {
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001512 SettingsManager settingsManager = mActivity.getSettingsManager();
1513
Michael Kolb8872c232013-01-29 10:33:22 -08001514 mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
Angus Kongb50b5cb2013-08-09 14:55:20 -07001515 int[] fpsRange = CameraUtil.getMaxPreviewFpsRange(mParameters);
Doris Liu6432cd62013-06-13 17:20:31 -07001516 if (fpsRange.length > 0) {
1517 mParameters.setPreviewFpsRange(
1518 fpsRange[Parameters.PREVIEW_FPS_MIN_INDEX],
1519 fpsRange[Parameters.PREVIEW_FPS_MAX_INDEX]);
1520 } else {
1521 mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
1522 }
Michael Kolb8872c232013-01-29 10:33:22 -08001523
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001524 enableTorchMode(settingsManager.isCameraBackFacing());
Michael Kolb8872c232013-01-29 10:33:22 -08001525
1526 // Set white balance parameter.
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001527 String whiteBalance = settingsManager.get(SettingsManager.SETTING_WHITE_BALANCE);
Michael Kolb8872c232013-01-29 10:33:22 -08001528 if (isSupported(whiteBalance,
1529 mParameters.getSupportedWhiteBalance())) {
1530 mParameters.setWhiteBalance(whiteBalance);
1531 } else {
1532 whiteBalance = mParameters.getWhiteBalance();
1533 if (whiteBalance == null) {
1534 whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1535 }
1536 }
1537
1538 // Set zoom.
1539 if (mParameters.isZoomSupported()) {
1540 mParameters.setZoom(mZoomValue);
1541 }
Doris Liua1ec04a2014-01-13 17:29:40 -08001542 updateFocusParameters();
Michael Kolb8872c232013-01-29 10:33:22 -08001543
Angus Kongb50b5cb2013-08-09 14:55:20 -07001544 mParameters.set(CameraUtil.RECORDING_HINT, CameraUtil.TRUE);
Michael Kolb8872c232013-01-29 10:33:22 -08001545
1546 // Enable video stabilization. Convenience methods not available in API
1547 // level <= 14
1548 String vstabSupported = mParameters.get("video-stabilization-supported");
1549 if ("true".equals(vstabSupported)) {
1550 mParameters.set("video-stabilization", "true");
1551 }
1552
1553 // Set picture size.
1554 // The logic here is different from the logic in still-mode camera.
1555 // There we determine the preview size based on the picture size, but
1556 // here we determine the picture size based on the preview size.
1557 List<Size> supported = 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);
1560 Size original = mParameters.getPictureSize();
1561 if (!original.equals(optimalSize)) {
1562 mParameters.setPictureSize(optimalSize.width, optimalSize.height);
1563 }
1564 Log.v(TAG, "Video snapshot size is " + optimalSize.width + "x" +
1565 optimalSize.height);
1566
1567 // Set JPEG quality.
1568 int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1569 CameraProfile.QUALITY_HIGH);
1570 mParameters.setJpegQuality(jpegQuality);
1571
Doris Liu6432cd62013-06-13 17:20:31 -07001572 mCameraDevice.setParameters(mParameters);
Andy Huibers64abfe92014-01-14 22:25:52 -08001573 // Nexus 5 through KitKat 4.4.2 requires a second call to
1574 // .setParameters() for frame rate settings to take effect.
1575 mCameraDevice.setParameters(mParameters);
Sascha Haeberlingf8b877c2013-09-09 17:32:48 -07001576
1577 // Update UI based on the new parameters.
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001578 mUI.updateOnScreenIndicators(mParameters);
Michael Kolb8872c232013-01-29 10:33:22 -08001579 }
1580
Doris Liua1ec04a2014-01-13 17:29:40 -08001581 private void updateFocusParameters() {
1582 // Set continuous autofocus. During recording, we use "continuous-video"
1583 // auto focus mode to ensure smooth focusing. Whereas during preview (i.e.
1584 // before recording starts) we use "continuous-picture" auto focus mode
1585 // for faster but slightly jittery focusing.
1586 List<String> supportedFocus = mParameters.getSupportedFocusModes();
1587 if (mMediaRecorderRecording) {
1588 if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
1589 mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
1590 mFocusManager.overrideFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
1591 } else {
1592 mFocusManager.overrideFocusMode(null);
1593 }
1594 } else {
1595 mFocusManager.overrideFocusMode(null);
1596 if (isSupported(CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE, supportedFocus)) {
1597 mParameters.setFocusMode(mFocusManager.getFocusMode());
1598 if (mFocusAreaSupported) {
1599 mParameters.setFocusAreas(mFocusManager.getFocusAreas());
1600 }
1601 }
1602 }
1603 updateAutoFocusMoveCallback();
1604 }
1605
Michael Kolb8872c232013-01-29 10:33:22 -08001606 @Override
Angus Kong20fad242013-11-11 18:23:46 -08001607 public void resume() {
Spike Sprague51c877c2014-02-18 11:14:12 -08001608 if (isVideoCaptureIntent()) {
1609 mDontResetIntentUiOnResume = mPaused;
1610 }
1611
Angus Kongc4e66562013-11-22 23:03:21 -08001612 mPaused = false;
Angus Kong13e87c42013-11-25 10:02:47 -08001613 installIntentFilter();
Angus Kongc4e66562013-11-22 23:03:21 -08001614 mUI.enableShutter(false);
1615 mZoomValue = 0;
1616
1617 showVideoSnapshotUI(false);
1618
1619 if (!mPreviewing) {
1620 requestCamera(mCameraId);
1621 } else {
1622 // preview already started
1623 mUI.enableShutter(true);
1624 }
1625
Doris Liua1ec04a2014-01-13 17:29:40 -08001626 if (mFocusManager != null) {
1627 // If camera is not open when resume is called, focus manager will not
1628 // be initialized yet, in which case it will start listening to
1629 // preview area size change later in the initialization.
1630 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1631 }
Sascha Haeberlingde303232014-02-07 02:30:53 +01001632
Angus Kongc4e66562013-11-22 23:03:21 -08001633 if (mPreviewing) {
1634 mOnResumeTime = SystemClock.uptimeMillis();
Angus Kong13e87c42013-11-25 10:02:47 -08001635 mHandler.sendEmptyMessageDelayed(MSG_CHECK_DISPLAY_ROTATION, 100);
Angus Kongc4e66562013-11-22 23:03:21 -08001636 }
1637
Seth Raphael5e09d012013-12-18 13:45:03 -08001638 UsageStatistics.changeScreen(eventprotos.NavigationChange.Mode.VIDEO_CAPTURE,
1639 eventprotos.CameraEvent.InteractionCause.BUTTON);
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -08001640 getServices().getMemoryManager().addListener(this);
Angus Kong20fad242013-11-11 18:23:46 -08001641 }
1642
1643 @Override
1644 public void pause() {
Angus Kongc4e66562013-11-22 23:03:21 -08001645 mPaused = true;
Doris Liua1ec04a2014-01-13 17:29:40 -08001646
1647 if (mFocusManager != null) {
1648 // If camera is not open when resume is called, focus manager will not
1649 // be initialized yet, in which case it will start listening to
1650 // preview area size change later in the initialization.
1651 mAppController.removePreviewAreaSizeChangedListener(mFocusManager);
1652 mFocusManager.removeMessages();
1653 }
Angus Kongc4e66562013-11-22 23:03:21 -08001654 if (mMediaRecorderRecording) {
1655 // Camera will be released in onStopVideoRecording.
1656 onStopVideoRecording();
1657 } else {
1658 stopPreview();
1659 closeCamera();
1660 releaseMediaRecorder();
1661 }
1662
1663 closeVideoFileDescriptor();
Angus Kongc4e66562013-11-22 23:03:21 -08001664
1665 if (mReceiver != null) {
1666 mActivity.unregisterReceiver(mReceiver);
1667 mReceiver = null;
1668 }
Angus Kongc4e66562013-11-22 23:03:21 -08001669
Angus Kong13e87c42013-11-25 10:02:47 -08001670 mHandler.removeMessages(MSG_CHECK_DISPLAY_ROTATION);
1671 mHandler.removeMessages(MSG_SWITCH_CAMERA);
1672 mHandler.removeMessages(MSG_SWITCH_CAMERA_START_ANIMATION);
Angus Kongc4e66562013-11-22 23:03:21 -08001673 mPendingSwitchCameraId = -1;
1674 mSwitchingCamera = false;
1675 mPreferenceRead = false;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -08001676 getServices().getMemoryManager().removeListener(this);
Angus Kong20fad242013-11-11 18:23:46 -08001677 }
1678
1679 @Override
1680 public void destroy() {
1681
1682 }
1683
1684 @Override
Angus Kong2f0e4a32013-12-03 10:02:35 -08001685 public void onLayoutOrientationChanged(boolean isLandscape) {
Michael Kolb8872c232013-01-29 10:33:22 -08001686 setDisplayOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001687 }
1688
Erin Dahlgrena07e94c2013-12-04 18:44:08 -08001689 // TODO: integrate this into the SettingsManager listeners.
Michael Kolb8872c232013-01-29 10:33:22 -08001690 public void onSharedPreferenceChanged() {
Michael Kolb8872c232013-01-29 10:33:22 -08001691
Michael Kolb8872c232013-01-29 10:33:22 -08001692 }
1693
1694 private void switchCamera() {
Sascha Haeberlingf8b877c2013-09-09 17:32:48 -07001695 if (mPaused) {
1696 return;
1697 }
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001698 SettingsManager settingsManager = mActivity.getSettingsManager();
Michael Kolb8872c232013-01-29 10:33:22 -08001699
1700 Log.d(TAG, "Start to switch camera.");
1701 mCameraId = mPendingSwitchCameraId;
1702 mPendingSwitchCameraId = -1;
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001703 settingsManager.set(SettingsManager.SETTING_CAMERA_ID, "" + mCameraId);
Michael Kolb8872c232013-01-29 10:33:22 -08001704
Doris Liua1ec04a2014-01-13 17:29:40 -08001705 if (mFocusManager != null) {
1706 mFocusManager.removeMessages();
1707 }
Michael Kolb8872c232013-01-29 10:33:22 -08001708 closeCamera();
Angus Kong20fad242013-11-11 18:23:46 -08001709 requestCamera(mCameraId);
Michael Kolb8872c232013-01-29 10:33:22 -08001710
Sascha Haeberling6ccec202014-03-11 09:44:34 -07001711 mMirror = isCameraFrontFacing();
Doris Liua1ec04a2014-01-13 17:29:40 -08001712 if (mFocusManager != null) {
1713 mFocusManager.setMirror(mMirror);
1714 }
1715
Michael Kolb8872c232013-01-29 10:33:22 -08001716 // From onResume
Doris Liu6432cd62013-06-13 17:20:31 -07001717 mZoomValue = 0;
Doris Liu6827ce22013-03-12 19:24:28 -07001718 mUI.setOrientationIndicator(0, false);
Michael Kolb8872c232013-01-29 10:33:22 -08001719
Doris Liu6432cd62013-06-13 17:20:31 -07001720 // Start switch camera animation. Post a message because
1721 // onFrameAvailable from the old camera may already exist.
Angus Kong13e87c42013-11-25 10:02:47 -08001722 mHandler.sendEmptyMessage(MSG_SWITCH_CAMERA_START_ANIMATION);
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001723 mUI.updateOnScreenIndicators(mParameters);
Michael Kolb8872c232013-01-29 10:33:22 -08001724 }
1725
Michael Kolb8872c232013-01-29 10:33:22 -08001726 private void initializeVideoSnapshot() {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001727 if (mParameters == null) {
1728 return;
1729 }
Michael Kolb8872c232013-01-29 10:33:22 -08001730 }
1731
1732 void showVideoSnapshotUI(boolean enabled) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001733 if (mParameters == null) {
1734 return;
1735 }
Angus Kongb50b5cb2013-08-09 14:55:20 -07001736 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
Doris Liu6432cd62013-06-13 17:20:31 -07001737 if (enabled) {
Sascha Haeberling37f36112013-08-06 14:31:52 -07001738 mUI.animateFlash();
Michael Kolb8872c232013-01-29 10:33:22 -08001739 } else {
Doris Liu6827ce22013-03-12 19:24:28 -07001740 mUI.showPreviewBorder(enabled);
Michael Kolb8872c232013-01-29 10:33:22 -08001741 }
Doris Liu6827ce22013-03-12 19:24:28 -07001742 mUI.enableShutter(!enabled);
Michael Kolb8872c232013-01-29 10:33:22 -08001743 }
1744 }
1745
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001746 /**
1747 * Used to update the flash mode. Video mode can turn on the flash as torch
1748 * mode, which we would like to turn on and off when we switching in and
1749 * out to the preview.
1750 *
1751 * @param enable Whether torch mode can be enabled.
1752 */
1753 private void enableTorchMode(boolean enable) {
1754 if (mParameters.getFlashMode() == null) {
1755 return;
1756 }
1757
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001758 SettingsManager settingsManager = mActivity.getSettingsManager();
1759
ztenghui7b265a62013-09-09 14:58:44 -07001760 String flashMode;
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001761 if (enable) {
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001762 flashMode = settingsManager.get(SettingsManager.SETTING_VIDEOCAMERA_FLASH_MODE);
ztenghui7b265a62013-09-09 14:58:44 -07001763 } else {
1764 flashMode = Parameters.FLASH_MODE_OFF;
1765 }
1766 List<String> supportedFlash = mParameters.getSupportedFlashModes();
1767 if (isSupported(flashMode, supportedFlash)) {
1768 mParameters.setFlashMode(flashMode);
1769 } else {
1770 flashMode = mParameters.getFlashMode();
1771 if (flashMode == null) {
1772 flashMode = mActivity.getString(
1773 R.string.pref_camera_flashmode_no_flash);
Angus Kongfaaee012013-12-07 00:38:46 -08001774 mParameters.setFlashMode(flashMode);
Michael Kolb8872c232013-01-29 10:33:22 -08001775 }
Michael Kolb8872c232013-01-29 10:33:22 -08001776 }
ztenghui7b265a62013-09-09 14:58:44 -07001777 mCameraDevice.setParameters(mParameters);
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001778 mUI.updateOnScreenIndicators(mParameters);
ztenghui7b265a62013-09-09 14:58:44 -07001779 }
1780
Michael Kolb8872c232013-01-29 10:33:22 -08001781 @Override
Sascha Haeberling8c1a9222014-02-25 09:38:06 -08001782 public void onPreviewVisibilityChanged(int visibility) {
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001783 if (mPreviewing) {
Sascha Haeberling8c1a9222014-02-25 09:38:06 -08001784 enableTorchMode(visibility == ModuleController.VISIBILITY_VISIBLE);
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001785 }
Erin Dahlgren3044d8c2013-10-10 18:23:45 -07001786 }
1787
Angus Kong9ef99252013-07-18 18:04:19 -07001788 private final class JpegPictureCallback implements CameraPictureCallback {
Michael Kolb8872c232013-01-29 10:33:22 -08001789 Location mLocation;
1790
1791 public JpegPictureCallback(Location loc) {
1792 mLocation = loc;
1793 }
1794
1795 @Override
Angus Kong9ef99252013-07-18 18:04:19 -07001796 public void onPictureTaken(byte [] jpegData, CameraProxy camera) {
Michael Kolb8872c232013-01-29 10:33:22 -08001797 Log.v(TAG, "onPictureTaken");
1798 mSnapshotInProgress = false;
1799 showVideoSnapshotUI(false);
1800 storeImage(jpegData, mLocation);
1801 }
1802 }
1803
1804 private void storeImage(final byte[] data, Location loc) {
1805 long dateTaken = System.currentTimeMillis();
Angus Kongb50b5cb2013-08-09 14:55:20 -07001806 String title = CameraUtil.createJpegName(dateTaken);
Angus Kong0d00a892013-03-26 11:40:40 -07001807 ExifInterface exif = Exif.getExif(data);
1808 int orientation = Exif.getOrientation(exif);
Doris Liu6df2d962013-08-20 16:31:29 -07001809
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08001810 getServices().getMediaSaver().addImage(
Doris Liu6df2d962013-08-20 16:31:29 -07001811 data, title, dateTaken, loc, orientation,
Angus Kong83a99ae2013-04-17 15:37:07 -07001812 exif, mOnPhotoSavedListener, mContentResolver);
Michael Kolb8872c232013-01-29 10:33:22 -08001813 }
1814
Michael Kolb8872c232013-01-29 10:33:22 -08001815 private String convertOutputFormatToMimeType(int outputFileFormat) {
1816 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1817 return "video/mp4";
1818 }
1819 return "video/3gpp";
1820 }
1821
1822 private String convertOutputFormatToFileExt(int outputFileFormat) {
1823 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1824 return ".mp4";
1825 }
1826 return ".3gp";
1827 }
1828
1829 private void closeVideoFileDescriptor() {
1830 if (mVideoFileDescriptor != null) {
1831 try {
1832 mVideoFileDescriptor.close();
1833 } catch (IOException e) {
1834 Log.e(TAG, "Fail to close fd", e);
1835 }
1836 mVideoFileDescriptor = null;
1837 }
1838 }
1839
Michael Kolb8872c232013-01-29 10:33:22 -08001840 @Override
Angus Kong395ee2d2013-07-15 12:42:41 -07001841 public void onPreviewUIReady() {
1842 startPreview();
1843 }
1844
1845 @Override
1846 public void onPreviewUIDestroyed() {
1847 stopPreview();
1848 }
Angus Kong20fad242013-11-11 18:23:46 -08001849
Doris Liu1dfe7822013-12-12 00:02:08 -08001850 @Override
1851 public void startPreCaptureAnimation() {
1852 mAppController.startPreCaptureAnimation();
1853 }
1854
Angus Kong20fad242013-11-11 18:23:46 -08001855 private void requestCamera(int id) {
1856 mActivity.getCameraProvider().requestCamera(id);
1857 }
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -08001858
1859 @Override
1860 public void onMemoryStateChanged(int state) {
1861 setShutterEnabled(state == MemoryManager.STATE_OK);
1862 }
1863
1864 @Override
1865 public void onLowMemory() {
1866 // Not much we can do in the video module.
1867 }
1868
1869 private void setShutterEnabled(boolean enabled) {
1870 mShutterEnabled = enabled;
1871 mUI.enableShutter(enabled);
1872 }
Doris Liua1ec04a2014-01-13 17:29:40 -08001873
1874 /***********************FocusOverlayManager Listener****************************/
1875 @Override
1876 public void autoFocus() {
1877 mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
1878 }
1879
1880 @Override
1881 public void cancelAutoFocus() {
1882 mCameraDevice.cancelAutoFocus();
1883 setFocusParameters();
1884 }
1885
1886 @Override
1887 public boolean capture() {
1888 return false;
1889 }
1890
1891 @Override
1892 public void startFaceDetection() {
1893
1894 }
1895
1896 @Override
1897 public void stopFaceDetection() {
1898
1899 }
1900
1901 @Override
1902 public void setFocusParameters() {
1903 updateFocusParameters();
1904 mCameraDevice.setParameters(mParameters);
1905 }
1906
Michael Kolb8872c232013-01-29 10:33:22 -08001907}