blob: 0bdb9a31635a1ee2889c882184f46322c7b1d8f4 [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;
Michael Kolb8872c232013-01-29 10:33:22 -080031import android.location.Location;
Marco Nelissen20694b22013-10-29 15:27:24 -070032import android.media.AudioManager;
Michael Kolb8872c232013-01-29 10:33:22 -080033import android.media.CamcorderProfile;
34import android.media.CameraProfile;
35import android.media.MediaRecorder;
36import android.net.Uri;
37import android.os.Build;
38import android.os.Bundle;
39import android.os.Handler;
40import android.os.Message;
41import android.os.ParcelFileDescriptor;
42import android.os.SystemClock;
43import android.provider.MediaStore;
Ruben Brunk16007962013-04-19 15:27:57 -070044import android.provider.MediaStore.MediaColumns;
Michael Kolb8872c232013-01-29 10:33:22 -080045import android.provider.MediaStore.Video;
Michael Kolb8872c232013-01-29 10:33:22 -080046import android.view.KeyEvent;
Michael Kolb8872c232013-01-29 10:33:22 -080047import android.view.OrientationEventListener;
Michael Kolb8872c232013-01-29 10:33:22 -080048import android.view.View;
Michael Kolb8872c232013-01-29 10:33:22 -080049import android.widget.Toast;
50
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080051import com.android.camera.app.AppController;
Spike Sprague39f8a762014-01-13 14:22:18 -080052import com.android.camera.app.CameraAppUI;
Kevin Gabayanffbc43c2013-12-09 11:41:50 -080053import com.android.camera.app.LocationManager;
Angus Kongfd4fc0e2013-11-07 15:38:09 -080054import com.android.camera.app.MediaSaver;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080055import com.android.camera.app.MemoryManager;
56import com.android.camera.app.MemoryManager.MemoryListener;
Angus Kong2bca2102014-03-11 16:27:30 -070057import com.android.camera.debug.Log;
ztenghuia16e7b52013-08-23 11:47:56 -070058import com.android.camera.exif.ExifInterface;
Erin Dahlgrenb1641f52014-01-14 15:58:52 -080059import com.android.camera.hardware.HardwareSpec;
60import com.android.camera.hardware.HardwareSpecImpl;
Angus Kong20fad242013-11-11 18:23:46 -080061import com.android.camera.module.ModuleController;
Erin Dahlgren6190c362014-06-13 14:12:08 -070062import com.android.camera.settings.Keys;
Erin Dahlgrenbd3da262013-12-02 11:48:13 -080063import com.android.camera.settings.SettingsManager;
Sascha Haeberlingde303232014-02-07 02:30:53 +010064import com.android.camera.settings.SettingsUtil;
Andy Huibersb7c7d9a2014-06-18 22:26:14 -070065import com.android.camera.ui.TouchCoordinate;
Sascha Haeberling638e6f02013-09-18 14:28:51 -070066import com.android.camera.util.ApiHelper;
Angus Kongb50b5cb2013-08-09 14:55:20 -070067import com.android.camera.util.CameraUtil;
Sascha Haeberling8e963a52013-08-06 11:43:02 -070068import com.android.camera.util.UsageStatistics;
69import com.android.camera2.R;
Sol Boucher5a344962014-06-17 14:05:08 -070070import com.android.ex.camera2.portability.CameraAgent;
71import com.android.ex.camera2.portability.CameraAgent.CameraPictureCallback;
72import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
Sascha Haeberlingf15d63e2014-07-21 11:31:13 -070073import com.android.ex.camera2.portability.CameraCapabilities;
Sol Boucher43e18132014-06-19 15:03:18 -070074import com.android.ex.camera2.portability.CameraDeviceInfo.Characteristics;
Sol Boucher5a344962014-06-17 14:05:08 -070075import com.android.ex.camera2.portability.CameraSettings;
76import com.android.ex.camera2.portability.Size;
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;
Angus Kong831347d2014-06-16 16:07:28 -070082import java.util.ArrayList;
Angus Kongb50b5cb2013-08-09 14:55:20 -070083import java.util.Date;
84import java.util.Iterator;
85import java.util.List;
Angus Kong6607dae2014-06-10 16:07:45 -070086import java.util.Set;
Angus Kongb50b5cb2013-08-09 14:55:20 -070087
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080088public class VideoModule extends CameraModule
89 implements ModuleController,
90 VideoController,
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080091 MemoryListener,
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080092 MediaRecorder.OnErrorListener,
Doris Liua1ec04a2014-01-13 17:29:40 -080093 MediaRecorder.OnInfoListener, FocusOverlayManager.Listener {
Michael Kolb8872c232013-01-29 10:33:22 -080094
Erin Dahlgren6190c362014-06-13 14:12:08 -070095 private static final String VIDEO_MODULE_STRING_ID = "VideoModule";
96
97 private static final Log.Tag TAG = new Log.Tag(VIDEO_MODULE_STRING_ID);
Michael Kolb8872c232013-01-29 10:33:22 -080098
Angus Kong13e87c42013-11-25 10:02:47 -080099 // Messages defined for the UI thread handler.
100 private static final int MSG_CHECK_DISPLAY_ROTATION = 4;
101 private static final int MSG_UPDATE_RECORD_TIME = 5;
102 private static final int MSG_ENABLE_SHUTTER_BUTTON = 6;
Angus Kong13e87c42013-11-25 10:02:47 -0800103 private static final int MSG_SWITCH_CAMERA = 8;
104 private static final int MSG_SWITCH_CAMERA_START_ANIMATION = 9;
Michael Kolb8872c232013-01-29 10:33:22 -0800105
106 private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
107
108 /**
109 * An unpublished intent flag requesting to start recording straight away
110 * and return as soon as recording is stopped.
111 * TODO: consider publishing by moving into MediaStore.
112 */
113 private static final String EXTRA_QUICK_CAPTURE =
114 "android.intent.extra.quickCapture";
115
Michael Kolb8872c232013-01-29 10:33:22 -0800116 // module fields
117 private CameraActivity mActivity;
Michael Kolb8872c232013-01-29 10:33:22 -0800118 private boolean mPaused;
Spike Sprague51c877c2014-02-18 11:14:12 -0800119
120 // if, during and intent capture, the activity is paused (e.g. when app switching or reviewing a
121 // shot video), we don't want the bottom bar intent ui to reset to the capture button
122 private boolean mDontResetIntentUiOnResume;
123
Michael Kolb8872c232013-01-29 10:33:22 -0800124 private int mCameraId;
Angus Kong6607dae2014-06-10 16:07:45 -0700125 private CameraSettings mCameraSettings;
Angus Kong88289042014-04-22 16:39:42 -0700126 private CameraCapabilities mCameraCapabilities;
Michael Kolb8872c232013-01-29 10:33:22 -0800127
Doris Liu6432cd62013-06-13 17:20:31 -0700128 private boolean mIsInReviewMode;
Michael Kolb8872c232013-01-29 10:33:22 -0800129 private boolean mSnapshotInProgress = false;
130
Michael Kolb8872c232013-01-29 10:33:22 -0800131 private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
132
Angus Kong395ee2d2013-07-15 12:42:41 -0700133 // Preference must be read before starting preview. We check this before starting
134 // preview.
135 private boolean mPreferenceRead;
Michael Kolb8872c232013-01-29 10:33:22 -0800136
Michael Kolb8872c232013-01-29 10:33:22 -0800137 private boolean mIsVideoCaptureIntent;
138 private boolean mQuickCapture;
139
140 private MediaRecorder mMediaRecorder;
Michael Kolb8872c232013-01-29 10:33:22 -0800141
142 private boolean mSwitchingCamera;
143 private boolean mMediaRecorderRecording = false;
144 private long mRecordingStartTime;
145 private boolean mRecordingTimeCountsDown = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800146 private long mOnResumeTime;
147 // The video file that the hardware camera is about to record into
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800148 // (or is recording into.
Michael Kolb8872c232013-01-29 10:33:22 -0800149 private String mVideoFilename;
150 private ParcelFileDescriptor mVideoFileDescriptor;
151
152 // The video file that has already been recorded, and that is being
153 // examined by the user.
154 private String mCurrentVideoFilename;
155 private Uri mCurrentVideoUri;
ztenghui70bd0242013-10-14 11:15:44 -0700156 private boolean mCurrentVideoUriFromMediaSaved;
Michael Kolb8872c232013-01-29 10:33:22 -0800157 private ContentValues mCurrentVideoValues;
158
159 private CamcorderProfile mProfile;
160
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800161 // The video duration limit. 0 means no limit.
Michael Kolb8872c232013-01-29 10:33:22 -0800162 private int mMaxVideoDurationInMs;
163
Michael Kolb8872c232013-01-29 10:33:22 -0800164 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
Sol Boucher2192fba2014-08-19 17:24:07 -0700185 private float mZoomValue; // The current zoom ratio.
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;
Doris Liua1ec04a2014-01-13 17:29:40 -0800211 private boolean mFocusAreaSupported;
212 private boolean mMeteringAreaSupported;
213
Sol Boucher5a344962014-06-17 14:05:08 -0700214 private final CameraAgent.CameraAFCallback mAutoFocusCallback =
215 new CameraAgent.CameraAFCallback() {
Doris Liua1ec04a2014-01-13 17:29:40 -0800216 @Override
217 public void onAutoFocus(boolean focused, CameraProxy camera) {
218 if (mPaused) {
219 return;
220 }
221 mFocusManager.onAutoFocus(focused, false);
222 }
223 };
224
225 private final Object mAutoFocusMoveCallback =
226 ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
Sol Boucher5a344962014-06-17 14:05:08 -0700227 ? new CameraAgent.CameraAFMoveCallback() {
Doris Liua1ec04a2014-01-13 17:29:40 -0800228 @Override
229 public void onAutoFocusMoving(boolean moving, CameraProxy camera) {
Andy Huibersc0fe0b62014-08-25 22:25:49 -0700230 // mFocusManager.onAutoFocusMoving(moving) not called because UI
231 // not compatible with vertical video hint UI.
Doris Liua1ec04a2014-01-13 17:29:40 -0800232 }
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:
Erin Dahlgren667630d2014-04-01 14:03:25 -0700245 mAppController.setShutterEnabled(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
292 private class MyBroadcastReceiver extends BroadcastReceiver {
293 @Override
294 public void onReceive(Context context, Intent intent) {
295 String action = intent.getAction();
296 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
297 stopVideoRecording();
298 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
299 Toast.makeText(mActivity,
300 mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
301 }
302 }
303 }
304
Spike Sprague41f68002014-01-24 16:59:25 -0800305 private int mShutterIconId;
306
307
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800308 /**
309 * Construct a new video module.
310 */
Angus Kongc4e66562013-11-22 23:03:21 -0800311 public VideoModule(AppController app) {
312 super(app);
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800313 }
314
Spike Sprague159e6e92014-05-27 18:26:30 -0700315 @Override
316 public String getPeekAccessibilityString() {
317 return mAppController.getAndroidContext()
318 .getResources().getString(R.string.video_accessibility_peek);
319 }
320
Michael Kolb8872c232013-01-29 10:33:22 -0800321 private String createName(long dateTaken) {
322 Date date = new Date(dateTaken);
323 SimpleDateFormat dateFormat = new SimpleDateFormat(
324 mActivity.getString(R.string.video_file_name_format));
325
326 return dateFormat.format(date);
327 }
328
Michael Kolb8872c232013-01-29 10:33:22 -0800329 @Override
Erin Dahlgren6190c362014-06-13 14:12:08 -0700330 public String getModuleStringIdentifier() {
331 return VIDEO_MODULE_STRING_ID;
332 }
333
334 @Override
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100335 public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
336 mActivity = activity;
337 // TODO: Need to look at the controller interface to see if we can get
338 // rid of passing in the activity directly.
339 mAppController = mActivity;
Spike Sprague3f673792014-04-14 16:16:27 -0700340
Spike Sprague48a38062014-04-29 18:04:02 -0700341 mActivity.updateStorageSpaceAndHint(null);
Spike Sprague3f673792014-04-14 16:16:27 -0700342
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100343 mUI = new VideoUI(mActivity, this, mActivity.getModuleLayoutRoot());
344 mActivity.setPreviewStatusListener(mUI);
Michael Kolb8872c232013-01-29 10:33:22 -0800345
Erin Dahlgrenbd3da262013-12-02 11:48:13 -0800346 SettingsManager settingsManager = mActivity.getSettingsManager();
Erin Dahlgren6190c362014-06-13 14:12:08 -0700347 mCameraId = settingsManager.getInteger(mAppController.getModuleScope(),
348 Keys.KEY_CAMERA_ID);
Michael Kolb8872c232013-01-29 10:33:22 -0800349
Michael Kolb8872c232013-01-29 10:33:22 -0800350 /*
351 * To reduce startup time, we start the preview in another thread.
352 * We make sure the preview is started at the end of onCreate.
353 */
Angus Kong20fad242013-11-11 18:23:46 -0800354 requestCamera(mCameraId);
Michael Kolb8872c232013-01-29 10:33:22 -0800355
356 mContentResolver = mActivity.getContentResolver();
357
Michael Kolb8872c232013-01-29 10:33:22 -0800358 // Surface texture is from camera screen nail and startPreview needs it.
359 // This must be done before startPreview.
360 mIsVideoCaptureIntent = isVideoCaptureIntent();
Michael Kolb8872c232013-01-29 10:33:22 -0800361
Michael Kolb8872c232013-01-29 10:33:22 -0800362 mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
Erin Dahlgren21c21a62013-11-19 16:37:38 -0800363 mLocationManager = mActivity.getLocationManager();
Michael Kolb8872c232013-01-29 10:33:22 -0800364
Doris Liu6827ce22013-03-12 19:24:28 -0700365 mUI.setOrientationIndicator(0, false);
Michael Kolb8872c232013-01-29 10:33:22 -0800366 setDisplayOrientation();
367
Michael Kolb8872c232013-01-29 10:33:22 -0800368 mPendingSwitchCameraId = -1;
Spike Sprague41f68002014-01-24 16:59:25 -0800369
370 mShutterIconId = CameraUtil.getCameraShutterIconId(
371 mAppController.getCurrentModuleIndex(), mAppController.getAndroidContext());
Doris Liu6827ce22013-03-12 19:24:28 -0700372 }
373
Erin Dahlgren4efa8b52013-12-17 18:31:35 -0800374 @Override
375 public boolean isUsingBottomBar() {
376 return true;
377 }
378
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800379 private void initializeControlByIntent() {
380 if (isVideoCaptureIntent()) {
Spike Sprague51c877c2014-02-18 11:14:12 -0800381 if (!mDontResetIntentUiOnResume) {
382 mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
383 }
384 // reset the flag
385 mDontResetIntentUiOnResume = false;
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800386 }
387 }
388
Doris Liu6827ce22013-03-12 19:24:28 -0700389 @Override
390 public void onSingleTapUp(View view, int x, int y) {
Doris Liu057b21a2014-04-25 17:35:27 -0700391 if (mPaused || mCameraDevice == null) {
392 return;
393 }
394 if (mMediaRecorderRecording) {
395 if (!mSnapshotInProgress) {
396 takeASnapshot();
397 }
Doris Liua1ec04a2014-01-13 17:29:40 -0800398 return;
399 }
400 // Check if metering area or focus area is supported.
401 if (!mFocusAreaSupported && !mMeteringAreaSupported) {
402 return;
403 }
404 // Tap to focus.
405 mFocusManager.onSingleTapUp(x, y);
Doris Liu38605742013-08-13 15:01:52 -0700406 }
407
408 private void takeASnapshot() {
Sascha Haeberlinga514b142013-08-13 16:10:01 -0700409 // Only take snapshots if video snapshot is supported by device
Angus Kong88289042014-04-22 16:39:42 -0700410 if(!mCameraCapabilities.supports(CameraCapabilities.Feature.VIDEO_SNAPSHOT)) {
Doris Liu46836732014-04-29 18:15:55 -0700411 Log.w(TAG, "Cannot take a video snapshot - not supported by hardware");
412 return;
413 }
414 if (!mIsVideoCaptureIntent) {
Erin Dahlgren667630d2014-04-01 14:03:25 -0700415 if (!mMediaRecorderRecording || mPaused || mSnapshotInProgress
Doris Liu057b21a2014-04-25 17:35:27 -0700416 || !mAppController.isShutterEnabled()) {
Doris Liu38605742013-08-13 15:01:52 -0700417 return;
418 }
419
Doris Liu38605742013-08-13 15:01:52 -0700420 Location loc = mLocationManager.getCurrentLocation();
Angus Kong6607dae2014-06-10 16:07:45 -0700421 CameraUtil.setGpsParameters(mCameraSettings, loc);
422 mCameraDevice.applySettings(mCameraSettings);
Doris Liu38605742013-08-13 15:01:52 -0700423
Doris Liu057b21a2014-04-25 17:35:27 -0700424 Log.i(TAG, "Video snapshot start");
Doris Liu38605742013-08-13 15:01:52 -0700425 mCameraDevice.takePicture(mHandler,
426 null, null, null, new JpegPictureCallback(loc));
427 showVideoSnapshotUI(true);
428 mSnapshotInProgress = true;
Doris Liu6827ce22013-03-12 19:24:28 -0700429 }
Michael Kolb8872c232013-01-29 10:33:22 -0800430 }
431
Doris Liua1ec04a2014-01-13 17:29:40 -0800432 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
433 private void updateAutoFocusMoveCallback() {
Sascha Haeberling8793eff2014-01-15 16:33:59 -0800434 if (mPaused) {
435 return;
436 }
437
Angus Kong6607dae2014-06-10 16:07:45 -0700438 if (mCameraSettings.getCurrentFocusMode() == CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
Doris Liua1ec04a2014-01-13 17:29:40 -0800439 mCameraDevice.setAutoFocusMoveCallback(mHandler,
Sol Boucher5a344962014-06-17 14:05:08 -0700440 (CameraAgent.CameraAFMoveCallback) mAutoFocusMoveCallback);
Doris Liua1ec04a2014-01-13 17:29:40 -0800441 } else {
442 mCameraDevice.setAutoFocusMoveCallback(null, null);
443 }
444 }
445
446 /**
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700447 * @return Whether the currently active camera is front-facing.
448 */
449 private boolean isCameraFrontFacing() {
Sol Boucher43e18132014-06-19 15:03:18 -0700450 return mAppController.getCameraProvider().getCharacteristics(mCameraId)
451 .isFacingFront();
452 }
453
454 /**
455 * @return Whether the currently active camera is back-facing.
456 */
457 private boolean isCameraBackFacing() {
458 return mAppController.getCameraProvider().getCharacteristics(mCameraId)
459 .isFacingBack();
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700460 }
461
462 /**
Doris Liua1ec04a2014-01-13 17:29:40 -0800463 * The focus manager gets initialized after camera is available.
464 */
465 private void initializeFocusManager() {
466 // Create FocusManager object. startPreview needs it.
467 // if mFocusManager not null, reuse it
468 // otherwise create a new instance
469 if (mFocusManager != null) {
470 mFocusManager.removeMessages();
471 } else {
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700472 mMirror = isCameraFrontFacing();
Angus Kong831347d2014-06-16 16:07:28 -0700473 String[] defaultFocusModesStrings = mActivity.getResources().getStringArray(
Doris Liua1ec04a2014-01-13 17:29:40 -0800474 R.array.pref_camera_focusmode_default_array);
Angus Kong831347d2014-06-16 16:07:28 -0700475 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
Sascha Haeberlingee36c5f2014-07-29 17:05:43 -0700476 ArrayList<CameraCapabilities.FocusMode> defaultFocusModes =
477 new ArrayList<CameraCapabilities.FocusMode>();
Angus Kong831347d2014-06-16 16:07:28 -0700478 for (String modeString : defaultFocusModesStrings) {
479 CameraCapabilities.FocusMode mode = stringifier.focusModeFromString(modeString);
480 if (mode != null) {
481 defaultFocusModes.add(mode);
482 }
483 }
Erin Dahlgren6190c362014-06-13 14:12:08 -0700484 mFocusManager = new FocusOverlayManager(mAppController,
Angus Kong831347d2014-06-16 16:07:28 -0700485 defaultFocusModes, mCameraCapabilities, this, mMirror,
Doris Liua1ec04a2014-01-13 17:29:40 -0800486 mActivity.getMainLooper(), mUI.getFocusUI());
487 }
488 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
489 }
490
Michael Kolb8872c232013-01-29 10:33:22 -0800491 @Override
492 public void onOrientationChanged(int orientation) {
493 // We keep the last known orientation. So if the user first orient
494 // the camera then point the camera to floor or sky, we still have
495 // the correct orientation.
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100496 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
497 return;
498 }
Angus Kongb50b5cb2013-08-09 14:55:20 -0700499 int newOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800500
501 if (mOrientation != newOrientation) {
502 mOrientation = newOrientation;
Michael Kolb8872c232013-01-29 10:33:22 -0800503 }
Doris Liu8dc878f2014-05-05 14:10:34 -0700504 mUI.onOrientationChanged(orientation);
Michael Kolb8872c232013-01-29 10:33:22 -0800505
Michael Kolb8872c232013-01-29 10:33:22 -0800506 }
507
Erin Dahlgren0a6a8d82014-01-09 22:17:38 -0800508 private final ButtonManager.ButtonCallback mFlashCallback =
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -0800509 new ButtonManager.ButtonCallback() {
510 @Override
511 public void onStateChanged(int state) {
512 // Update flash parameters.
513 enableTorchMode(true);
514 }
515 };
516
Erin Dahlgren0a6a8d82014-01-09 22:17:38 -0800517 private final ButtonManager.ButtonCallback mCameraCallback =
Erin Dahlgren18e2ef62013-12-05 14:53:38 -0800518 new ButtonManager.ButtonCallback() {
519 @Override
520 public void onStateChanged(int state) {
Angus Kong97e282a2014-03-04 18:44:49 -0800521 if (mPaused || mAppController.getCameraProvider().waitingForCamera()) {
Erin Dahlgren18e2ef62013-12-05 14:53:38 -0800522 return;
523 }
524 mPendingSwitchCameraId = state;
525 Log.d(TAG, "Start to copy texture.");
526
527 // Disable all camera controls.
528 mSwitchingCamera = true;
529 switchCamera();
530 }
531 };
532
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800533 private final View.OnClickListener mCancelCallback = new View.OnClickListener() {
534 @Override
535 public void onClick(View v) {
536 onReviewCancelClicked(v);
537 }
538 };
539
540 private final View.OnClickListener mDoneCallback = new View.OnClickListener() {
541 @Override
542 public void onClick(View v) {
543 onReviewDoneClicked(v);
544 }
545 };
546 private final View.OnClickListener mReviewCallback = new View.OnClickListener() {
547 @Override
548 public void onClick(View v) {
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800549 onReviewPlayClicked(v);
550 }
551 };
552
Angus Kong20fad242013-11-11 18:23:46 -0800553 @Override
Erin Dahlgren1ca516f2014-03-28 12:44:04 -0700554 public void hardResetSettings(SettingsManager settingsManager) {
555 // VideoModule does not need to hard reset any settings.
556 }
557
558 @Override
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800559 public HardwareSpec getHardwareSpec() {
Angus Kong831347d2014-06-16 16:07:28 -0700560 return (mCameraSettings != null ?
561 new HardwareSpecImpl(getCameraProvider(), mCameraCapabilities) : null);
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800562 }
563
564 @Override
565 public CameraAppUI.BottomBarUISpec getBottomBarSpec() {
566 CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
567
568 bottomBarSpec.enableCamera = true;
569 bottomBarSpec.cameraCallback = mCameraCallback;
570 bottomBarSpec.enableTorchFlash = true;
571 bottomBarSpec.flashCallback = mFlashCallback;
572 bottomBarSpec.hideHdr = true;
Spike Spragueab3adde2014-03-10 12:19:08 -0700573 bottomBarSpec.enableGridLines = true;
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800574
575 if (isVideoCaptureIntent()) {
576 bottomBarSpec.showCancel = true;
577 bottomBarSpec.cancelCallback = mCancelCallback;
578 bottomBarSpec.showDone = true;
579 bottomBarSpec.doneCallback = mDoneCallback;
580 bottomBarSpec.showReview = true;
581 bottomBarSpec.reviewCallback = mReviewCallback;
582 }
583
584 return bottomBarSpec;
Erin Dahlgren0a6a8d82014-01-09 22:17:38 -0800585 }
586
587 @Override
Angus Kong20fad242013-11-11 18:23:46 -0800588 public void onCameraAvailable(CameraProxy cameraProxy) {
589 mCameraDevice = cameraProxy;
Angus Kong88289042014-04-22 16:39:42 -0700590 mCameraCapabilities = mCameraDevice.getCapabilities();
Angus Kong831347d2014-06-16 16:07:28 -0700591 mCameraSettings = mCameraDevice.getSettings();
592 mFocusAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA);
593 mMeteringAreaSupported =
594 mCameraCapabilities.supports(CameraCapabilities.Feature.METERING_AREA);
Angus Kong20fad242013-11-11 18:23:46 -0800595 readVideoPreferences();
596 resizeForPreviewAspectRatio();
Doris Liua1ec04a2014-01-13 17:29:40 -0800597 initializeFocusManager();
Doris Liu5dd85a12014-04-15 11:45:55 -0700598 // TODO: Having focus overlay manager caching the parameters is prone to error,
599 // we should consider passing the parameters to focus overlay to ensure the
600 // parameters are up to date.
Angus Kong831347d2014-06-16 16:07:28 -0700601 mFocusManager.updateCapabilities(mCameraCapabilities);
Doris Liu5a367542014-01-17 17:21:42 -0800602
Angus Kong20fad242013-11-11 18:23:46 -0800603 startPreview();
604 initializeVideoSnapshot();
Angus Kong6607dae2014-06-10 16:07:45 -0700605 mUI.initializeZoom(mCameraSettings, mCameraCapabilities);
Erin Dahlgrenb1641f52014-01-14 15:58:52 -0800606 initializeControlByIntent();
Angus Kong20fad242013-11-11 18:23:46 -0800607 }
608
Michael Kolb8872c232013-01-29 10:33:22 -0800609 private void startPlayVideoActivity() {
610 Intent intent = new Intent(Intent.ACTION_VIEW);
611 intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
Senpo Hu9f2b20a2014-08-29 16:27:03 -0700612 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Michael Kolb8872c232013-01-29 10:33:22 -0800613 try {
Senpo Hu9f2b20a2014-08-29 16:27:03 -0700614 mActivity.startActivity(intent);
Michael Kolb8872c232013-01-29 10:33:22 -0800615 } catch (ActivityNotFoundException ex) {
616 Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
617 }
618 }
619
ztenghui7b265a62013-09-09 14:58:44 -0700620 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800621 @OnClickAttr
622 public void onReviewPlayClicked(View v) {
623 startPlayVideoActivity();
624 }
625
ztenghui7b265a62013-09-09 14:58:44 -0700626 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800627 @OnClickAttr
628 public void onReviewDoneClicked(View v) {
Doris Liu69ef5ea2013-05-07 13:48:10 -0700629 mIsInReviewMode = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800630 doReturnToCaller(true);
631 }
632
ztenghui7b265a62013-09-09 14:58:44 -0700633 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800634 @OnClickAttr
635 public void onReviewCancelClicked(View v) {
ztenghuiaf3a1972013-09-17 16:51:15 -0700636 // TODO: It should be better to not even insert the URI at all before we
637 // confirm done in review, which means we need to handle temporary video
638 // files in a quite different way than we currently had.
ztenghui70bd0242013-10-14 11:15:44 -0700639 // Make sure we don't delete the Uri sent from the video capture intent.
640 if (mCurrentVideoUriFromMediaSaved) {
ztenghuidfe5b152013-10-08 17:07:15 -0700641 mContentResolver.delete(mCurrentVideoUri, null, null);
642 }
ztenghui638bf9a2013-10-09 10:52:33 -0700643 mIsInReviewMode = false;
Michael Kolb8872c232013-01-29 10:33:22 -0800644 doReturnToCaller(false);
645 }
646
Doris Liu69ef5ea2013-05-07 13:48:10 -0700647 @Override
648 public boolean isInReviewMode() {
649 return mIsInReviewMode;
650 }
651
Michael Kolb8872c232013-01-29 10:33:22 -0800652 private void onStopVideoRecording() {
Sascha Haeberling8793eff2014-01-15 16:33:59 -0800653 mAppController.getCameraAppUI().setSwipeEnabled(true);
Michael Kolb8872c232013-01-29 10:33:22 -0800654 boolean recordFail = stopVideoRecording();
655 if (mIsVideoCaptureIntent) {
Doris Liu3973deb2013-08-21 13:42:22 -0700656 if (mQuickCapture) {
657 doReturnToCaller(!recordFail);
658 } else if (!recordFail) {
659 showCaptureResult();
Michael Kolb8872c232013-01-29 10:33:22 -0800660 }
661 } else if (!recordFail){
662 // Start capture animation.
663 if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
664 // The capture animation is disabled on ICS because we use SurfaceView
665 // for preview during recording. When the recording is done, we switch
666 // back to use SurfaceTexture for preview and we need to stop then start
667 // the preview. This will cause the preview flicker since the preview
668 // will not be continuous for a short period of time.
Sascha Haeberling4f91ab52013-05-21 11:26:13 -0700669
Sascha Haeberling37f36112013-08-06 14:31:52 -0700670 mUI.animateFlash();
Michael Kolb8872c232013-01-29 10:33:22 -0800671 }
672 }
673 }
674
Doris Liu2a7f44c2013-08-12 15:18:53 -0700675 public void onVideoSaved() {
676 if (mIsVideoCaptureIntent) {
677 showCaptureResult();
678 }
679 }
680
Michael Kolb8872c232013-01-29 10:33:22 -0800681 public void onProtectiveCurtainClick(View v) {
682 // Consume clicks
683 }
684
685 @Override
686 public void onShutterButtonClick() {
Sascha Haeberling8793eff2014-01-15 16:33:59 -0800687 if (mSwitchingCamera) {
688 return;
689 }
Michael Kolb8872c232013-01-29 10:33:22 -0800690 boolean stop = mMediaRecorderRecording;
691
692 if (stop) {
693 onStopVideoRecording();
694 } else {
695 startVideoRecording();
696 }
Erin Dahlgren667630d2014-04-01 14:03:25 -0700697 mAppController.setShutterEnabled(false);
Angus Kong831347d2014-06-16 16:07:28 -0700698 mFocusManager.onShutterUp(mCameraSettings.getCurrentFocusMode());
Michael Kolb8872c232013-01-29 10:33:22 -0800699
700 // Keep the shutter button disabled when in video capture intent
701 // mode and recording is stopped. It'll be re-enabled when
702 // re-take button is clicked.
703 if (!(mIsVideoCaptureIntent && stop)) {
Angus Kong13e87c42013-11-25 10:02:47 -0800704 mHandler.sendEmptyMessageDelayed(MSG_ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
Michael Kolb8872c232013-01-29 10:33:22 -0800705 }
706 }
707
708 @Override
Andy Huibersb7c7d9a2014-06-18 22:26:14 -0700709 public void onShutterCoordinate(TouchCoordinate coord) {
710 // Do nothing.
711 }
712
713 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800714 public void onShutterButtonFocus(boolean pressed) {
Doris Liuf55f3c42013-11-20 00:24:46 -0800715 // TODO: Remove this when old camera controls are removed from the UI.
Michael Kolb8872c232013-01-29 10:33:22 -0800716 }
717
718 private void readVideoPreferences() {
719 // The preference stores values from ListPreference and is thus string type for all values.
720 // We need to convert it to int manually.
Erin Dahlgrenbd3da262013-12-02 11:48:13 -0800721 SettingsManager settingsManager = mActivity.getSettingsManager();
Erin Dahlgren6190c362014-06-13 14:12:08 -0700722 if (!settingsManager.isSet(SettingsManager.SCOPE_GLOBAL,
723 Keys.KEY_VIDEO_QUALITY_BACK)) {
724 settingsManager.setToDefault(SettingsManager.SCOPE_GLOBAL,
725 Keys.KEY_VIDEO_QUALITY_BACK);
Senpo Hu20571ff2014-08-18 16:45:45 -0700726
727 // TODO: Remove this once 4k recording is stable enough on new devices.
728 // Don't set the default resolution to be large if the device supports 4k video.
729 if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_2160P)) {
730 settingsManager.set(SettingsManager.SCOPE_GLOBAL,
731 Keys.KEY_VIDEO_QUALITY_BACK,
732 mAppController.getAndroidContext().getString(R.string.pref_video_quality_medium));
733 }
Doris Liu3f7e0042013-07-31 11:25:09 -0700734 }
Erin Dahlgren6190c362014-06-13 14:12:08 -0700735 if (!settingsManager.isSet(SettingsManager.SCOPE_GLOBAL,
736 Keys.KEY_VIDEO_QUALITY_FRONT)) {
737 settingsManager.setToDefault(SettingsManager.SCOPE_GLOBAL,
738 Keys.KEY_VIDEO_QUALITY_FRONT);
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700739 }
Erin Dahlgren6190c362014-06-13 14:12:08 -0700740 String videoQualityKey = isCameraFrontFacing() ? Keys.KEY_VIDEO_QUALITY_FRONT
741 : Keys.KEY_VIDEO_QUALITY_BACK;
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700742 String videoQuality = settingsManager
Erin Dahlgren6190c362014-06-13 14:12:08 -0700743 .getString(SettingsManager.SCOPE_GLOBAL, videoQualityKey);
Sascha Haeberlingde303232014-02-07 02:30:53 +0100744 int quality = SettingsUtil.getVideoQuality(videoQuality, mCameraId);
745 Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality);
Michael Kolb8872c232013-01-29 10:33:22 -0800746
747 // Set video quality.
748 Intent intent = mActivity.getIntent();
749 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
750 int extraVideoQuality =
751 intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
752 if (extraVideoQuality > 0) {
753 quality = CamcorderProfile.QUALITY_HIGH;
754 } else { // 0 is mms.
755 quality = CamcorderProfile.QUALITY_LOW;
756 }
757 }
758
759 // Set video duration limit. The limit is read from the preference,
760 // unless it is specified in the intent.
761 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
762 int seconds =
763 intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
764 mMaxVideoDurationInMs = 1000 * seconds;
765 } else {
Erin Dahlgren6190c362014-06-13 14:12:08 -0700766 mMaxVideoDurationInMs = SettingsUtil.getMaxVideoDuration(mActivity
Sascha Haeberling4044ab72014-04-02 16:25:03 -0700767 .getAndroidContext());
Michael Kolb8872c232013-01-29 10:33:22 -0800768 }
769
Andy Huibers329e1012014-02-04 08:03:35 -0800770 // If quality is not supported, request QUALITY_HIGH which is always supported.
771 if (CamcorderProfile.hasProfile(mCameraId, quality) == false) {
772 quality = CamcorderProfile.QUALITY_HIGH;
773 }
Michael Kolb8872c232013-01-29 10:33:22 -0800774 mProfile = CamcorderProfile.get(mCameraId, quality);
Angus Kong395ee2d2013-07-15 12:42:41 -0700775 mPreferenceRead = true;
ztenghuief01a312013-10-14 15:25:16 -0700776 if (mCameraDevice == null) {
777 return;
778 }
Angus Kong6607dae2014-06-10 16:07:45 -0700779 mCameraSettings = mCameraDevice.getSettings();
Angus Kong5f8c30e2014-03-06 17:15:08 -0800780 Point desiredPreviewSize = getDesiredPreviewSize(mAppController.getAndroidContext(),
Angus Kong6607dae2014-06-10 16:07:45 -0700781 mCameraSettings, mCameraCapabilities, mProfile, mUI.getPreviewScreenSize());
Angus Kong5f8c30e2014-03-06 17:15:08 -0800782 mDesiredPreviewWidth = desiredPreviewSize.x;
783 mDesiredPreviewHeight = desiredPreviewSize.y;
Doris Liu6432cd62013-06-13 17:20:31 -0700784 mUI.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800785 Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
786 ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
787 }
788
Angus Kong5f8c30e2014-03-06 17:15:08 -0800789 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
790 /**
791 * Calculates the preview size and stores it in mDesiredPreviewWidth and
Angus Kong831347d2014-06-16 16:07:28 -0700792 * mDesiredPreviewHeight.
793 *
794 * <p>This function checks {@link
795 * com.android.camera.cameradevice.CameraCapabilities#getPreferredPreviewSizeForVideo()}
Angus Kong5f8c30e2014-03-06 17:15:08 -0800796 * but also considers the current preview area size on screen and make sure
797 * the final preview size will not be smaller than 1/2 of the current
Angus Kong831347d2014-06-16 16:07:28 -0700798 * on screen preview area in terms of their short sides.</p>
Angus Kong5f8c30e2014-03-06 17:15:08 -0800799 *
800 * @return The preferred preview size or {@code null} if the camera is not
801 * opened yet.
802 */
Angus Kong6607dae2014-06-10 16:07:45 -0700803 private static Point getDesiredPreviewSize(Context context, CameraSettings settings,
804 CameraCapabilities capabilities, CamcorderProfile profile, Point previewScreenSize) {
805 if (capabilities.getSupportedVideoSizes() == null) {
Angus Kong5f8c30e2014-03-06 17:15:08 -0800806 // Driver doesn't support separate outputs for preview and video.
807 return new Point(profile.videoFrameWidth, profile.videoFrameHeight);
808 }
809
810 final int previewScreenShortSide = (previewScreenSize.x < previewScreenSize.y ?
811 previewScreenSize.x : previewScreenSize.y);
Angus Kong6607dae2014-06-10 16:07:45 -0700812 List<Size> sizes = capabilities.getSupportedPreviewSizes();
813 Size preferred = capabilities.getPreferredPreviewSizeForVideo();
Angus Kong00b7b102014-04-24 15:46:52 -0700814 final int preferredPreviewSizeShortSide = (preferred.width() < preferred.height() ?
815 preferred.width() : preferred.height());
Angus Kong5f8c30e2014-03-06 17:15:08 -0800816 if (preferredPreviewSizeShortSide * 2 < previewScreenShortSide) {
Angus Kong63424662014-04-23 10:47:47 -0700817 preferred = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
Angus Kong5f8c30e2014-03-06 17:15:08 -0800818 }
Angus Kong00b7b102014-04-24 15:46:52 -0700819 int product = preferred.width() * preferred.height();
Angus Kong5f8c30e2014-03-06 17:15:08 -0800820 Iterator<Size> it = sizes.iterator();
821 // Remove the preview sizes that are not preferred.
822 while (it.hasNext()) {
823 Size size = it.next();
Angus Kong00b7b102014-04-24 15:46:52 -0700824 if (size.width() * size.height() > product) {
Angus Kong5f8c30e2014-03-06 17:15:08 -0800825 it.remove();
826 }
827 }
828 Size optimalSize = CameraUtil.getOptimalPreviewSize(context, sizes,
829 (double) profile.videoFrameWidth / profile.videoFrameHeight);
Angus Kong00b7b102014-04-24 15:46:52 -0700830 return new Point(optimalSize.width(), optimalSize.height());
Angus Kong5f8c30e2014-03-06 17:15:08 -0800831 }
832
Michael Kolb8872c232013-01-29 10:33:22 -0800833 private void resizeForPreviewAspectRatio() {
Doris Liue038c162013-12-13 23:06:11 -0800834 mUI.setAspectRatio((float) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800835 }
836
Angus Kong13e87c42013-11-25 10:02:47 -0800837 private void installIntentFilter() {
Michael Kolb8872c232013-01-29 10:33:22 -0800838 // install an intent filter to receive SD card related events.
839 IntentFilter intentFilter =
840 new IntentFilter(Intent.ACTION_MEDIA_EJECT);
841 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
842 intentFilter.addDataScheme("file");
843 mReceiver = new MyBroadcastReceiver();
844 mActivity.registerReceiver(mReceiver, intentFilter);
845 }
846
Michael Kolb8872c232013-01-29 10:33:22 -0800847 private void setDisplayOrientation() {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700848 mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
Sol Boucher43e18132014-06-19 15:03:18 -0700849 Characteristics info =
850 mActivity.getCameraProvider().getCharacteristics(mCameraId);
Sol Boucher73ec9852014-07-24 23:59:21 -0700851 mCameraDisplayOrientation = info.getPreviewOrientation(mDisplayRotation);
Doris Liu6432cd62013-06-13 17:20:31 -0700852 // Change the camera display orientation
853 if (mCameraDevice != null) {
Sol Boucher73ec9852014-07-24 23:59:21 -0700854 mCameraDevice.setDisplayOrientation(mDisplayRotation);
Michael Kolb8872c232013-01-29 10:33:22 -0800855 }
Doris Liua1ec04a2014-01-13 17:29:40 -0800856 if (mFocusManager != null) {
857 mFocusManager.setDisplayOrientation(mCameraDisplayOrientation);
858 }
Doris Liu6432cd62013-06-13 17:20:31 -0700859 }
860
861 @Override
862 public void updateCameraOrientation() {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100863 if (mMediaRecorderRecording) {
864 return;
865 }
Angus Kongb50b5cb2013-08-09 14:55:20 -0700866 if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
Doris Liu6432cd62013-06-13 17:20:31 -0700867 setDisplayOrientation();
868 }
Michael Kolb8872c232013-01-29 10:33:22 -0800869 }
870
Doris Liu6827ce22013-03-12 19:24:28 -0700871 @Override
Doris Liu70da9182013-12-17 18:41:15 -0800872 public void updatePreviewAspectRatio(float aspectRatio) {
873 mAppController.updatePreviewAspectRatio(aspectRatio);
874 }
875
Andy Huibers203abe52014-05-19 13:59:01 -0700876 /**
877 * Returns current Zoom value, with 1.0 as the value for no zoom.
878 */
879 private float currentZoomValue() {
Sol Boucher2192fba2014-08-19 17:24:07 -0700880 return mCameraSettings.getCurrentZoomRatio();
Andy Huibers203abe52014-05-19 13:59:01 -0700881 }
882
Doris Liu70da9182013-12-17 18:41:15 -0800883 @Override
Sol Boucher2192fba2014-08-19 17:24:07 -0700884 public void onZoomChanged(float ratio) {
Doris Liu6827ce22013-03-12 19:24:28 -0700885 // Not useful to change zoom value when the activity is paused.
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100886 if (mPaused) {
Sol Boucher2192fba2014-08-19 17:24:07 -0700887 return;
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100888 }
Sol Boucher2192fba2014-08-19 17:24:07 -0700889 mZoomValue = ratio;
Angus Kong6607dae2014-06-10 16:07:45 -0700890 if (mCameraSettings == null || mCameraDevice == null) {
Sol Boucher2192fba2014-08-19 17:24:07 -0700891 return;
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100892 }
Doris Liu6827ce22013-03-12 19:24:28 -0700893 // Set zoom parameters asynchronously
Sol Boucher2192fba2014-08-19 17:24:07 -0700894 mCameraSettings.setZoomRatio(mZoomValue);
Angus Kong6607dae2014-06-10 16:07:45 -0700895 mCameraDevice.applySettings(mCameraSettings);
Doris Liu6827ce22013-03-12 19:24:28 -0700896 }
Angus Kong395ee2d2013-07-15 12:42:41 -0700897
Michael Kolb8872c232013-01-29 10:33:22 -0800898 private void startPreview() {
Alan Newbergerd41766f2014-04-09 18:25:34 -0700899 Log.i(TAG, "startPreview");
Michael Kolb8872c232013-01-29 10:33:22 -0800900
Erin Dahlgrend8de0772014-02-03 10:12:27 -0800901 SurfaceTexture surfaceTexture = mActivity.getCameraAppUI().getSurfaceTexture();
ztenghuief01a312013-10-14 15:25:16 -0700902 if (!mPreferenceRead || surfaceTexture == null || mPaused == true ||
903 mCameraDevice == null) {
904 return;
905 }
Angus Kong395ee2d2013-07-15 12:42:41 -0700906
Angus Kong2bca2102014-03-11 16:27:30 -0700907 mCameraDevice.setErrorCallback(mHandler, mErrorCallback);
Michael Kolb8872c232013-01-29 10:33:22 -0800908 if (mPreviewing == true) {
909 stopPreview();
Michael Kolb8872c232013-01-29 10:33:22 -0800910 }
911
Michael Kolb8872c232013-01-29 10:33:22 -0800912 setDisplayOrientation();
Sol Boucher73ec9852014-07-24 23:59:21 -0700913 mCameraDevice.setDisplayOrientation(mDisplayRotation);
Michael Kolb8872c232013-01-29 10:33:22 -0800914 setCameraParameters();
915
Doris Liua1ec04a2014-01-13 17:29:40 -0800916 if (mFocusManager != null) {
917 // If the focus mode is continuous autofocus, call cancelAutoFocus
918 // to resume it because it may have been paused by autoFocus call.
Angus Kong831347d2014-06-16 16:07:28 -0700919 CameraCapabilities.FocusMode focusMode =
920 mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode());
921 if (focusMode == CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
Doris Liua1ec04a2014-01-13 17:29:40 -0800922 mCameraDevice.cancelAutoFocus();
923 }
924 }
Doris Liu5a367542014-01-17 17:21:42 -0800925
926 // This is to notify app controller that preview will start next, so app
927 // controller can set preview callbacks if needed. This has to happen before
928 // preview is started as a workaround of the framework issue related to preview
929 // callbacks that causes preview stretch and crash. (More details see b/12210027
Alan Newbergerfb172ac2014-08-21 14:42:36 -0700930 // and b/12591410. Don't apply this to L, see b/16649297.
931 if (!ApiHelper.isLOrHigher()) {
932 Log.v(TAG, "calling onPreviewReadyToStart to set one shot callback");
933 mAppController.onPreviewReadyToStart();
934 } else {
935 Log.v(TAG, "on L, no one shot callback necessary");
936 }
Michael Kolb8872c232013-01-29 10:33:22 -0800937 try {
Doris Liu3973deb2013-08-21 13:42:22 -0700938 mCameraDevice.setPreviewTexture(surfaceTexture);
939 mCameraDevice.startPreview();
940 mPreviewing = true;
941 onPreviewStarted();
Michael Kolb8872c232013-01-29 10:33:22 -0800942 } catch (Throwable ex) {
943 closeCamera();
944 throw new RuntimeException("startPreview failed", ex);
Michael Kolb8872c232013-01-29 10:33:22 -0800945 }
Michael Kolbb1aeb392013-03-11 12:37:40 -0700946 }
947
948 private void onPreviewStarted() {
Erin Dahlgren667630d2014-04-01 14:03:25 -0700949 mAppController.setShutterEnabled(true);
Doris Liu2b906b82013-12-10 16:34:08 -0800950 mAppController.onPreviewStarted();
Doris Liua1ec04a2014-01-13 17:29:40 -0800951 if (mFocusManager != null) {
952 mFocusManager.onPreviewStarted();
953 }
Michael Kolb8872c232013-01-29 10:33:22 -0800954 }
955
Doris Liu6827ce22013-03-12 19:24:28 -0700956 @Override
Sameer Padaladb81ce62014-03-21 15:33:56 -0700957 public void onPreviewInitialDataReceived() {
Sameer Padaladb81ce62014-03-21 15:33:56 -0700958 }
959
960 @Override
Doris Liu6827ce22013-03-12 19:24:28 -0700961 public void stopPreview() {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100962 if (!mPreviewing) {
963 return;
964 }
Doris Liu6432cd62013-06-13 17:20:31 -0700965 mCameraDevice.stopPreview();
Doris Liua1ec04a2014-01-13 17:29:40 -0800966 if (mFocusManager != null) {
967 mFocusManager.onPreviewStopped();
968 }
Michael Kolb8872c232013-01-29 10:33:22 -0800969 mPreviewing = false;
970 }
971
Michael Kolb8872c232013-01-29 10:33:22 -0800972 private void closeCamera() {
Alan Newbergerd41766f2014-04-09 18:25:34 -0700973 Log.i(TAG, "closeCamera");
Doris Liu6432cd62013-06-13 17:20:31 -0700974 if (mCameraDevice == null) {
Michael Kolb8872c232013-01-29 10:33:22 -0800975 Log.d(TAG, "already stopped.");
976 return;
977 }
Doris Liu6432cd62013-06-13 17:20:31 -0700978 mCameraDevice.setZoomChangeListener(null);
Angus Kong2bca2102014-03-11 16:27:30 -0700979 mCameraDevice.setErrorCallback(null, null);
Angus Kong20fad242013-11-11 18:23:46 -0800980 mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
Angus Kong395ee2d2013-07-15 12:42:41 -0700981 mCameraDevice = null;
Michael Kolb8872c232013-01-29 10:33:22 -0800982 mPreviewing = false;
983 mSnapshotInProgress = false;
Doris Liua1ec04a2014-01-13 17:29:40 -0800984 if (mFocusManager != null) {
985 mFocusManager.onCameraReleased();
986 }
Michael Kolb8872c232013-01-29 10:33:22 -0800987 }
988
Michael Kolb8872c232013-01-29 10:33:22 -0800989 @Override
Michael Kolb8872c232013-01-29 10:33:22 -0800990 public boolean onBackPressed() {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100991 if (mPaused) {
992 return true;
993 }
Michael Kolb8872c232013-01-29 10:33:22 -0800994 if (mMediaRecorderRecording) {
995 onStopVideoRecording();
996 return true;
Michael Kolb8872c232013-01-29 10:33:22 -0800997 } else {
Doris Liuf9e4f8f2013-12-04 18:04:22 -0800998 return false;
Michael Kolb8872c232013-01-29 10:33:22 -0800999 }
1000 }
1001
1002 @Override
1003 public boolean onKeyDown(int keyCode, KeyEvent event) {
1004 // Do not handle any key if the activity is paused.
1005 if (mPaused) {
1006 return true;
1007 }
1008
1009 switch (keyCode) {
1010 case KeyEvent.KEYCODE_CAMERA:
1011 if (event.getRepeatCount() == 0) {
Erin Dahlgren667630d2014-04-01 14:03:25 -07001012 onShutterButtonClick();
Michael Kolb8872c232013-01-29 10:33:22 -08001013 return true;
1014 }
Michael Kolb8872c232013-01-29 10:33:22 -08001015 case KeyEvent.KEYCODE_DPAD_CENTER:
1016 if (event.getRepeatCount() == 0) {
Erin Dahlgren667630d2014-04-01 14:03:25 -07001017 onShutterButtonClick();
Michael Kolb8872c232013-01-29 10:33:22 -08001018 return true;
1019 }
Michael Kolb8872c232013-01-29 10:33:22 -08001020 case KeyEvent.KEYCODE_MENU:
Erin Dahlgren15691af2014-03-14 14:10:57 -07001021 // Consume menu button presses during capture.
1022 return mMediaRecorderRecording;
Michael Kolb8872c232013-01-29 10:33:22 -08001023 }
1024 return false;
1025 }
1026
1027 @Override
1028 public boolean onKeyUp(int keyCode, KeyEvent event) {
1029 switch (keyCode) {
1030 case KeyEvent.KEYCODE_CAMERA:
Erin Dahlgren667630d2014-04-01 14:03:25 -07001031 onShutterButtonClick();
Michael Kolb8872c232013-01-29 10:33:22 -08001032 return true;
Erin Dahlgren15691af2014-03-14 14:10:57 -07001033 case KeyEvent.KEYCODE_MENU:
1034 // Consume menu button presses during capture.
1035 return mMediaRecorderRecording;
Michael Kolb8872c232013-01-29 10:33:22 -08001036 }
1037 return false;
1038 }
1039
Doris Liu6827ce22013-03-12 19:24:28 -07001040 @Override
1041 public boolean isVideoCaptureIntent() {
Michael Kolb8872c232013-01-29 10:33:22 -08001042 String action = mActivity.getIntent().getAction();
1043 return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
1044 }
1045
1046 private void doReturnToCaller(boolean valid) {
1047 Intent resultIntent = new Intent();
1048 int resultCode;
1049 if (valid) {
1050 resultCode = Activity.RESULT_OK;
1051 resultIntent.setData(mCurrentVideoUri);
1052 } else {
1053 resultCode = Activity.RESULT_CANCELED;
1054 }
1055 mActivity.setResultEx(resultCode, resultIntent);
1056 mActivity.finish();
1057 }
1058
1059 private void cleanupEmptyFile() {
1060 if (mVideoFilename != null) {
1061 File f = new File(mVideoFilename);
1062 if (f.length() == 0 && f.delete()) {
1063 Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
1064 mVideoFilename = null;
1065 }
1066 }
1067 }
1068
Michael Kolb8872c232013-01-29 10:33:22 -08001069 // Prepares media recorder.
1070 private void initializeRecorder() {
Alan Newbergerd41766f2014-04-09 18:25:34 -07001071 Log.i(TAG, "initializeRecorder");
Michael Kolb8872c232013-01-29 10:33:22 -08001072 // If the mCameraDevice is null, then this activity is going to finish
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001073 if (mCameraDevice == null) {
1074 return;
1075 }
Michael Kolb8872c232013-01-29 10:33:22 -08001076
Michael Kolb8872c232013-01-29 10:33:22 -08001077 Intent intent = mActivity.getIntent();
1078 Bundle myExtras = intent.getExtras();
1079
1080 long requestedSizeLimit = 0;
1081 closeVideoFileDescriptor();
ztenghui70bd0242013-10-14 11:15:44 -07001082 mCurrentVideoUriFromMediaSaved = false;
Michael Kolb8872c232013-01-29 10:33:22 -08001083 if (mIsVideoCaptureIntent && myExtras != null) {
1084 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1085 if (saveUri != null) {
1086 try {
1087 mVideoFileDescriptor =
1088 mContentResolver.openFileDescriptor(saveUri, "rw");
1089 mCurrentVideoUri = saveUri;
1090 } catch (java.io.FileNotFoundException ex) {
1091 // invalid uri
1092 Log.e(TAG, ex.toString());
1093 }
1094 }
1095 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1096 }
1097 mMediaRecorder = new MediaRecorder();
1098
Michael Kolb8872c232013-01-29 10:33:22 -08001099 // Unlock the camera object before passing it to media recorder.
Doris Liu6432cd62013-06-13 17:20:31 -07001100 mCameraDevice.unlock();
Doris Liu6432cd62013-06-13 17:20:31 -07001101 mMediaRecorder.setCamera(mCameraDevice.getCamera());
Sascha Haeberlingf15d63e2014-07-21 11:31:13 -07001102 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
Michael Kolb8872c232013-01-29 10:33:22 -08001103 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
1104 mMediaRecorder.setProfile(mProfile);
Michael Kolbf9542362013-05-30 07:45:41 -07001105 mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight);
Michael Kolb8872c232013-01-29 10:33:22 -08001106 mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
Michael Kolb8872c232013-01-29 10:33:22 -08001107
1108 setRecordLocation();
1109
1110 // Set output file.
1111 // Try Uri in the intent first. If it doesn't exist, use our own
1112 // instead.
1113 if (mVideoFileDescriptor != null) {
1114 mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1115 } else {
1116 generateVideoFilename(mProfile.fileFormat);
1117 mMediaRecorder.setOutputFile(mVideoFilename);
1118 }
1119
1120 // Set maximum file size.
Angus Kong2dcc0a92013-09-25 13:00:08 -07001121 long maxFileSize = mActivity.getStorageSpaceBytes() - Storage.LOW_STORAGE_THRESHOLD_BYTES;
Michael Kolb8872c232013-01-29 10:33:22 -08001122 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1123 maxFileSize = requestedSizeLimit;
1124 }
1125
1126 try {
1127 mMediaRecorder.setMaxFileSize(maxFileSize);
1128 } catch (RuntimeException exception) {
1129 // We are going to ignore failure of setMaxFileSize here, as
1130 // a) The composer selected may simply not support it, or
1131 // b) The underlying media framework may not handle 64-bit range
1132 // on the size restriction.
1133 }
1134
Angus Kong831347d2014-06-16 16:07:28 -07001135 // See com.android.camera.cameradevice.CameraSettings.setPhotoRotationDegrees
1136 // for documentation.
Michael Kolb8872c232013-01-29 10:33:22 -08001137 // Note that mOrientation here is the device orientation, which is the opposite of
1138 // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
1139 // which is the orientation the graphics need to rotate in order to render correctly.
1140 int rotation = 0;
1141 if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
Sol Boucher43e18132014-06-19 15:03:18 -07001142 Characteristics info =
1143 mActivity.getCameraProvider().getCharacteristics(mCameraId);
Sascha Haeberling6ccec202014-03-11 09:44:34 -07001144 if (isCameraFrontFacing()) {
Sol Boucher43e18132014-06-19 15:03:18 -07001145 rotation = (info.getSensorOrientation() - mOrientation + 360) % 360;
1146 } else if (isCameraBackFacing()) {
1147 rotation = (info.getSensorOrientation() + mOrientation) % 360;
1148 } else {
1149 Log.e(TAG, "Camera is facing unhandled direction");
Michael Kolb8872c232013-01-29 10:33:22 -08001150 }
1151 }
1152 mMediaRecorder.setOrientationHint(rotation);
1153
1154 try {
1155 mMediaRecorder.prepare();
1156 } catch (IOException e) {
1157 Log.e(TAG, "prepare failed for " + mVideoFilename, e);
1158 releaseMediaRecorder();
1159 throw new RuntimeException(e);
1160 }
1161
1162 mMediaRecorder.setOnErrorListener(this);
1163 mMediaRecorder.setOnInfoListener(this);
1164 }
1165
Michael Kolb8872c232013-01-29 10:33:22 -08001166 private static void setCaptureRate(MediaRecorder recorder, double fps) {
1167 recorder.setCaptureRate(fps);
1168 }
1169
Michael Kolb8872c232013-01-29 10:33:22 -08001170 private void setRecordLocation() {
Sascha Haeberling638e6f02013-09-18 14:28:51 -07001171 Location loc = mLocationManager.getCurrentLocation();
1172 if (loc != null) {
1173 mMediaRecorder.setLocation((float) loc.getLatitude(),
1174 (float) loc.getLongitude());
Michael Kolb8872c232013-01-29 10:33:22 -08001175 }
1176 }
1177
Michael Kolb8872c232013-01-29 10:33:22 -08001178 private void releaseMediaRecorder() {
Alan Newbergerd41766f2014-04-09 18:25:34 -07001179 Log.i(TAG, "Releasing media recorder.");
Michael Kolb8872c232013-01-29 10:33:22 -08001180 if (mMediaRecorder != null) {
1181 cleanupEmptyFile();
1182 mMediaRecorder.reset();
1183 mMediaRecorder.release();
1184 mMediaRecorder = null;
1185 }
1186 mVideoFilename = null;
1187 }
1188
Michael Kolb8872c232013-01-29 10:33:22 -08001189 private void generateVideoFilename(int outputFileFormat) {
1190 long dateTaken = System.currentTimeMillis();
1191 String title = createName(dateTaken);
1192 // Used when emailing.
1193 String filename = title + convertOutputFormatToFileExt(outputFileFormat);
1194 String mime = convertOutputFormatToMimeType(outputFileFormat);
1195 String path = Storage.DIRECTORY + '/' + filename;
1196 String tmpPath = path + ".tmp";
Ruben Brunk16007962013-04-19 15:27:57 -07001197 mCurrentVideoValues = new ContentValues(9);
Michael Kolb8872c232013-01-29 10:33:22 -08001198 mCurrentVideoValues.put(Video.Media.TITLE, title);
1199 mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
1200 mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
Ruben Brunk16007962013-04-19 15:27:57 -07001201 mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000);
Michael Kolb8872c232013-01-29 10:33:22 -08001202 mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
1203 mCurrentVideoValues.put(Video.Media.DATA, path);
Sam Juddde3e9ab2014-03-17 13:07:22 -07001204 mCurrentVideoValues.put(Video.Media.WIDTH, mProfile.videoFrameWidth);
1205 mCurrentVideoValues.put(Video.Media.HEIGHT, mProfile.videoFrameHeight);
Michael Kolb8872c232013-01-29 10:33:22 -08001206 mCurrentVideoValues.put(Video.Media.RESOLUTION,
1207 Integer.toString(mProfile.videoFrameWidth) + "x" +
1208 Integer.toString(mProfile.videoFrameHeight));
1209 Location loc = mLocationManager.getCurrentLocation();
1210 if (loc != null) {
1211 mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
1212 mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
1213 }
Michael Kolb8872c232013-01-29 10:33:22 -08001214 mVideoFilename = tmpPath;
1215 Log.v(TAG, "New video filename: " + mVideoFilename);
1216 }
1217
Andy Huibers203abe52014-05-19 13:59:01 -07001218 private void logVideoCapture(long duration) {
1219 String flashSetting = mActivity.getSettingsManager()
Erin Dahlgren6190c362014-06-13 14:12:08 -07001220 .getString(mAppController.getCameraScope(),
1221 Keys.KEY_VIDEOCAMERA_FLASH_MODE);
1222 boolean gridLinesOn = Keys.areGridLinesOn(mActivity.getSettingsManager());
Andy Huibers203abe52014-05-19 13:59:01 -07001223 int width = (Integer) mCurrentVideoValues.get(Video.Media.WIDTH);
1224 int height = (Integer) mCurrentVideoValues.get(Video.Media.HEIGHT);
1225 long size = new File(mCurrentVideoFilename).length();
1226 String name = new File(mCurrentVideoValues.getAsString(Video.Media.DATA)).getName();
1227 UsageStatistics.instance().videoCaptureDoneEvent(name, duration, isCameraFrontFacing(),
1228 currentZoomValue(), width, height, size, flashSetting, gridLinesOn);
1229 }
1230
Angus Kong83a99ae2013-04-17 15:37:07 -07001231 private void saveVideo() {
Michael Kolb8872c232013-01-29 10:33:22 -08001232 if (mVideoFileDescriptor == null) {
Michael Kolb8872c232013-01-29 10:33:22 -08001233 long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
1234 if (duration > 0) {
Spike Spragued020fbf2014-07-16 15:47:32 -07001235 //
Michael Kolb8872c232013-01-29 10:33:22 -08001236 } else {
1237 Log.w(TAG, "Video duration <= 0 : " + duration);
1238 }
Andy Huibers203abe52014-05-19 13:59:01 -07001239 mCurrentVideoValues.put(Video.Media.SIZE, new File(mCurrentVideoFilename).length());
1240 mCurrentVideoValues.put(Video.Media.DURATION, duration);
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08001241 getServices().getMediaSaver().addVideo(mCurrentVideoFilename,
Andy Huibers203abe52014-05-19 13:59:01 -07001242 mCurrentVideoValues, mOnVideoSavedListener, mContentResolver);
1243 logVideoCapture(duration);
Michael Kolb8872c232013-01-29 10:33:22 -08001244 }
1245 mCurrentVideoValues = null;
Michael Kolb8872c232013-01-29 10:33:22 -08001246 }
1247
1248 private void deleteVideoFile(String fileName) {
1249 Log.v(TAG, "Deleting video " + fileName);
1250 File f = new File(fileName);
1251 if (!f.delete()) {
1252 Log.v(TAG, "Could not delete " + fileName);
1253 }
1254 }
1255
Michael Kolb8872c232013-01-29 10:33:22 -08001256 // from MediaRecorder.OnErrorListener
1257 @Override
1258 public void onError(MediaRecorder mr, int what, int extra) {
1259 Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
1260 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1261 // We may have run out of space on the sdcard.
1262 stopVideoRecording();
Spike Spraguee6374b72014-04-25 17:24:32 -07001263 mActivity.updateStorageSpaceAndHint(null);
Michael Kolb8872c232013-01-29 10:33:22 -08001264 }
1265 }
1266
1267 // from MediaRecorder.OnInfoListener
1268 @Override
1269 public void onInfo(MediaRecorder mr, int what, int extra) {
1270 if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001271 if (mMediaRecorderRecording) {
1272 onStopVideoRecording();
1273 }
Michael Kolb8872c232013-01-29 10:33:22 -08001274 } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001275 if (mMediaRecorderRecording) {
1276 onStopVideoRecording();
1277 }
Michael Kolb8872c232013-01-29 10:33:22 -08001278
1279 // Show the toast.
1280 Toast.makeText(mActivity, R.string.video_reach_size_limit,
1281 Toast.LENGTH_LONG).show();
1282 }
1283 }
1284
1285 /*
1286 * Make sure we're not recording music playing in the background, ask the
1287 * MediaPlaybackService to pause playback.
1288 */
1289 private void pauseAudioPlayback() {
Marco Nelissen20694b22013-10-29 15:27:24 -07001290 AudioManager am = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);
1291 am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
Michael Kolb8872c232013-01-29 10:33:22 -08001292 }
1293
1294 // For testing.
1295 public boolean isRecording() {
1296 return mMediaRecorderRecording;
1297 }
1298
1299 private void startVideoRecording() {
Alan Newbergerd41766f2014-04-09 18:25:34 -07001300 Log.i(TAG, "startVideoRecording");
Sascha Haeberling37f36112013-08-06 14:31:52 -07001301 mUI.cancelAnimations();
Doris Liu6432cd62013-06-13 17:20:31 -07001302 mUI.setSwipingEnabled(false);
Doris Liu38c6bc32014-01-16 18:03:18 -08001303 mUI.showFocusUI(false);
Doris Liud6487c92014-02-28 10:35:45 -08001304 mUI.showVideoRecordingHints(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001305
Spike Spraguee6374b72014-04-25 17:24:32 -07001306 mActivity.updateStorageSpaceAndHint(new CameraActivity.OnStorageUpdateDoneListener() {
1307 @Override
1308 public void onStorageUpdateDone(long bytes) {
1309 if (bytes <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1310 Log.w(TAG, "Storage issue, ignore the start request");
1311 } else {
1312 //??
1313 //if (!mCameraDevice.waitDone()) return;
Alan Newberger26b224d2014-08-26 18:18:48 -07001314 if (mPaused == true) {
1315 Log.v(TAG, "in storage callback after module paused");
1316 return;
1317 }
Spike Spraguee6374b72014-04-25 17:24:32 -07001318 mCurrentVideoUri = null;
Michael Kolb8872c232013-01-29 10:33:22 -08001319
Spike Spraguee6374b72014-04-25 17:24:32 -07001320 initializeRecorder();
1321 if (mMediaRecorder == null) {
1322 Log.e(TAG, "Fail to initialize media recorder");
1323 return;
1324 }
Doris Liu3973deb2013-08-21 13:42:22 -07001325
Spike Spraguee6374b72014-04-25 17:24:32 -07001326 pauseAudioPlayback();
Michael Kolb8872c232013-01-29 10:33:22 -08001327
Spike Spraguee6374b72014-04-25 17:24:32 -07001328 try {
1329 mMediaRecorder.start(); // Recording is now started
1330 } catch (RuntimeException e) {
1331 Log.e(TAG, "Could not start media recorder. ", e);
1332 releaseMediaRecorder();
1333 // If start fails, frameworks will not lock the camera for us.
1334 mCameraDevice.lock();
1335 return;
1336 }
1337 mAppController.getCameraAppUI().setSwipeEnabled(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001338
Spike Spraguee6374b72014-04-25 17:24:32 -07001339 // The parameters might have been altered by MediaRecorder already.
1340 // We need to force mCameraDevice to refresh before getting it.
Angus Kong6607dae2014-06-10 16:07:45 -07001341 mCameraDevice.refreshSettings();
Spike Spraguee6374b72014-04-25 17:24:32 -07001342 // The parameters may have been changed by MediaRecorder upon starting
1343 // recording. We need to alter the parameters if we support camcorder
1344 // zoom. To reduce latency when setting the parameters during zoom, we
Angus Kong6607dae2014-06-10 16:07:45 -07001345 // update the settings here once.
1346 mCameraSettings = mCameraDevice.getSettings();
Michael Kolb8872c232013-01-29 10:33:22 -08001347
Spike Spraguee6374b72014-04-25 17:24:32 -07001348 mMediaRecorderRecording = true;
1349 mActivity.lockOrientation();
1350 mRecordingStartTime = SystemClock.uptimeMillis();
Michael Kolb8872c232013-01-29 10:33:22 -08001351
Spike Spraguee6374b72014-04-25 17:24:32 -07001352 // A special case of mode options closing: during capture it should
1353 // not be possible to change mode state.
1354 mAppController.getCameraAppUI().hideModeOptions();
1355 mAppController.getCameraAppUI().animateBottomBarToVideoStop(R.drawable.ic_stop);
1356 mUI.showRecordingUI(true);
Doris Liu4df91582014-03-21 18:33:57 -07001357
Spike Spraguee6374b72014-04-25 17:24:32 -07001358 setFocusParameters();
Senpo Hu11186312014-08-08 13:42:13 -07001359
Spike Spraguee6374b72014-04-25 17:24:32 -07001360 updateRecordingTime();
1361 mActivity.enableKeepScreenOn(true);
1362 }
1363 }
1364 });
Michael Kolb8872c232013-01-29 10:33:22 -08001365 }
1366
Sascha Haeberling37f36112013-08-06 14:31:52 -07001367 private Bitmap getVideoThumbnail() {
Michael Kolb8872c232013-01-29 10:33:22 -08001368 Bitmap bitmap = null;
1369 if (mVideoFileDescriptor != null) {
1370 bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
Doris Liu3c2fca32013-02-13 18:28:03 -08001371 mDesiredPreviewWidth);
Doris Liu2a7f44c2013-08-12 15:18:53 -07001372 } else if (mCurrentVideoUri != null) {
1373 try {
1374 mVideoFileDescriptor = mContentResolver.openFileDescriptor(mCurrentVideoUri, "r");
1375 bitmap = Thumbnail.createVideoThumbnailBitmap(
1376 mVideoFileDescriptor.getFileDescriptor(), mDesiredPreviewWidth);
1377 } catch (java.io.FileNotFoundException ex) {
1378 // invalid uri
1379 Log.e(TAG, ex.toString());
1380 }
Michael Kolb8872c232013-01-29 10:33:22 -08001381 }
Doris Liu3973deb2013-08-21 13:42:22 -07001382
Michael Kolb8872c232013-01-29 10:33:22 -08001383 if (bitmap != null) {
1384 // MetadataRetriever already rotates the thumbnail. We should rotate
1385 // it to match the UI orientation (and mirror if it is front-facing camera).
Sascha Haeberling6ccec202014-03-11 09:44:34 -07001386 bitmap = CameraUtil.rotateAndMirror(bitmap, 0, isCameraFrontFacing());
Sascha Haeberling37f36112013-08-06 14:31:52 -07001387 }
1388 return bitmap;
1389 }
1390
1391 private void showCaptureResult() {
1392 mIsInReviewMode = true;
1393 Bitmap bitmap = getVideoThumbnail();
1394 if (bitmap != null) {
Doris Liu6827ce22013-03-12 19:24:28 -07001395 mUI.showReviewImage(bitmap);
Michael Kolb8872c232013-01-29 10:33:22 -08001396 }
Doris Liu6827ce22013-03-12 19:24:28 -07001397 mUI.showReviewControls();
Michael Kolb8872c232013-01-29 10:33:22 -08001398 }
1399
Michael Kolb8872c232013-01-29 10:33:22 -08001400 private boolean stopVideoRecording() {
Alan Newbergerd41766f2014-04-09 18:25:34 -07001401 Log.i(TAG, "stopVideoRecording");
Doris Liu6432cd62013-06-13 17:20:31 -07001402 mUI.setSwipingEnabled(true);
Doris Liu38c6bc32014-01-16 18:03:18 -08001403 mUI.showFocusUI(true);
Doris Liud6487c92014-02-28 10:35:45 -08001404 mUI.showVideoRecordingHints(true);
Michael Kolb8872c232013-01-29 10:33:22 -08001405
1406 boolean fail = false;
1407 if (mMediaRecorderRecording) {
1408 boolean shouldAddToMediaStoreNow = false;
1409
1410 try {
Doris Liu3973deb2013-08-21 13:42:22 -07001411 mMediaRecorder.setOnErrorListener(null);
1412 mMediaRecorder.setOnInfoListener(null);
1413 mMediaRecorder.stop();
1414 shouldAddToMediaStoreNow = true;
Michael Kolb8872c232013-01-29 10:33:22 -08001415 mCurrentVideoFilename = mVideoFilename;
Andy Huibers10c58162014-03-29 14:06:54 -07001416 Log.v(TAG, "stopVideoRecording: current video filename: " + mCurrentVideoFilename);
Michael Kolb8872c232013-01-29 10:33:22 -08001417 } catch (RuntimeException e) {
1418 Log.e(TAG, "stop fail", e);
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001419 if (mVideoFilename != null) {
1420 deleteVideoFile(mVideoFilename);
1421 }
Michael Kolb8872c232013-01-29 10:33:22 -08001422 fail = true;
1423 }
1424 mMediaRecorderRecording = false;
Angus Kong9f1db522013-11-09 16:25:59 -08001425 mActivity.unlockOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001426
1427 // If the activity is paused, this means activity is interrupted
1428 // during recording. Release the camera as soon as possible because
1429 // face unlock or other applications may need to use the camera.
Michael Kolb8872c232013-01-29 10:33:22 -08001430 if (mPaused) {
Doris Liu3973deb2013-08-21 13:42:22 -07001431 closeCamera();
Michael Kolb8872c232013-01-29 10:33:22 -08001432 }
1433
Doris Liufe6596c2013-10-08 11:03:37 -07001434 mUI.showRecordingUI(false);
Michael Kolb8872c232013-01-29 10:33:22 -08001435 // The orientation was fixed during video recording. Now make it
1436 // reflect the device orientation as video recording is stopped.
Doris Liu6827ce22013-03-12 19:24:28 -07001437 mUI.setOrientationIndicator(0, true);
Angus Kong13e87c42013-11-25 10:02:47 -08001438 mActivity.enableKeepScreenOn(false);
Doris Liu2a7f44c2013-08-12 15:18:53 -07001439 if (shouldAddToMediaStoreNow && !fail) {
1440 if (mVideoFileDescriptor == null) {
1441 saveVideo();
1442 } else if (mIsVideoCaptureIntent) {
1443 // if no file save is needed, we can show the post capture UI now
1444 showCaptureResult();
1445 }
Michael Kolb8872c232013-01-29 10:33:22 -08001446 }
1447 }
Doris Liu3973deb2013-08-21 13:42:22 -07001448 // release media recorder
1449 releaseMediaRecorder();
Doris Liu4df91582014-03-21 18:33:57 -07001450
1451 mAppController.getCameraAppUI().showModeOptions();
1452 mAppController.getCameraAppUI().animateBottomBarToFullSize(mShutterIconId);
Doris Liu3973deb2013-08-21 13:42:22 -07001453 if (!mPaused) {
Doris Liu1143ebd2014-01-24 14:11:50 -08001454 setFocusParameters();
Doris Liu3973deb2013-08-21 13:42:22 -07001455 mCameraDevice.lock();
1456 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
1457 stopPreview();
Doris Liu3973deb2013-08-21 13:42:22 -07001458 // Switch back to use SurfaceTexture for preview.
1459 startPreview();
Michael Kolb8872c232013-01-29 10:33:22 -08001460 }
Angus Kong62753ae2014-02-10 10:53:54 -08001461 // Update the parameters here because the parameters might have been altered
1462 // by MediaRecorder.
Angus Kong6607dae2014-06-10 16:07:45 -07001463 mCameraSettings = mCameraDevice.getSettings();
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001464 }
Spike Sprague3f673792014-04-14 16:16:27 -07001465
Spike Spraguee6374b72014-04-25 17:24:32 -07001466 // Check this in advance of each shot so we don't add to shutter
1467 // latency. It's true that someone else could write to the SD card
1468 // in the mean time and fill it, but that could have happened
1469 // between the shutter press and saving the file too.
1470 mActivity.updateStorageSpaceAndHint(null);
Spike Sprague3f673792014-04-14 16:16:27 -07001471
Michael Kolb8872c232013-01-29 10:33:22 -08001472 return fail;
1473 }
1474
Michael Kolb8872c232013-01-29 10:33:22 -08001475 private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
1476 long seconds = milliSeconds / 1000; // round down to compute seconds
1477 long minutes = seconds / 60;
1478 long hours = minutes / 60;
1479 long remainderMinutes = minutes - (hours * 60);
1480 long remainderSeconds = seconds - (minutes * 60);
1481
1482 StringBuilder timeStringBuilder = new StringBuilder();
1483
1484 // Hours
1485 if (hours > 0) {
1486 if (hours < 10) {
1487 timeStringBuilder.append('0');
1488 }
1489 timeStringBuilder.append(hours);
1490
1491 timeStringBuilder.append(':');
1492 }
1493
1494 // Minutes
1495 if (remainderMinutes < 10) {
1496 timeStringBuilder.append('0');
1497 }
1498 timeStringBuilder.append(remainderMinutes);
1499 timeStringBuilder.append(':');
1500
1501 // Seconds
1502 if (remainderSeconds < 10) {
1503 timeStringBuilder.append('0');
1504 }
1505 timeStringBuilder.append(remainderSeconds);
1506
1507 // Centi seconds
1508 if (displayCentiSeconds) {
1509 timeStringBuilder.append('.');
1510 long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
1511 if (remainderCentiSeconds < 10) {
1512 timeStringBuilder.append('0');
1513 }
1514 timeStringBuilder.append(remainderCentiSeconds);
1515 }
1516
1517 return timeStringBuilder.toString();
1518 }
1519
Michael Kolb8872c232013-01-29 10:33:22 -08001520 private void updateRecordingTime() {
1521 if (!mMediaRecorderRecording) {
1522 return;
1523 }
1524 long now = SystemClock.uptimeMillis();
1525 long delta = now - mRecordingStartTime;
1526
1527 // Starting a minute before reaching the max duration
1528 // limit, we'll countdown the remaining time instead.
1529 boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1530 && delta >= mMaxVideoDurationInMs - 60000);
1531
1532 long deltaAdjusted = delta;
1533 if (countdownRemainingTime) {
1534 deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
1535 }
1536 String text;
1537
1538 long targetNextUpdateDelay;
Spike Spragued020fbf2014-07-16 15:47:32 -07001539
1540 text = millisecondToTimeString(deltaAdjusted, false);
1541 targetNextUpdateDelay = 1000;
Michael Kolb8872c232013-01-29 10:33:22 -08001542
Doris Liu6827ce22013-03-12 19:24:28 -07001543 mUI.setRecordingTime(text);
Michael Kolb8872c232013-01-29 10:33:22 -08001544
1545 if (mRecordingTimeCountsDown != countdownRemainingTime) {
1546 // Avoid setting the color on every update, do it only
1547 // when it needs changing.
1548 mRecordingTimeCountsDown = countdownRemainingTime;
1549
Spike Spragued020fbf2014-07-16 15:47:32 -07001550 int color = mActivity.getResources().getColor(R.color.recording_time_remaining_text);
Michael Kolb8872c232013-01-29 10:33:22 -08001551
Doris Liu6827ce22013-03-12 19:24:28 -07001552 mUI.setRecordingTimeTextColor(color);
Michael Kolb8872c232013-01-29 10:33:22 -08001553 }
1554
1555 long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
Angus Kong13e87c42013-11-25 10:02:47 -08001556 mHandler.sendEmptyMessageDelayed(MSG_UPDATE_RECORD_TIME, actualNextUpdateDelay);
Michael Kolb8872c232013-01-29 10:33:22 -08001557 }
1558
1559 private static boolean isSupported(String value, List<String> supported) {
1560 return supported == null ? false : supported.indexOf(value) >= 0;
1561 }
1562
1563 @SuppressWarnings("deprecation")
1564 private void setCameraParameters() {
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001565 SettingsManager settingsManager = mActivity.getSettingsManager();
1566
Angus Kong6607dae2014-06-10 16:07:45 -07001567 mCameraSettings.setPreviewSize(new Size(mDesiredPreviewWidth, mDesiredPreviewHeight));
Andy Huibers6dcf90c2014-04-14 13:53:12 -07001568 // This is required for Samsung SGH-I337 and probably other Samsung S4 versions
1569 if (Build.BRAND.toLowerCase().contains("samsung")) {
Angus Kong6607dae2014-06-10 16:07:45 -07001570 mCameraSettings.setSetting("video-size",
1571 mProfile.videoFrameWidth + "x" + mProfile.videoFrameHeight);
Andy Huibers6dcf90c2014-04-14 13:53:12 -07001572 }
Angus Kong6607dae2014-06-10 16:07:45 -07001573 int[] fpsRange =
1574 CameraUtil.getMaxPreviewFpsRange(mCameraCapabilities.getSupportedPreviewFpsRange());
Doris Liu6432cd62013-06-13 17:20:31 -07001575 if (fpsRange.length > 0) {
Angus Kong831347d2014-06-16 16:07:28 -07001576 mCameraSettings.setPreviewFpsRange(fpsRange[0], fpsRange[1]);
Doris Liu6432cd62013-06-13 17:20:31 -07001577 } else {
Angus Kong6607dae2014-06-10 16:07:45 -07001578 mCameraSettings.setPreviewFrameRate(mProfile.videoFrameRate);
Doris Liu6432cd62013-06-13 17:20:31 -07001579 }
Michael Kolb8872c232013-01-29 10:33:22 -08001580
Erin Dahlgren6190c362014-06-13 14:12:08 -07001581 enableTorchMode(Keys.isCameraBackFacing(settingsManager, mAppController.getModuleScope()));
Michael Kolb8872c232013-01-29 10:33:22 -08001582
Michael Kolb8872c232013-01-29 10:33:22 -08001583 // Set zoom.
Angus Kong88289042014-04-22 16:39:42 -07001584 if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) {
Sol Boucher2192fba2014-08-19 17:24:07 -07001585 mCameraSettings.setZoomRatio(mZoomValue);
Michael Kolb8872c232013-01-29 10:33:22 -08001586 }
Doris Liua1ec04a2014-01-13 17:29:40 -08001587 updateFocusParameters();
Michael Kolb8872c232013-01-29 10:33:22 -08001588
Angus Kong831347d2014-06-16 16:07:28 -07001589 mCameraSettings.setRecordingHintEnabled(true);
Michael Kolb8872c232013-01-29 10:33:22 -08001590
Angus Kong6607dae2014-06-10 16:07:45 -07001591 if (mCameraCapabilities.supports(CameraCapabilities.Feature.VIDEO_STABILIZATION)) {
1592 mCameraSettings.setVideoStabilization(true);
Michael Kolb8872c232013-01-29 10:33:22 -08001593 }
1594
1595 // Set picture size.
1596 // The logic here is different from the logic in still-mode camera.
1597 // There we determine the preview size based on the picture size, but
1598 // here we determine the picture size based on the preview size.
Angus Kong6607dae2014-06-10 16:07:45 -07001599 List<Size> supported = mCameraCapabilities.getSupportedPhotoSizes();
Angus Kongb50b5cb2013-08-09 14:55:20 -07001600 Size optimalSize = CameraUtil.getOptimalVideoSnapshotPictureSize(supported,
Michael Kolb8872c232013-01-29 10:33:22 -08001601 (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
Angus Kong6607dae2014-06-10 16:07:45 -07001602 Size original = new Size(mCameraSettings.getCurrentPhotoSize());
Michael Kolb8872c232013-01-29 10:33:22 -08001603 if (!original.equals(optimalSize)) {
Angus Kong6607dae2014-06-10 16:07:45 -07001604 mCameraSettings.setPhotoSize(optimalSize);
Michael Kolb8872c232013-01-29 10:33:22 -08001605 }
Angus Kong00b7b102014-04-24 15:46:52 -07001606 Log.d(TAG, "Video snapshot size is " + optimalSize);
Michael Kolb8872c232013-01-29 10:33:22 -08001607
1608 // Set JPEG quality.
1609 int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1610 CameraProfile.QUALITY_HIGH);
Angus Kong6607dae2014-06-10 16:07:45 -07001611 mCameraSettings.setPhotoJpegCompressionQuality(jpegQuality);
Michael Kolb8872c232013-01-29 10:33:22 -08001612
Angus Kong6607dae2014-06-10 16:07:45 -07001613 mCameraDevice.applySettings(mCameraSettings);
Andy Huibers64abfe92014-01-14 22:25:52 -08001614 // Nexus 5 through KitKat 4.4.2 requires a second call to
1615 // .setParameters() for frame rate settings to take effect.
Angus Kong6607dae2014-06-10 16:07:45 -07001616 mCameraDevice.applySettings(mCameraSettings);
Sascha Haeberlingf8b877c2013-09-09 17:32:48 -07001617
1618 // Update UI based on the new parameters.
Angus Kong6607dae2014-06-10 16:07:45 -07001619 mUI.updateOnScreenIndicators(mCameraSettings);
Michael Kolb8872c232013-01-29 10:33:22 -08001620 }
1621
Doris Liua1ec04a2014-01-13 17:29:40 -08001622 private void updateFocusParameters() {
1623 // Set continuous autofocus. During recording, we use "continuous-video"
1624 // auto focus mode to ensure smooth focusing. Whereas during preview (i.e.
1625 // before recording starts) we use "continuous-picture" auto focus mode
1626 // for faster but slightly jittery focusing.
Angus Kong6607dae2014-06-10 16:07:45 -07001627 Set<CameraCapabilities.FocusMode> supportedFocus = mCameraCapabilities
1628 .getSupportedFocusModes();
Doris Liua1ec04a2014-01-13 17:29:40 -08001629 if (mMediaRecorderRecording) {
Angus Kong6607dae2014-06-10 16:07:45 -07001630 if (mCameraCapabilities.supports(CameraCapabilities.FocusMode.CONTINUOUS_VIDEO)) {
1631 mCameraSettings.setFocusMode(CameraCapabilities.FocusMode.CONTINUOUS_VIDEO);
Angus Kong831347d2014-06-16 16:07:28 -07001632 mFocusManager.overrideFocusMode(CameraCapabilities.FocusMode.CONTINUOUS_VIDEO);
Doris Liua1ec04a2014-01-13 17:29:40 -08001633 } else {
1634 mFocusManager.overrideFocusMode(null);
1635 }
1636 } else {
Senpo Hu11186312014-08-08 13:42:13 -07001637 // FIXME(b/16984793): This is broken. For some reasons, CONTINUOUS_PICTURE is not on
1638 // when preview starts.
Doris Liua1ec04a2014-01-13 17:29:40 -08001639 mFocusManager.overrideFocusMode(null);
Angus Kong6607dae2014-06-10 16:07:45 -07001640 if (mCameraCapabilities.supports(CameraCapabilities.FocusMode.CONTINUOUS_PICTURE)) {
Angus Kong831347d2014-06-16 16:07:28 -07001641 mCameraSettings.setFocusMode(
1642 mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()));
Doris Liua1ec04a2014-01-13 17:29:40 -08001643 if (mFocusAreaSupported) {
Angus Kong6607dae2014-06-10 16:07:45 -07001644 mCameraSettings.setFocusAreas(mFocusManager.getFocusAreas());
Doris Liua1ec04a2014-01-13 17:29:40 -08001645 }
1646 }
1647 }
1648 updateAutoFocusMoveCallback();
1649 }
1650
Michael Kolb8872c232013-01-29 10:33:22 -08001651 @Override
Angus Kong20fad242013-11-11 18:23:46 -08001652 public void resume() {
Spike Sprague51c877c2014-02-18 11:14:12 -08001653 if (isVideoCaptureIntent()) {
1654 mDontResetIntentUiOnResume = mPaused;
1655 }
1656
Angus Kongc4e66562013-11-22 23:03:21 -08001657 mPaused = false;
Angus Kong13e87c42013-11-25 10:02:47 -08001658 installIntentFilter();
Erin Dahlgren667630d2014-04-01 14:03:25 -07001659 mAppController.setShutterEnabled(false);
Sol Boucher2192fba2014-08-19 17:24:07 -07001660 mZoomValue = 1.0f;
Angus Kongc4e66562013-11-22 23:03:21 -08001661
1662 showVideoSnapshotUI(false);
1663
1664 if (!mPreviewing) {
1665 requestCamera(mCameraId);
1666 } else {
1667 // preview already started
Erin Dahlgren667630d2014-04-01 14:03:25 -07001668 mAppController.setShutterEnabled(true);
Angus Kongc4e66562013-11-22 23:03:21 -08001669 }
1670
Doris Liua1ec04a2014-01-13 17:29:40 -08001671 if (mFocusManager != null) {
1672 // If camera is not open when resume is called, focus manager will not
1673 // be initialized yet, in which case it will start listening to
1674 // preview area size change later in the initialization.
1675 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1676 }
Sascha Haeberlingde303232014-02-07 02:30:53 +01001677
Angus Kongc4e66562013-11-22 23:03:21 -08001678 if (mPreviewing) {
1679 mOnResumeTime = SystemClock.uptimeMillis();
Angus Kong13e87c42013-11-25 10:02:47 -08001680 mHandler.sendEmptyMessageDelayed(MSG_CHECK_DISPLAY_ROTATION, 100);
Angus Kongc4e66562013-11-22 23:03:21 -08001681 }
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -08001682 getServices().getMemoryManager().addListener(this);
Angus Kong20fad242013-11-11 18:23:46 -08001683 }
1684
1685 @Override
1686 public void pause() {
Angus Kongc4e66562013-11-22 23:03:21 -08001687 mPaused = true;
Doris Liua1ec04a2014-01-13 17:29:40 -08001688
1689 if (mFocusManager != null) {
1690 // If camera is not open when resume is called, focus manager will not
1691 // be initialized yet, in which case it will start listening to
1692 // preview area size change later in the initialization.
1693 mAppController.removePreviewAreaSizeChangedListener(mFocusManager);
1694 mFocusManager.removeMessages();
1695 }
Angus Kongc4e66562013-11-22 23:03:21 -08001696 if (mMediaRecorderRecording) {
1697 // Camera will be released in onStopVideoRecording.
1698 onStopVideoRecording();
1699 } else {
1700 stopPreview();
1701 closeCamera();
1702 releaseMediaRecorder();
1703 }
1704
1705 closeVideoFileDescriptor();
Angus Kongc4e66562013-11-22 23:03:21 -08001706
1707 if (mReceiver != null) {
1708 mActivity.unregisterReceiver(mReceiver);
1709 mReceiver = null;
1710 }
Angus Kongc4e66562013-11-22 23:03:21 -08001711
Angus Kong13e87c42013-11-25 10:02:47 -08001712 mHandler.removeMessages(MSG_CHECK_DISPLAY_ROTATION);
1713 mHandler.removeMessages(MSG_SWITCH_CAMERA);
1714 mHandler.removeMessages(MSG_SWITCH_CAMERA_START_ANIMATION);
Angus Kongc4e66562013-11-22 23:03:21 -08001715 mPendingSwitchCameraId = -1;
1716 mSwitchingCamera = false;
1717 mPreferenceRead = false;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -08001718 getServices().getMemoryManager().removeListener(this);
Angus Kong20fad242013-11-11 18:23:46 -08001719 }
1720
1721 @Override
1722 public void destroy() {
1723
1724 }
1725
1726 @Override
Angus Kong2f0e4a32013-12-03 10:02:35 -08001727 public void onLayoutOrientationChanged(boolean isLandscape) {
Michael Kolb8872c232013-01-29 10:33:22 -08001728 setDisplayOrientation();
Michael Kolb8872c232013-01-29 10:33:22 -08001729 }
1730
Erin Dahlgrena07e94c2013-12-04 18:44:08 -08001731 // TODO: integrate this into the SettingsManager listeners.
Michael Kolb8872c232013-01-29 10:33:22 -08001732 public void onSharedPreferenceChanged() {
Michael Kolb8872c232013-01-29 10:33:22 -08001733
Michael Kolb8872c232013-01-29 10:33:22 -08001734 }
1735
1736 private void switchCamera() {
Sascha Haeberlingf8b877c2013-09-09 17:32:48 -07001737 if (mPaused) {
1738 return;
1739 }
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001740 SettingsManager settingsManager = mActivity.getSettingsManager();
Michael Kolb8872c232013-01-29 10:33:22 -08001741
1742 Log.d(TAG, "Start to switch camera.");
1743 mCameraId = mPendingSwitchCameraId;
1744 mPendingSwitchCameraId = -1;
Erin Dahlgren6190c362014-06-13 14:12:08 -07001745 settingsManager.set(mAppController.getModuleScope(),
1746 Keys.KEY_CAMERA_ID, mCameraId);
Michael Kolb8872c232013-01-29 10:33:22 -08001747
Doris Liua1ec04a2014-01-13 17:29:40 -08001748 if (mFocusManager != null) {
1749 mFocusManager.removeMessages();
1750 }
Michael Kolb8872c232013-01-29 10:33:22 -08001751 closeCamera();
Angus Kong20fad242013-11-11 18:23:46 -08001752 requestCamera(mCameraId);
Michael Kolb8872c232013-01-29 10:33:22 -08001753
Sascha Haeberling6ccec202014-03-11 09:44:34 -07001754 mMirror = isCameraFrontFacing();
Doris Liua1ec04a2014-01-13 17:29:40 -08001755 if (mFocusManager != null) {
1756 mFocusManager.setMirror(mMirror);
1757 }
1758
Michael Kolb8872c232013-01-29 10:33:22 -08001759 // From onResume
Sol Boucher2192fba2014-08-19 17:24:07 -07001760 mZoomValue = 1.0f;
Doris Liu6827ce22013-03-12 19:24:28 -07001761 mUI.setOrientationIndicator(0, false);
Michael Kolb8872c232013-01-29 10:33:22 -08001762
Doris Liu6432cd62013-06-13 17:20:31 -07001763 // Start switch camera animation. Post a message because
1764 // onFrameAvailable from the old camera may already exist.
Angus Kong13e87c42013-11-25 10:02:47 -08001765 mHandler.sendEmptyMessage(MSG_SWITCH_CAMERA_START_ANIMATION);
Angus Kong6607dae2014-06-10 16:07:45 -07001766 mUI.updateOnScreenIndicators(mCameraSettings);
Michael Kolb8872c232013-01-29 10:33:22 -08001767 }
1768
Michael Kolb8872c232013-01-29 10:33:22 -08001769 private void initializeVideoSnapshot() {
Angus Kong6607dae2014-06-10 16:07:45 -07001770 if (mCameraSettings == null) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001771 return;
1772 }
Michael Kolb8872c232013-01-29 10:33:22 -08001773 }
1774
1775 void showVideoSnapshotUI(boolean enabled) {
Angus Kong6607dae2014-06-10 16:07:45 -07001776 if (mCameraSettings == null) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +01001777 return;
1778 }
Angus Kong88289042014-04-22 16:39:42 -07001779 if (mCameraCapabilities.supports(CameraCapabilities.Feature.VIDEO_SNAPSHOT) &&
1780 !mIsVideoCaptureIntent) {
Doris Liu6432cd62013-06-13 17:20:31 -07001781 if (enabled) {
Sascha Haeberling37f36112013-08-06 14:31:52 -07001782 mUI.animateFlash();
Michael Kolb8872c232013-01-29 10:33:22 -08001783 } else {
Doris Liu6827ce22013-03-12 19:24:28 -07001784 mUI.showPreviewBorder(enabled);
Michael Kolb8872c232013-01-29 10:33:22 -08001785 }
Erin Dahlgren667630d2014-04-01 14:03:25 -07001786 mAppController.setShutterEnabled(!enabled);
Michael Kolb8872c232013-01-29 10:33:22 -08001787 }
1788 }
1789
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001790 /**
1791 * Used to update the flash mode. Video mode can turn on the flash as torch
1792 * mode, which we would like to turn on and off when we switching in and
1793 * out to the preview.
1794 *
1795 * @param enable Whether torch mode can be enabled.
1796 */
1797 private void enableTorchMode(boolean enable) {
Angus Kong6607dae2014-06-10 16:07:45 -07001798 if (mCameraSettings.getCurrentFlashMode() == null) {
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001799 return;
1800 }
1801
Erin Dahlgrenbd3da262013-12-02 11:48:13 -08001802 SettingsManager settingsManager = mActivity.getSettingsManager();
1803
Angus Kong6607dae2014-06-10 16:07:45 -07001804 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
1805 CameraCapabilities.FlashMode flashMode;
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001806 if (enable) {
Erin Dahlgren6190c362014-06-13 14:12:08 -07001807 flashMode = stringifier
1808 .flashModeFromString(settingsManager.getString(mAppController.getCameraScope(),
1809 Keys.KEY_VIDEOCAMERA_FLASH_MODE));
ztenghui7b265a62013-09-09 14:58:44 -07001810 } else {
Angus Kong6607dae2014-06-10 16:07:45 -07001811 flashMode = CameraCapabilities.FlashMode.OFF;
ztenghui7b265a62013-09-09 14:58:44 -07001812 }
Angus Kong6607dae2014-06-10 16:07:45 -07001813 if (mCameraCapabilities.supports(flashMode)) {
1814 mCameraSettings.setFlashMode(flashMode);
1815 }
1816 /* TODO: Find out how to deal with the following code piece:
1817 else {
1818 flashMode = mCameraSettings.getCurrentFlashMode();
ztenghui7b265a62013-09-09 14:58:44 -07001819 if (flashMode == null) {
1820 flashMode = mActivity.getString(
1821 R.string.pref_camera_flashmode_no_flash);
Angus Kongfaaee012013-12-07 00:38:46 -08001822 mParameters.setFlashMode(flashMode);
Michael Kolb8872c232013-01-29 10:33:22 -08001823 }
Angus Kong6607dae2014-06-10 16:07:45 -07001824 }*/
1825 mCameraDevice.applySettings(mCameraSettings);
1826 mUI.updateOnScreenIndicators(mCameraSettings);
ztenghui7b265a62013-09-09 14:58:44 -07001827 }
1828
Michael Kolb8872c232013-01-29 10:33:22 -08001829 @Override
Sascha Haeberling8c1a9222014-02-25 09:38:06 -08001830 public void onPreviewVisibilityChanged(int visibility) {
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001831 if (mPreviewing) {
Sascha Haeberling8c1a9222014-02-25 09:38:06 -08001832 enableTorchMode(visibility == ModuleController.VISIBILITY_VISIBLE);
Erin Dahlgrenfce8a0b2013-12-12 17:36:32 -08001833 }
Erin Dahlgren3044d8c2013-10-10 18:23:45 -07001834 }
1835
Angus Kong9ef99252013-07-18 18:04:19 -07001836 private final class JpegPictureCallback implements CameraPictureCallback {
Michael Kolb8872c232013-01-29 10:33:22 -08001837 Location mLocation;
1838
1839 public JpegPictureCallback(Location loc) {
1840 mLocation = loc;
1841 }
1842
1843 @Override
Angus Kong9ef99252013-07-18 18:04:19 -07001844 public void onPictureTaken(byte [] jpegData, CameraProxy camera) {
Doris Liu057b21a2014-04-25 17:35:27 -07001845 Log.i(TAG, "Video snapshot taken.");
Michael Kolb8872c232013-01-29 10:33:22 -08001846 mSnapshotInProgress = false;
1847 showVideoSnapshotUI(false);
1848 storeImage(jpegData, mLocation);
1849 }
1850 }
1851
1852 private void storeImage(final byte[] data, Location loc) {
1853 long dateTaken = System.currentTimeMillis();
Angus Kongb50b5cb2013-08-09 14:55:20 -07001854 String title = CameraUtil.createJpegName(dateTaken);
Angus Kong0d00a892013-03-26 11:40:40 -07001855 ExifInterface exif = Exif.getExif(data);
1856 int orientation = Exif.getOrientation(exif);
Doris Liu6df2d962013-08-20 16:31:29 -07001857
Erin Dahlgren6190c362014-06-13 14:12:08 -07001858 String flashSetting = mActivity.getSettingsManager()
1859 .getString(mAppController.getCameraScope(), Keys.KEY_VIDEOCAMERA_FLASH_MODE);
1860 Boolean gridLinesOn = Keys.areGridLinesOn(mActivity.getSettingsManager());
Andy Huibers10c58162014-03-29 14:06:54 -07001861 UsageStatistics.instance().photoCaptureDoneEvent(
1862 eventprotos.NavigationChange.Mode.VIDEO_STILL, title + ".jpeg", exif,
Andy Huibersb7c7d9a2014-06-18 22:26:14 -07001863 isCameraFrontFacing(), false, currentZoomValue(), flashSetting, gridLinesOn,
1864 null, null, null);
Andy Huibers10c58162014-03-29 14:06:54 -07001865
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08001866 getServices().getMediaSaver().addImage(
Doris Liu6df2d962013-08-20 16:31:29 -07001867 data, title, dateTaken, loc, orientation,
Angus Kong83a99ae2013-04-17 15:37:07 -07001868 exif, mOnPhotoSavedListener, mContentResolver);
Michael Kolb8872c232013-01-29 10:33:22 -08001869 }
1870
Michael Kolb8872c232013-01-29 10:33:22 -08001871 private String convertOutputFormatToMimeType(int outputFileFormat) {
1872 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1873 return "video/mp4";
1874 }
1875 return "video/3gpp";
1876 }
1877
1878 private String convertOutputFormatToFileExt(int outputFileFormat) {
1879 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1880 return ".mp4";
1881 }
1882 return ".3gp";
1883 }
1884
1885 private void closeVideoFileDescriptor() {
1886 if (mVideoFileDescriptor != null) {
1887 try {
1888 mVideoFileDescriptor.close();
1889 } catch (IOException e) {
1890 Log.e(TAG, "Fail to close fd", e);
1891 }
1892 mVideoFileDescriptor = null;
1893 }
1894 }
1895
Michael Kolb8872c232013-01-29 10:33:22 -08001896 @Override
Angus Kong395ee2d2013-07-15 12:42:41 -07001897 public void onPreviewUIReady() {
1898 startPreview();
1899 }
1900
1901 @Override
1902 public void onPreviewUIDestroyed() {
1903 stopPreview();
1904 }
Angus Kong20fad242013-11-11 18:23:46 -08001905
Doris Liu1dfe7822013-12-12 00:02:08 -08001906 @Override
1907 public void startPreCaptureAnimation() {
1908 mAppController.startPreCaptureAnimation();
1909 }
1910
Angus Kong20fad242013-11-11 18:23:46 -08001911 private void requestCamera(int id) {
1912 mActivity.getCameraProvider().requestCamera(id);
1913 }
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -08001914
1915 @Override
1916 public void onMemoryStateChanged(int state) {
Erin Dahlgren667630d2014-04-01 14:03:25 -07001917 mAppController.setShutterEnabled(state == MemoryManager.STATE_OK);
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -08001918 }
1919
1920 @Override
1921 public void onLowMemory() {
1922 // Not much we can do in the video module.
1923 }
1924
Doris Liua1ec04a2014-01-13 17:29:40 -08001925 /***********************FocusOverlayManager Listener****************************/
1926 @Override
1927 public void autoFocus() {
1928 mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
1929 }
1930
1931 @Override
1932 public void cancelAutoFocus() {
1933 mCameraDevice.cancelAutoFocus();
1934 setFocusParameters();
1935 }
1936
1937 @Override
1938 public boolean capture() {
1939 return false;
1940 }
1941
1942 @Override
1943 public void startFaceDetection() {
1944
1945 }
1946
1947 @Override
1948 public void stopFaceDetection() {
1949
1950 }
1951
1952 @Override
1953 public void setFocusParameters() {
1954 updateFocusParameters();
Angus Kong6607dae2014-06-10 16:07:45 -07001955 mCameraDevice.applySettings(mCameraSettings);
Doris Liua1ec04a2014-01-13 17:29:40 -08001956 }
1957
Michael Kolb8872c232013-01-29 10:33:22 -08001958}