• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.camera;
18 
19 import android.annotation.TargetApi;
20 import android.app.Activity;
21 import android.content.ActivityNotFoundException;
22 import android.content.BroadcastReceiver;
23 import android.content.ContentResolver;
24 import android.content.ContentValues;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.SharedPreferences.Editor;
29 import android.content.res.Configuration;
30 import android.graphics.Bitmap;
31 import android.graphics.SurfaceTexture;
32 import android.hardware.Camera.CameraInfo;
33 import android.hardware.Camera.Parameters;
34 import android.hardware.Camera.PictureCallback;
35 import android.hardware.Camera.Size;
36 import android.location.Location;
37 import android.media.CamcorderProfile;
38 import android.media.CameraProfile;
39 import android.media.MediaRecorder;
40 import android.net.Uri;
41 import android.os.Build;
42 import android.os.Bundle;
43 import android.os.Handler;
44 import android.os.Message;
45 import android.os.ParcelFileDescriptor;
46 import android.os.SystemClock;
47 import android.provider.MediaStore;
48 import android.provider.MediaStore.Video;
49 import android.util.Log;
50 import android.view.Gravity;
51 import android.view.KeyEvent;
52 import android.view.LayoutInflater;
53 import android.view.MotionEvent;
54 import android.view.OrientationEventListener;
55 import android.view.SurfaceHolder;
56 import android.view.View;
57 import android.view.View.OnClickListener;
58 import android.view.ViewGroup;
59 import android.view.WindowManager;
60 import android.widget.FrameLayout;
61 import android.widget.FrameLayout.LayoutParams;
62 import android.widget.ImageView;
63 import android.widget.LinearLayout;
64 import android.widget.TextView;
65 import android.widget.Toast;
66 
67 import com.android.camera.ui.AbstractSettingPopup;
68 import com.android.camera.ui.PieRenderer;
69 import com.android.camera.ui.PopupManager;
70 import com.android.camera.ui.PreviewSurfaceView;
71 import com.android.camera.ui.RenderOverlay;
72 import com.android.camera.ui.Rotatable;
73 import com.android.camera.ui.RotateImageView;
74 import com.android.camera.ui.RotateLayout;
75 import com.android.camera.ui.RotateTextToast;
76 import com.android.camera.ui.TwoStateImageView;
77 import com.android.camera.ui.ZoomRenderer;
78 import com.android.gallery3d.common.ApiHelper;
79 import com.android.gallery3d.util.AccessibilityUtils;
80 
81 import java.io.File;
82 import java.io.IOException;
83 import java.text.SimpleDateFormat;
84 import java.util.Date;
85 import java.util.Iterator;
86 import java.util.List;
87 
88 public class VideoModule implements CameraModule,
89     CameraPreference.OnPreferenceChangedListener,
90     ShutterButton.OnShutterButtonListener,
91     MediaRecorder.OnErrorListener,
92     MediaRecorder.OnInfoListener,
93     EffectsRecorder.EffectsListener,
94     PieRenderer.PieListener {
95 
96     private static final String TAG = "CAM_VideoModule";
97 
98     // We number the request code from 1000 to avoid collision with Gallery.
99     private static final int REQUEST_EFFECT_BACKDROPPER = 1000;
100 
101     private static final int CHECK_DISPLAY_ROTATION = 3;
102     private static final int CLEAR_SCREEN_DELAY = 4;
103     private static final int UPDATE_RECORD_TIME = 5;
104     private static final int ENABLE_SHUTTER_BUTTON = 6;
105     private static final int SHOW_TAP_TO_SNAPSHOT_TOAST = 7;
106     private static final int SWITCH_CAMERA = 8;
107     private static final int SWITCH_CAMERA_START_ANIMATION = 9;
108     private static final int HIDE_SURFACE_VIEW = 10;
109 
110     private static final int SCREEN_DELAY = 2 * 60 * 1000;
111 
112     private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
113 
114     /**
115      * An unpublished intent flag requesting to start recording straight away
116      * and return as soon as recording is stopped.
117      * TODO: consider publishing by moving into MediaStore.
118      */
119     private static final String EXTRA_QUICK_CAPTURE =
120             "android.intent.extra.quickCapture";
121 
122     private static final int MIN_THUMB_SIZE = 64;
123     // module fields
124     private CameraActivity mActivity;
125     private View mRootView;
126     private boolean mPaused;
127     private int mCameraId;
128     private Parameters mParameters;
129 
130     private boolean mSnapshotInProgress = false;
131 
132     private static final String EFFECT_BG_FROM_GALLERY = "gallery";
133 
134     private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
135 
136     private ComboPreferences mPreferences;
137     private PreferenceGroup mPreferenceGroup;
138 
139     private PreviewFrameLayout mPreviewFrameLayout;
140     private boolean mSurfaceViewReady;
141     private SurfaceHolder.Callback mSurfaceViewCallback;
142     private PreviewSurfaceView mPreviewSurfaceView;
143     private CameraScreenNail.OnFrameDrawnListener mFrameDrawnListener;
144     private View mReviewControl;
145 
146     // An review image having same size as preview. It is displayed when
147     // recording is stopped in capture intent.
148     private ImageView mReviewImage;
149     private Rotatable mReviewCancelButton;
150     private Rotatable mReviewDoneButton;
151     private RotateImageView mReviewPlayButton;
152     private ShutterButton mShutterButton;
153     private TextView mRecordingTimeView;
154     private RotateLayout mBgLearningMessageRotater;
155     private View mBgLearningMessageFrame;
156     private LinearLayout mLabelsLinearLayout;
157 
158     private boolean mIsVideoCaptureIntent;
159     private boolean mQuickCapture;
160 
161     private MediaRecorder mMediaRecorder;
162     private EffectsRecorder mEffectsRecorder;
163     private boolean mEffectsDisplayResult;
164 
165     private int mEffectType = EffectsRecorder.EFFECT_NONE;
166     private Object mEffectParameter = null;
167     private String mEffectUriFromGallery = null;
168     private String mPrefVideoEffectDefault;
169     private boolean mResetEffect = true;
170 
171     private boolean mSwitchingCamera;
172     private boolean mMediaRecorderRecording = false;
173     private long mRecordingStartTime;
174     private boolean mRecordingTimeCountsDown = false;
175     private RotateLayout mRecordingTimeRect;
176     private long mOnResumeTime;
177     // The video file that the hardware camera is about to record into
178     // (or is recording into.)
179     private String mVideoFilename;
180     private ParcelFileDescriptor mVideoFileDescriptor;
181 
182     // The video file that has already been recorded, and that is being
183     // examined by the user.
184     private String mCurrentVideoFilename;
185     private Uri mCurrentVideoUri;
186     private ContentValues mCurrentVideoValues;
187 
188     private CamcorderProfile mProfile;
189 
190     // The video duration limit. 0 menas no limit.
191     private int mMaxVideoDurationInMs;
192 
193     // Time Lapse parameters.
194     private boolean mCaptureTimeLapse = false;
195     // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
196     private int mTimeBetweenTimeLapseFrameCaptureMs = 0;
197     private View mTimeLapseLabel;
198 
199     private int mDesiredPreviewWidth;
200     private int mDesiredPreviewHeight;
201 
202     boolean mPreviewing = false; // True if preview is started.
203     // The display rotation in degrees. This is only valid when mPreviewing is
204     // true.
205     private int mDisplayRotation;
206     private int mCameraDisplayOrientation;
207 
208     private ContentResolver mContentResolver;
209 
210     private LocationManager mLocationManager;
211 
212     private VideoNamer mVideoNamer;
213 
214     private RenderOverlay mRenderOverlay;
215     private PieRenderer mPieRenderer;
216 
217     private VideoController mVideoControl;
218     private AbstractSettingPopup mPopup;
219     private int mPendingSwitchCameraId;
220 
221     private ZoomRenderer mZoomRenderer;
222 
223     private PreviewGestures mGestures;
224     private View mMenu;
225     private View mBlocker;
226     private View mOnScreenIndicators;
227     private ImageView mFlashIndicator;
228 
229     private final Handler mHandler = new MainHandler();
230 
231     // The degrees of the device rotated clockwise from its natural orientation.
232     private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
233 
234     private int mZoomValue;  // The current zoom value.
235     private int mZoomMax;
236     private List<Integer> mZoomRatios;
237     private boolean mRestoreFlash;  // This is used to check if we need to restore the flash
238                                     // status when going back from gallery.
239 
240     protected class CameraOpenThread extends Thread {
241         @Override
run()242         public void run() {
243             openCamera();
244         }
245     }
246 
openCamera()247     private void openCamera() {
248         try {
249             mActivity.mCameraDevice = Util.openCamera(mActivity, mCameraId);
250             mParameters = mActivity.mCameraDevice.getParameters();
251         } catch (CameraHardwareException e) {
252             mActivity.mOpenCameraFail = true;
253         } catch (CameraDisabledException e) {
254             mActivity.mCameraDisabled = true;
255         }
256     }
257 
258     // This Handler is used to post message back onto the main thread of the
259     // application
260     private class MainHandler extends Handler {
261         @Override
handleMessage(Message msg)262         public void handleMessage(Message msg) {
263             switch (msg.what) {
264 
265                 case ENABLE_SHUTTER_BUTTON:
266                     mShutterButton.setEnabled(true);
267                     break;
268 
269                 case CLEAR_SCREEN_DELAY: {
270                     mActivity.getWindow().clearFlags(
271                             WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
272                     break;
273                 }
274 
275                 case UPDATE_RECORD_TIME: {
276                     updateRecordingTime();
277                     break;
278                 }
279 
280                 case CHECK_DISPLAY_ROTATION: {
281                     // Restart the preview if display rotation has changed.
282                     // Sometimes this happens when the device is held upside
283                     // down and camera app is opened. Rotation animation will
284                     // take some time and the rotation value we have got may be
285                     // wrong. Framework does not have a callback for this now.
286                     if ((Util.getDisplayRotation(mActivity) != mDisplayRotation)
287                             && !mMediaRecorderRecording && !mSwitchingCamera) {
288                         startPreview();
289                     }
290                     if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
291                         mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
292                     }
293                     break;
294                 }
295 
296                 case SHOW_TAP_TO_SNAPSHOT_TOAST: {
297                     showTapToSnapshotToast();
298                     break;
299                 }
300 
301                 case SWITCH_CAMERA: {
302                     switchCamera();
303                     break;
304                 }
305 
306                 case SWITCH_CAMERA_START_ANIMATION: {
307                     ((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
308 
309                     // Enable all camera controls.
310                     mSwitchingCamera = false;
311                     break;
312                 }
313 
314                 case HIDE_SURFACE_VIEW: {
315                     mPreviewSurfaceView.setVisibility(View.GONE);
316                     break;
317                 }
318 
319                 default:
320                     Log.v(TAG, "Unhandled message: " + msg.what);
321                     break;
322             }
323         }
324     }
325 
326     private BroadcastReceiver mReceiver = null;
327 
328     private class MyBroadcastReceiver extends BroadcastReceiver {
329         @Override
onReceive(Context context, Intent intent)330         public void onReceive(Context context, Intent intent) {
331             String action = intent.getAction();
332             if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
333                 stopVideoRecording();
334             } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
335                 Toast.makeText(mActivity,
336                         mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
337             }
338         }
339     }
340 
createName(long dateTaken)341     private String createName(long dateTaken) {
342         Date date = new Date(dateTaken);
343         SimpleDateFormat dateFormat = new SimpleDateFormat(
344                 mActivity.getString(R.string.video_file_name_format));
345 
346         return dateFormat.format(date);
347     }
348 
getPreferredCameraId(ComboPreferences preferences)349     private int getPreferredCameraId(ComboPreferences preferences) {
350         int intentCameraId = Util.getCameraFacingIntentExtras(mActivity);
351         if (intentCameraId != -1) {
352             // Testing purpose. Launch a specific camera through the intent
353             // extras.
354             return intentCameraId;
355         } else {
356             return CameraSettings.readPreferredCameraId(preferences);
357         }
358     }
359 
initializeSurfaceView()360     private void initializeSurfaceView() {
361         mPreviewSurfaceView = (PreviewSurfaceView) mRootView.findViewById(R.id.preview_surface_view);
362         if (!ApiHelper.HAS_SURFACE_TEXTURE) {  // API level < 11
363             if (mSurfaceViewCallback == null) {
364                 mSurfaceViewCallback = new SurfaceViewCallback();
365             }
366             mPreviewSurfaceView.getHolder().addCallback(mSurfaceViewCallback);
367             mPreviewSurfaceView.setVisibility(View.VISIBLE);
368         } else if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {  // API level < 16
369             if (mSurfaceViewCallback == null) {
370                 mSurfaceViewCallback = new SurfaceViewCallback();
371                 mFrameDrawnListener = new CameraScreenNail.OnFrameDrawnListener() {
372                     @Override
373                     public void onFrameDrawn(CameraScreenNail c) {
374                         mHandler.sendEmptyMessage(HIDE_SURFACE_VIEW);
375                     }
376                 };
377             }
378             mPreviewSurfaceView.getHolder().addCallback(mSurfaceViewCallback);
379         }
380     }
381 
initializeOverlay()382     private void initializeOverlay() {
383         mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
384         if (mPieRenderer == null) {
385             mPieRenderer = new PieRenderer(mActivity);
386             mVideoControl = new VideoController(mActivity, this, mPieRenderer);
387             mVideoControl.setListener(this);
388             mPieRenderer.setPieListener(this);
389         }
390         mRenderOverlay.addRenderer(mPieRenderer);
391         if (mZoomRenderer == null) {
392             mZoomRenderer = new ZoomRenderer(mActivity);
393         }
394         mRenderOverlay.addRenderer(mZoomRenderer);
395         if (mGestures == null) {
396             mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer);
397         }
398         mGestures.setRenderOverlay(mRenderOverlay);
399         mGestures.clearTouchReceivers();
400         mGestures.addTouchReceiver(mMenu);
401         mGestures.addTouchReceiver(mBlocker);
402 
403         if (isVideoCaptureIntent()) {
404             if (mReviewCancelButton != null) {
405                 mGestures.addTouchReceiver((View) mReviewCancelButton);
406             }
407             if (mReviewDoneButton != null) {
408                 mGestures.addTouchReceiver((View) mReviewDoneButton);
409             }
410             if (mReviewPlayButton != null) {
411                 mGestures.addTouchReceiver((View) mReviewPlayButton);
412             }
413         }
414     }
415 
416     @Override
init(CameraActivity activity, View root, boolean reuseScreenNail)417     public void init(CameraActivity activity, View root, boolean reuseScreenNail) {
418         mActivity = activity;
419         mRootView = root;
420         mPreferences = new ComboPreferences(mActivity);
421         CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
422         mCameraId = getPreferredCameraId(mPreferences);
423 
424         mPreferences.setLocalId(mActivity, mCameraId);
425         CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
426 
427         mActivity.mNumberOfCameras = CameraHolder.instance().getNumberOfCameras();
428         mPrefVideoEffectDefault = mActivity.getString(R.string.pref_video_effect_default);
429         resetEffect();
430 
431         /*
432          * To reduce startup time, we start the preview in another thread.
433          * We make sure the preview is started at the end of onCreate.
434          */
435         CameraOpenThread cameraOpenThread = new CameraOpenThread();
436         cameraOpenThread.start();
437 
438         mContentResolver = mActivity.getContentResolver();
439 
440         mActivity.getLayoutInflater().inflate(R.layout.video_module, (ViewGroup) mRootView);
441 
442         // Surface texture is from camera screen nail and startPreview needs it.
443         // This must be done before startPreview.
444         mIsVideoCaptureIntent = isVideoCaptureIntent();
445         if (reuseScreenNail) {
446             mActivity.reuseCameraScreenNail(!mIsVideoCaptureIntent);
447         } else {
448             mActivity.createCameraScreenNail(!mIsVideoCaptureIntent);
449         }
450         initializeSurfaceView();
451 
452         // Make sure camera device is opened.
453         try {
454             cameraOpenThread.join();
455             if (mActivity.mOpenCameraFail) {
456                 Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
457                 return;
458             } else if (mActivity.mCameraDisabled) {
459                 Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
460                 return;
461             }
462         } catch (InterruptedException ex) {
463             // ignore
464         }
465 
466         readVideoPreferences();
467         new Thread(new Runnable() {
468             @Override
469             public void run() {
470                 startPreview();
471             }
472         }).start();
473 
474         initializeControlByIntent();
475         initializeOverlay();
476         initializeMiscControls();
477 
478         mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
479         mLocationManager = new LocationManager(mActivity, null);
480 
481         setOrientationIndicator(0, false);
482         setDisplayOrientation();
483 
484         showTimeLapseUI(mCaptureTimeLapse);
485         initializeVideoSnapshot();
486         resizeForPreviewAspectRatio();
487 
488         initializeVideoControl();
489         mPendingSwitchCameraId = -1;
490         updateOnScreenIndicators();
491     }
492 
493     @Override
onStop()494     public void onStop() {}
495 
loadCameraPreferences()496     private void loadCameraPreferences() {
497         CameraSettings settings = new CameraSettings(mActivity, mParameters,
498                 mCameraId, CameraHolder.instance().getCameraInfo());
499         // Remove the video quality preference setting when the quality is given in the intent.
500         mPreferenceGroup = filterPreferenceScreenByIntent(
501                 settings.getPreferenceGroup(R.xml.video_preferences));
502     }
503 
504     @Override
collapseCameraControls()505     public boolean collapseCameraControls() {
506         boolean ret = false;
507         if (mPopup != null) {
508             dismissPopup(false);
509             ret = true;
510         }
511         return ret;
512     }
513 
removeTopLevelPopup()514     public boolean removeTopLevelPopup() {
515         if (mPopup != null) {
516             dismissPopup(true);
517             return true;
518         }
519         return false;
520     }
521 
enableCameraControls(boolean enable)522     private void enableCameraControls(boolean enable) {
523         if (mGestures != null) {
524             mGestures.setZoomOnly(!enable);
525         }
526         if (mPieRenderer != null && mPieRenderer.showsItems()) {
527             mPieRenderer.hide();
528         }
529     }
530 
initializeVideoControl()531     private void initializeVideoControl() {
532         loadCameraPreferences();
533         mVideoControl.initialize(mPreferenceGroup);
534         if (effectsActive()) {
535             mVideoControl.overrideSettings(
536                     CameraSettings.KEY_VIDEO_QUALITY,
537                     Integer.toString(getLowVideoQuality()));
538         }
539     }
540 
541     @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
getLowVideoQuality()542     private static int getLowVideoQuality() {
543         if (ApiHelper.HAS_FINE_RESOLUTION_QUALITY_LEVELS) {
544             return CamcorderProfile.QUALITY_480P;
545         } else {
546             return CamcorderProfile.QUALITY_LOW;
547         }
548     }
549 
550 
551     @Override
onOrientationChanged(int orientation)552     public void onOrientationChanged(int orientation) {
553         // We keep the last known orientation. So if the user first orient
554         // the camera then point the camera to floor or sky, we still have
555         // the correct orientation.
556         if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return;
557         int newOrientation = Util.roundOrientation(orientation, mOrientation);
558 
559         if (mOrientation != newOrientation) {
560             mOrientation = newOrientation;
561             // The input of effects recorder is affected by
562             // android.hardware.Camera.setDisplayOrientation. Its value only
563             // compensates the camera orientation (no Display.getRotation).
564             // So the orientation hint here should only consider sensor
565             // orientation.
566             if (effectsActive()) {
567                 mEffectsRecorder.setOrientationHint(mOrientation);
568             }
569         }
570 
571         // Show the toast after getting the first orientation changed.
572         if (mHandler.hasMessages(SHOW_TAP_TO_SNAPSHOT_TOAST)) {
573             mHandler.removeMessages(SHOW_TAP_TO_SNAPSHOT_TOAST);
574             showTapToSnapshotToast();
575         }
576     }
577 
setOrientationIndicator(int orientation, boolean animation)578     private void setOrientationIndicator(int orientation, boolean animation) {
579         Rotatable[] indicators = {
580                 mBgLearningMessageRotater,
581                 mReviewDoneButton, mReviewPlayButton};
582         for (Rotatable indicator : indicators) {
583             if (indicator != null) indicator.setOrientation(orientation, animation);
584         }
585         if (mGestures != null) {
586             mGestures.setOrientation(orientation);
587         }
588 
589         // We change the orientation of the review cancel button only for tablet
590         // UI because there's a label along with the X icon. For phone UI, we
591         // don't change the orientation because there's only a symmetrical X
592         // icon.
593         if (mReviewCancelButton instanceof RotateLayout) {
594             mReviewCancelButton.setOrientation(orientation, animation);
595         }
596 
597         // We change the orientation of the linearlayout only for phone UI because when in portrait
598         // the width is not enough.
599         if (mLabelsLinearLayout != null) {
600             if (((orientation / 90) & 1) == 0) {
601                 mLabelsLinearLayout.setOrientation(LinearLayout.VERTICAL);
602             } else {
603                 mLabelsLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
604             }
605         }
606         mRecordingTimeRect.setOrientation(0, animation);
607     }
608 
startPlayVideoActivity()609     private void startPlayVideoActivity() {
610         Intent intent = new Intent(Intent.ACTION_VIEW);
611         intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
612         try {
613             mActivity.startActivity(intent);
614         } catch (ActivityNotFoundException ex) {
615             Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
616         }
617     }
618 
619     @OnClickAttr
onReviewPlayClicked(View v)620     public void onReviewPlayClicked(View v) {
621         startPlayVideoActivity();
622     }
623 
624     @OnClickAttr
onReviewDoneClicked(View v)625     public void onReviewDoneClicked(View v) {
626         doReturnToCaller(true);
627     }
628 
629     @OnClickAttr
onReviewCancelClicked(View v)630     public void onReviewCancelClicked(View v) {
631         stopVideoRecording();
632         doReturnToCaller(false);
633     }
634 
onStopVideoRecording()635     private void onStopVideoRecording() {
636         mEffectsDisplayResult = true;
637         boolean recordFail = stopVideoRecording();
638         if (mIsVideoCaptureIntent) {
639             if (!effectsActive()) {
640                 if (mQuickCapture) {
641                     doReturnToCaller(!recordFail);
642                 } else if (!recordFail) {
643                     showAlert();
644                 }
645             }
646         } else if (!recordFail){
647             // Start capture animation.
648             if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
649                 // The capture animation is disabled on ICS because we use SurfaceView
650                 // for preview during recording. When the recording is done, we switch
651                 // back to use SurfaceTexture for preview and we need to stop then start
652                 // the preview. This will cause the preview flicker since the preview
653                 // will not be continuous for a short period of time.
654                 ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation);
655             }
656         }
657     }
658 
onProtectiveCurtainClick(View v)659     public void onProtectiveCurtainClick(View v) {
660         // Consume clicks
661     }
662 
663     @Override
onShutterButtonClick()664     public void onShutterButtonClick() {
665         if (collapseCameraControls() || mSwitchingCamera) return;
666 
667         boolean stop = mMediaRecorderRecording;
668 
669         if (stop) {
670             onStopVideoRecording();
671         } else {
672             startVideoRecording();
673         }
674         mShutterButton.setEnabled(false);
675 
676         // Keep the shutter button disabled when in video capture intent
677         // mode and recording is stopped. It'll be re-enabled when
678         // re-take button is clicked.
679         if (!(mIsVideoCaptureIntent && stop)) {
680             mHandler.sendEmptyMessageDelayed(
681                     ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
682         }
683     }
684 
685     @Override
onShutterButtonFocus(boolean pressed)686     public void onShutterButtonFocus(boolean pressed) {
687         // Do nothing (everything happens in onShutterButtonClick).
688     }
689 
readVideoPreferences()690     private void readVideoPreferences() {
691         // The preference stores values from ListPreference and is thus string type for all values.
692         // We need to convert it to int manually.
693         String defaultQuality = CameraSettings.getDefaultVideoQuality(mCameraId,
694                 mActivity.getResources().getString(R.string.pref_video_quality_default));
695         String videoQuality =
696                 mPreferences.getString(CameraSettings.KEY_VIDEO_QUALITY,
697                         defaultQuality);
698         int quality = Integer.valueOf(videoQuality);
699 
700         // Set video quality.
701         Intent intent = mActivity.getIntent();
702         if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
703             int extraVideoQuality =
704                     intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
705             if (extraVideoQuality > 0) {
706                 quality = CamcorderProfile.QUALITY_HIGH;
707             } else {  // 0 is mms.
708                 quality = CamcorderProfile.QUALITY_LOW;
709             }
710         }
711 
712         // Set video duration limit. The limit is read from the preference,
713         // unless it is specified in the intent.
714         if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
715             int seconds =
716                     intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
717             mMaxVideoDurationInMs = 1000 * seconds;
718         } else {
719             mMaxVideoDurationInMs = CameraSettings.getMaxVideoDuration(mActivity);
720         }
721 
722         // Set effect
723         mEffectType = CameraSettings.readEffectType(mPreferences);
724         if (mEffectType != EffectsRecorder.EFFECT_NONE) {
725             mEffectParameter = CameraSettings.readEffectParameter(mPreferences);
726             // Set quality to be no higher than 480p.
727             CamcorderProfile profile = CamcorderProfile.get(mCameraId, quality);
728             if (profile.videoFrameHeight > 480) {
729                 quality = getLowVideoQuality();
730             }
731         } else {
732             mEffectParameter = null;
733         }
734         // Read time lapse recording interval.
735         if (ApiHelper.HAS_TIME_LAPSE_RECORDING) {
736             String frameIntervalStr = mPreferences.getString(
737                     CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
738                     mActivity.getString(R.string.pref_video_time_lapse_frame_interval_default));
739             mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
740             mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
741         }
742         // TODO: This should be checked instead directly +1000.
743         if (mCaptureTimeLapse) quality += 1000;
744         mProfile = CamcorderProfile.get(mCameraId, quality);
745         getDesiredPreviewSize();
746     }
747 
writeDefaultEffectToPrefs()748     private void writeDefaultEffectToPrefs()  {
749         ComboPreferences.Editor editor = mPreferences.edit();
750         editor.putString(CameraSettings.KEY_VIDEO_EFFECT,
751                 mActivity.getString(R.string.pref_video_effect_default));
752         editor.apply();
753     }
754 
755     @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
getDesiredPreviewSize()756     private void getDesiredPreviewSize() {
757         mParameters = mActivity.mCameraDevice.getParameters();
758         if (ApiHelper.HAS_GET_SUPPORTED_VIDEO_SIZE) {
759             if (mParameters.getSupportedVideoSizes() == null || effectsActive()) {
760                 mDesiredPreviewWidth = mProfile.videoFrameWidth;
761                 mDesiredPreviewHeight = mProfile.videoFrameHeight;
762             } else {  // Driver supports separates outputs for preview and video.
763                 List<Size> sizes = mParameters.getSupportedPreviewSizes();
764                 Size preferred = mParameters.getPreferredPreviewSizeForVideo();
765                 int product = preferred.width * preferred.height;
766                 Iterator<Size> it = sizes.iterator();
767                 // Remove the preview sizes that are not preferred.
768                 while (it.hasNext()) {
769                     Size size = it.next();
770                     if (size.width * size.height > product) {
771                         it.remove();
772                     }
773                 }
774                 Size optimalSize = Util.getOptimalPreviewSize(mActivity, sizes,
775                         (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
776                 mDesiredPreviewWidth = optimalSize.width;
777                 mDesiredPreviewHeight = optimalSize.height;
778             }
779         } else {
780             mDesiredPreviewWidth = mProfile.videoFrameWidth;
781             mDesiredPreviewHeight = mProfile.videoFrameHeight;
782         }
783         Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
784                 ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
785     }
786 
resizeForPreviewAspectRatio()787     private void resizeForPreviewAspectRatio() {
788         mPreviewFrameLayout.setAspectRatio(
789                 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
790     }
791 
792     @Override
installIntentFilter()793     public void installIntentFilter() {
794         // install an intent filter to receive SD card related events.
795         IntentFilter intentFilter =
796                 new IntentFilter(Intent.ACTION_MEDIA_EJECT);
797         intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
798         intentFilter.addDataScheme("file");
799         mReceiver = new MyBroadcastReceiver();
800         mActivity.registerReceiver(mReceiver, intentFilter);
801     }
802 
803     @Override
onResumeBeforeSuper()804     public void onResumeBeforeSuper() {
805         mPaused = false;
806     }
807 
808     @Override
onResumeAfterSuper()809     public void onResumeAfterSuper() {
810         if (mActivity.mOpenCameraFail || mActivity.mCameraDisabled)
811             return;
812 
813         mZoomValue = 0;
814 
815         showVideoSnapshotUI(false);
816 
817 
818         if (!mPreviewing) {
819             if (resetEffect()) {
820                 mBgLearningMessageFrame.setVisibility(View.GONE);
821             }
822             openCamera();
823             if (mActivity.mOpenCameraFail) {
824                 Util.showErrorAndFinish(mActivity,
825                         R.string.cannot_connect_camera);
826                 return;
827             } else if (mActivity.mCameraDisabled) {
828                 Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
829                 return;
830             }
831             readVideoPreferences();
832             resizeForPreviewAspectRatio();
833             new Thread(new Runnable() {
834                 @Override
835                 public void run() {
836                     startPreview();
837                 }
838             }).start();
839         }
840 
841         // Initializing it here after the preview is started.
842         initializeZoom();
843 
844         keepScreenOnAwhile();
845 
846         // Initialize location service.
847         boolean recordLocation = RecordLocationPreference.get(mPreferences,
848                 mContentResolver);
849         mLocationManager.recordLocation(recordLocation);
850 
851         if (mPreviewing) {
852             mOnResumeTime = SystemClock.uptimeMillis();
853             mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
854         }
855         // Dismiss open menu if exists.
856         PopupManager.getInstance(mActivity).notifyShowPopup(null);
857 
858         mVideoNamer = new VideoNamer();
859     }
860 
setDisplayOrientation()861     private void setDisplayOrientation() {
862         mDisplayRotation = Util.getDisplayRotation(mActivity);
863         if (ApiHelper.HAS_SURFACE_TEXTURE) {
864             // The display rotation is handled by gallery.
865             mCameraDisplayOrientation = Util.getDisplayOrientation(0, mCameraId);
866         } else {
867             // We need to consider display rotation ourselves.
868             mCameraDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
869         }
870         // GLRoot also uses the DisplayRotation, and needs to be told to layout to update
871         mActivity.getGLRoot().requestLayoutContentPane();
872     }
873 
startPreview()874     private void startPreview() {
875         Log.v(TAG, "startPreview");
876 
877         mActivity.mCameraDevice.setErrorCallback(mErrorCallback);
878         if (mPreviewing == true) {
879             stopPreview();
880             if (effectsActive() && mEffectsRecorder != null) {
881                 mEffectsRecorder.release();
882                 mEffectsRecorder = null;
883             }
884         }
885 
886         mPreviewing = true;
887 
888         setDisplayOrientation();
889         mActivity.mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
890         setCameraParameters();
891 
892         try {
893             if (!effectsActive()) {
894                 if (ApiHelper.HAS_SURFACE_TEXTURE) {
895                     SurfaceTexture surfaceTexture = ((CameraScreenNail) mActivity.mCameraScreenNail)
896                             .getSurfaceTexture();
897                     if (surfaceTexture == null) {
898                         return; // The texture has been destroyed (pause, etc)
899                     }
900                     mActivity.mCameraDevice.setPreviewTextureAsync(surfaceTexture);
901                 } else {
902                     mActivity.mCameraDevice.setPreviewDisplayAsync(mPreviewSurfaceView.getHolder());
903                 }
904                 mActivity.mCameraDevice.startPreviewAsync();
905             } else {
906                 initializeEffectsPreview();
907                 mEffectsRecorder.startPreview();
908             }
909         } catch (Throwable ex) {
910             closeCamera();
911             throw new RuntimeException("startPreview failed", ex);
912         } finally {
913             mActivity.runOnUiThread(new Runnable() {
914                 @Override
915                 public void run() {
916                     if (mActivity.mOpenCameraFail) {
917                         Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
918                     } else if (mActivity.mCameraDisabled) {
919                         Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
920                     }
921                 }
922             });
923         }
924     }
925 
stopPreview()926     private void stopPreview() {
927         mActivity.mCameraDevice.stopPreview();
928         mPreviewing = false;
929     }
930 
931     // Closing the effects out. Will shut down the effects graph.
closeEffects()932     private void closeEffects() {
933         Log.v(TAG, "Closing effects");
934         mEffectType = EffectsRecorder.EFFECT_NONE;
935         if (mEffectsRecorder == null) {
936             Log.d(TAG, "Effects are already closed. Nothing to do");
937             return;
938         }
939         // This call can handle the case where the camera is already released
940         // after the recording has been stopped.
941         mEffectsRecorder.release();
942         mEffectsRecorder = null;
943     }
944 
945     // By default, we want to close the effects as well with the camera.
closeCamera()946     private void closeCamera() {
947         closeCamera(true);
948     }
949 
950     // In certain cases, when the effects are active, we may want to shutdown
951     // only the camera related parts, and handle closing the effects in the
952     // effectsUpdate callback.
953     // For example, in onPause, we want to make the camera available to
954     // outside world immediately, however, want to wait till the effects
955     // callback to shut down the effects. In such a case, we just disconnect
956     // the effects from the camera by calling disconnectCamera. That way
957     // the effects can handle that when shutting down.
958     //
959     // @param closeEffectsAlso - indicates whether we want to close the
960     // effects also along with the camera.
closeCamera(boolean closeEffectsAlso)961     private void closeCamera(boolean closeEffectsAlso) {
962         Log.v(TAG, "closeCamera");
963         if (mActivity.mCameraDevice == null) {
964             Log.d(TAG, "already stopped.");
965             return;
966         }
967 
968         if (mEffectsRecorder != null) {
969             // Disconnect the camera from effects so that camera is ready to
970             // be released to the outside world.
971             mEffectsRecorder.disconnectCamera();
972         }
973         if (closeEffectsAlso) closeEffects();
974         mActivity.mCameraDevice.setZoomChangeListener(null);
975         mActivity.mCameraDevice.setErrorCallback(null);
976         CameraHolder.instance().release();
977         mActivity.mCameraDevice = null;
978         mPreviewing = false;
979         mSnapshotInProgress = false;
980     }
981 
releasePreviewResources()982     private void releasePreviewResources() {
983         if (ApiHelper.HAS_SURFACE_TEXTURE) {
984             CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
985             screenNail.releaseSurfaceTexture();
986             if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
987                 mHandler.removeMessages(HIDE_SURFACE_VIEW);
988                 mPreviewSurfaceView.setVisibility(View.GONE);
989             }
990         }
991     }
992 
993     @Override
onPauseBeforeSuper()994     public void onPauseBeforeSuper() {
995         mPaused = true;
996 
997         if (mMediaRecorderRecording) {
998             // Camera will be released in onStopVideoRecording.
999             onStopVideoRecording();
1000         } else {
1001             closeCamera();
1002             if (!effectsActive()) releaseMediaRecorder();
1003         }
1004         if (effectsActive()) {
1005             // If the effects are active, make sure we tell the graph that the
1006             // surfacetexture is not valid anymore. Disconnect the graph from
1007             // the display. This should be done before releasing the surface
1008             // texture.
1009             mEffectsRecorder.disconnectDisplay();
1010         } else {
1011             // Close the file descriptor and clear the video namer only if the
1012             // effects are not active. If effects are active, we need to wait
1013             // till we get the callback from the Effects that the graph is done
1014             // recording. That also needs a change in the stopVideoRecording()
1015             // call to not call closeCamera if the effects are active, because
1016             // that will close down the effects are well, thus making this if
1017             // condition invalid.
1018             closeVideoFileDescriptor();
1019             clearVideoNamer();
1020         }
1021 
1022         releasePreviewResources();
1023 
1024         if (mReceiver != null) {
1025             mActivity.unregisterReceiver(mReceiver);
1026             mReceiver = null;
1027         }
1028         resetScreenOn();
1029 
1030         if (mLocationManager != null) mLocationManager.recordLocation(false);
1031 
1032         mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
1033         mHandler.removeMessages(SWITCH_CAMERA);
1034         mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION);
1035         mPendingSwitchCameraId = -1;
1036         mSwitchingCamera = false;
1037         // Call onPause after stopping video recording. So the camera can be
1038         // released as soon as possible.
1039     }
1040 
1041     @Override
onPauseAfterSuper()1042     public void onPauseAfterSuper() {
1043     }
1044 
1045     @Override
onUserInteraction()1046     public void onUserInteraction() {
1047         if (!mMediaRecorderRecording && !mActivity.isFinishing()) {
1048             keepScreenOnAwhile();
1049         }
1050     }
1051 
1052     @Override
onBackPressed()1053     public boolean onBackPressed() {
1054         if (mPaused) return true;
1055         if (mMediaRecorderRecording) {
1056             onStopVideoRecording();
1057             return true;
1058         } else if (mPieRenderer != null && mPieRenderer.showsItems()) {
1059             mPieRenderer.hide();
1060             return true;
1061         } else {
1062             return removeTopLevelPopup();
1063         }
1064     }
1065 
1066     @Override
onKeyDown(int keyCode, KeyEvent event)1067     public boolean onKeyDown(int keyCode, KeyEvent event) {
1068         // Do not handle any key if the activity is paused.
1069         if (mPaused) {
1070             return true;
1071         }
1072 
1073         switch (keyCode) {
1074             case KeyEvent.KEYCODE_CAMERA:
1075                 if (event.getRepeatCount() == 0) {
1076                     mShutterButton.performClick();
1077                     return true;
1078                 }
1079                 break;
1080             case KeyEvent.KEYCODE_DPAD_CENTER:
1081                 if (event.getRepeatCount() == 0) {
1082                     mShutterButton.performClick();
1083                     return true;
1084                 }
1085                 break;
1086             case KeyEvent.KEYCODE_MENU:
1087                 if (mMediaRecorderRecording) return true;
1088                 break;
1089         }
1090         return false;
1091     }
1092 
1093     @Override
onKeyUp(int keyCode, KeyEvent event)1094     public boolean onKeyUp(int keyCode, KeyEvent event) {
1095         switch (keyCode) {
1096             case KeyEvent.KEYCODE_CAMERA:
1097                 mShutterButton.setPressed(false);
1098                 return true;
1099         }
1100         return false;
1101     }
1102 
isVideoCaptureIntent()1103     private boolean isVideoCaptureIntent() {
1104         String action = mActivity.getIntent().getAction();
1105         return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
1106     }
1107 
doReturnToCaller(boolean valid)1108     private void doReturnToCaller(boolean valid) {
1109         Intent resultIntent = new Intent();
1110         int resultCode;
1111         if (valid) {
1112             resultCode = Activity.RESULT_OK;
1113             resultIntent.setData(mCurrentVideoUri);
1114         } else {
1115             resultCode = Activity.RESULT_CANCELED;
1116         }
1117         mActivity.setResultEx(resultCode, resultIntent);
1118         mActivity.finish();
1119     }
1120 
cleanupEmptyFile()1121     private void cleanupEmptyFile() {
1122         if (mVideoFilename != null) {
1123             File f = new File(mVideoFilename);
1124             if (f.length() == 0 && f.delete()) {
1125                 Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
1126                 mVideoFilename = null;
1127             }
1128         }
1129     }
1130 
setupMediaRecorderPreviewDisplay()1131     private void setupMediaRecorderPreviewDisplay() {
1132         // Nothing to do here if using SurfaceTexture.
1133         if (!ApiHelper.HAS_SURFACE_TEXTURE) {
1134             mMediaRecorder.setPreviewDisplay(mPreviewSurfaceView.getHolder().getSurface());
1135         } else if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
1136             // We stop the preview here before unlocking the device because we
1137             // need to change the SurfaceTexture to SurfaceView for preview.
1138             stopPreview();
1139             mActivity.mCameraDevice.setPreviewDisplayAsync(mPreviewSurfaceView.getHolder());
1140             // The orientation for SurfaceTexture is different from that for
1141             // SurfaceView. For SurfaceTexture we don't need to consider the
1142             // display rotation. Just consider the sensor's orientation and we
1143             // will set the orientation correctly when showing the texture.
1144             // Gallery will handle the orientation for the preview. For
1145             // SurfaceView we will have to take everything into account so the
1146             // display rotation is considered.
1147             mActivity.mCameraDevice.setDisplayOrientation(
1148                     Util.getDisplayOrientation(mDisplayRotation, mCameraId));
1149             mActivity.mCameraDevice.startPreviewAsync();
1150             mPreviewing = true;
1151             mMediaRecorder.setPreviewDisplay(mPreviewSurfaceView.getHolder().getSurface());
1152         }
1153     }
1154 
1155     // Prepares media recorder.
initializeRecorder()1156     private void initializeRecorder() {
1157         Log.v(TAG, "initializeRecorder");
1158         // If the mCameraDevice is null, then this activity is going to finish
1159         if (mActivity.mCameraDevice == null) return;
1160 
1161         if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING && ApiHelper.HAS_SURFACE_TEXTURE) {
1162             // Set the SurfaceView to visible so the surface gets created.
1163             // surfaceCreated() is called immediately when the visibility is
1164             // changed to visible. Thus, mSurfaceViewReady should become true
1165             // right after calling setVisibility().
1166             mPreviewSurfaceView.setVisibility(View.VISIBLE);
1167             if (!mSurfaceViewReady) return;
1168         }
1169 
1170         Intent intent = mActivity.getIntent();
1171         Bundle myExtras = intent.getExtras();
1172 
1173         long requestedSizeLimit = 0;
1174         closeVideoFileDescriptor();
1175         if (mIsVideoCaptureIntent && myExtras != null) {
1176             Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1177             if (saveUri != null) {
1178                 try {
1179                     mVideoFileDescriptor =
1180                             mContentResolver.openFileDescriptor(saveUri, "rw");
1181                     mCurrentVideoUri = saveUri;
1182                 } catch (java.io.FileNotFoundException ex) {
1183                     // invalid uri
1184                     Log.e(TAG, ex.toString());
1185                 }
1186             }
1187             requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1188         }
1189         mMediaRecorder = new MediaRecorder();
1190 
1191         setupMediaRecorderPreviewDisplay();
1192         // Unlock the camera object before passing it to media recorder.
1193         mActivity.mCameraDevice.unlock();
1194         mMediaRecorder.setCamera(mActivity.mCameraDevice.getCamera());
1195         if (!mCaptureTimeLapse) {
1196             mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
1197         }
1198         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
1199         mMediaRecorder.setProfile(mProfile);
1200         mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
1201         if (mCaptureTimeLapse) {
1202             double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs;
1203             setCaptureRate(mMediaRecorder, fps);
1204         }
1205 
1206         setRecordLocation();
1207 
1208         // Set output file.
1209         // Try Uri in the intent first. If it doesn't exist, use our own
1210         // instead.
1211         if (mVideoFileDescriptor != null) {
1212             mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1213         } else {
1214             generateVideoFilename(mProfile.fileFormat);
1215             mMediaRecorder.setOutputFile(mVideoFilename);
1216         }
1217 
1218         // Set maximum file size.
1219         long maxFileSize = mActivity.getStorageSpace() - Storage.LOW_STORAGE_THRESHOLD;
1220         if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1221             maxFileSize = requestedSizeLimit;
1222         }
1223 
1224         try {
1225             mMediaRecorder.setMaxFileSize(maxFileSize);
1226         } catch (RuntimeException exception) {
1227             // We are going to ignore failure of setMaxFileSize here, as
1228             // a) The composer selected may simply not support it, or
1229             // b) The underlying media framework may not handle 64-bit range
1230             // on the size restriction.
1231         }
1232 
1233         // See android.hardware.Camera.Parameters.setRotation for
1234         // documentation.
1235         // Note that mOrientation here is the device orientation, which is the opposite of
1236         // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
1237         // which is the orientation the graphics need to rotate in order to render correctly.
1238         int rotation = 0;
1239         if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1240             CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1241             if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
1242                 rotation = (info.orientation - mOrientation + 360) % 360;
1243             } else {  // back-facing camera
1244                 rotation = (info.orientation + mOrientation) % 360;
1245             }
1246         }
1247         mMediaRecorder.setOrientationHint(rotation);
1248 
1249         try {
1250             mMediaRecorder.prepare();
1251         } catch (IOException e) {
1252             Log.e(TAG, "prepare failed for " + mVideoFilename, e);
1253             releaseMediaRecorder();
1254             throw new RuntimeException(e);
1255         }
1256 
1257         mMediaRecorder.setOnErrorListener(this);
1258         mMediaRecorder.setOnInfoListener(this);
1259     }
1260 
1261     @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
setCaptureRate(MediaRecorder recorder, double fps)1262     private static void setCaptureRate(MediaRecorder recorder, double fps) {
1263         recorder.setCaptureRate(fps);
1264     }
1265 
1266     @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
setRecordLocation()1267     private void setRecordLocation() {
1268         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
1269             Location loc = mLocationManager.getCurrentLocation();
1270             if (loc != null) {
1271                 mMediaRecorder.setLocation((float) loc.getLatitude(),
1272                         (float) loc.getLongitude());
1273             }
1274         }
1275     }
1276 
initializeEffectsPreview()1277     private void initializeEffectsPreview() {
1278         Log.v(TAG, "initializeEffectsPreview");
1279         // If the mCameraDevice is null, then this activity is going to finish
1280         if (mActivity.mCameraDevice == null) return;
1281 
1282         boolean inLandscape = (mActivity.getResources().getConfiguration().orientation
1283                 == Configuration.ORIENTATION_LANDSCAPE);
1284 
1285         CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1286 
1287         mEffectsDisplayResult = false;
1288         mEffectsRecorder = new EffectsRecorder(mActivity);
1289 
1290         // TODO: Confirm none of the following need to go to initializeEffectsRecording()
1291         // and none of these change even when the preview is not refreshed.
1292         mEffectsRecorder.setCameraDisplayOrientation(mCameraDisplayOrientation);
1293         mEffectsRecorder.setCamera(mActivity.mCameraDevice);
1294         mEffectsRecorder.setCameraFacing(info.facing);
1295         mEffectsRecorder.setProfile(mProfile);
1296         mEffectsRecorder.setEffectsListener(this);
1297         mEffectsRecorder.setOnInfoListener(this);
1298         mEffectsRecorder.setOnErrorListener(this);
1299 
1300         // The input of effects recorder is affected by
1301         // android.hardware.Camera.setDisplayOrientation. Its value only
1302         // compensates the camera orientation (no Display.getRotation). So the
1303         // orientation hint here should only consider sensor orientation.
1304         int orientation = 0;
1305         if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1306             orientation = mOrientation;
1307         }
1308         mEffectsRecorder.setOrientationHint(orientation);
1309 
1310         CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
1311         mEffectsRecorder.setPreviewSurfaceTexture(screenNail.getSurfaceTexture(),
1312                 screenNail.getWidth(), screenNail.getHeight());
1313 
1314         if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER &&
1315                 ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) {
1316             mEffectsRecorder.setEffect(mEffectType, mEffectUriFromGallery);
1317         } else {
1318             mEffectsRecorder.setEffect(mEffectType, mEffectParameter);
1319         }
1320     }
1321 
initializeEffectsRecording()1322     private void initializeEffectsRecording() {
1323         Log.v(TAG, "initializeEffectsRecording");
1324 
1325         Intent intent = mActivity.getIntent();
1326         Bundle myExtras = intent.getExtras();
1327 
1328         long requestedSizeLimit = 0;
1329         closeVideoFileDescriptor();
1330         if (mIsVideoCaptureIntent && myExtras != null) {
1331             Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1332             if (saveUri != null) {
1333                 try {
1334                     mVideoFileDescriptor =
1335                             mContentResolver.openFileDescriptor(saveUri, "rw");
1336                     mCurrentVideoUri = saveUri;
1337                 } catch (java.io.FileNotFoundException ex) {
1338                     // invalid uri
1339                     Log.e(TAG, ex.toString());
1340                 }
1341             }
1342             requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1343         }
1344 
1345         mEffectsRecorder.setProfile(mProfile);
1346         // important to set the capture rate to zero if not timelapsed, since the
1347         // effectsrecorder object does not get created again for each recording
1348         // session
1349         if (mCaptureTimeLapse) {
1350             mEffectsRecorder.setCaptureRate((1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs));
1351         } else {
1352             mEffectsRecorder.setCaptureRate(0);
1353         }
1354 
1355         // Set output file
1356         if (mVideoFileDescriptor != null) {
1357             mEffectsRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1358         } else {
1359             generateVideoFilename(mProfile.fileFormat);
1360             mEffectsRecorder.setOutputFile(mVideoFilename);
1361         }
1362 
1363         // Set maximum file size.
1364         long maxFileSize = mActivity.getStorageSpace() - Storage.LOW_STORAGE_THRESHOLD;
1365         if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1366             maxFileSize = requestedSizeLimit;
1367         }
1368         mEffectsRecorder.setMaxFileSize(maxFileSize);
1369         mEffectsRecorder.setMaxDuration(mMaxVideoDurationInMs);
1370     }
1371 
1372 
releaseMediaRecorder()1373     private void releaseMediaRecorder() {
1374         Log.v(TAG, "Releasing media recorder.");
1375         if (mMediaRecorder != null) {
1376             cleanupEmptyFile();
1377             mMediaRecorder.reset();
1378             mMediaRecorder.release();
1379             mMediaRecorder = null;
1380         }
1381         mVideoFilename = null;
1382     }
1383 
releaseEffectsRecorder()1384     private void releaseEffectsRecorder() {
1385         Log.v(TAG, "Releasing effects recorder.");
1386         if (mEffectsRecorder != null) {
1387             cleanupEmptyFile();
1388             mEffectsRecorder.release();
1389             mEffectsRecorder = null;
1390         }
1391         mEffectType = EffectsRecorder.EFFECT_NONE;
1392         mVideoFilename = null;
1393     }
1394 
generateVideoFilename(int outputFileFormat)1395     private void generateVideoFilename(int outputFileFormat) {
1396         long dateTaken = System.currentTimeMillis();
1397         String title = createName(dateTaken);
1398         // Used when emailing.
1399         String filename = title + convertOutputFormatToFileExt(outputFileFormat);
1400         String mime = convertOutputFormatToMimeType(outputFileFormat);
1401         String path = Storage.DIRECTORY + '/' + filename;
1402         String tmpPath = path + ".tmp";
1403         mCurrentVideoValues = new ContentValues(7);
1404         mCurrentVideoValues.put(Video.Media.TITLE, title);
1405         mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
1406         mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
1407         mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
1408         mCurrentVideoValues.put(Video.Media.DATA, path);
1409         mCurrentVideoValues.put(Video.Media.RESOLUTION,
1410                 Integer.toString(mProfile.videoFrameWidth) + "x" +
1411                 Integer.toString(mProfile.videoFrameHeight));
1412         Location loc = mLocationManager.getCurrentLocation();
1413         if (loc != null) {
1414             mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
1415             mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
1416         }
1417         mVideoNamer.prepareUri(mContentResolver, mCurrentVideoValues);
1418         mVideoFilename = tmpPath;
1419         Log.v(TAG, "New video filename: " + mVideoFilename);
1420     }
1421 
addVideoToMediaStore()1422     private boolean addVideoToMediaStore() {
1423         boolean fail = false;
1424         if (mVideoFileDescriptor == null) {
1425             mCurrentVideoValues.put(Video.Media.SIZE,
1426                     new File(mCurrentVideoFilename).length());
1427             long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
1428             if (duration > 0) {
1429                 if (mCaptureTimeLapse) {
1430                     duration = getTimeLapseVideoLength(duration);
1431                 }
1432                 mCurrentVideoValues.put(Video.Media.DURATION, duration);
1433             } else {
1434                 Log.w(TAG, "Video duration <= 0 : " + duration);
1435             }
1436             try {
1437                 mCurrentVideoUri = mVideoNamer.getUri();
1438                 mActivity.addSecureAlbumItemIfNeeded(true, mCurrentVideoUri);
1439 
1440                 // Rename the video file to the final name. This avoids other
1441                 // apps reading incomplete data.  We need to do it after the
1442                 // above mVideoNamer.getUri() call, so we are certain that the
1443                 // previous insert to MediaProvider is completed.
1444                 String finalName = mCurrentVideoValues.getAsString(
1445                         Video.Media.DATA);
1446                 if (new File(mCurrentVideoFilename).renameTo(new File(finalName))) {
1447                     mCurrentVideoFilename = finalName;
1448                 }
1449 
1450                 mContentResolver.update(mCurrentVideoUri, mCurrentVideoValues
1451                         , null, null);
1452                 mActivity.sendBroadcast(new Intent(Util.ACTION_NEW_VIDEO,
1453                         mCurrentVideoUri));
1454             } catch (Exception e) {
1455                 // We failed to insert into the database. This can happen if
1456                 // the SD card is unmounted.
1457                 Log.e(TAG, "failed to add video to media store", e);
1458                 mCurrentVideoUri = null;
1459                 mCurrentVideoFilename = null;
1460                 fail = true;
1461             } finally {
1462                 Log.v(TAG, "Current video URI: " + mCurrentVideoUri);
1463             }
1464         }
1465         mCurrentVideoValues = null;
1466         return fail;
1467     }
1468 
deleteCurrentVideo()1469     private void deleteCurrentVideo() {
1470         // Remove the video and the uri if the uri is not passed in by intent.
1471         if (mCurrentVideoFilename != null) {
1472             deleteVideoFile(mCurrentVideoFilename);
1473             mCurrentVideoFilename = null;
1474             if (mCurrentVideoUri != null) {
1475                 mContentResolver.delete(mCurrentVideoUri, null, null);
1476                 mCurrentVideoUri = null;
1477             }
1478         }
1479         mActivity.updateStorageSpaceAndHint();
1480     }
1481 
deleteVideoFile(String fileName)1482     private void deleteVideoFile(String fileName) {
1483         Log.v(TAG, "Deleting video " + fileName);
1484         File f = new File(fileName);
1485         if (!f.delete()) {
1486             Log.v(TAG, "Could not delete " + fileName);
1487         }
1488     }
1489 
filterPreferenceScreenByIntent( PreferenceGroup screen)1490     private PreferenceGroup filterPreferenceScreenByIntent(
1491             PreferenceGroup screen) {
1492         Intent intent = mActivity.getIntent();
1493         if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
1494             CameraSettings.removePreferenceFromScreen(screen,
1495                     CameraSettings.KEY_VIDEO_QUALITY);
1496         }
1497 
1498         if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
1499             CameraSettings.removePreferenceFromScreen(screen,
1500                     CameraSettings.KEY_VIDEO_QUALITY);
1501         }
1502         return screen;
1503     }
1504 
1505     // from MediaRecorder.OnErrorListener
1506     @Override
onError(MediaRecorder mr, int what, int extra)1507     public void onError(MediaRecorder mr, int what, int extra) {
1508         Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
1509         if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1510             // We may have run out of space on the sdcard.
1511             stopVideoRecording();
1512             mActivity.updateStorageSpaceAndHint();
1513         }
1514     }
1515 
1516     // from MediaRecorder.OnInfoListener
1517     @Override
onInfo(MediaRecorder mr, int what, int extra)1518     public void onInfo(MediaRecorder mr, int what, int extra) {
1519         if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
1520             if (mMediaRecorderRecording) onStopVideoRecording();
1521         } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
1522             if (mMediaRecorderRecording) onStopVideoRecording();
1523 
1524             // Show the toast.
1525             Toast.makeText(mActivity, R.string.video_reach_size_limit,
1526                     Toast.LENGTH_LONG).show();
1527         }
1528     }
1529 
1530     /*
1531      * Make sure we're not recording music playing in the background, ask the
1532      * MediaPlaybackService to pause playback.
1533      */
pauseAudioPlayback()1534     private void pauseAudioPlayback() {
1535         // Shamelessly copied from MediaPlaybackService.java, which
1536         // should be public, but isn't.
1537         Intent i = new Intent("com.android.music.musicservicecommand");
1538         i.putExtra("command", "pause");
1539 
1540         mActivity.sendBroadcast(i);
1541     }
1542 
1543     // For testing.
isRecording()1544     public boolean isRecording() {
1545         return mMediaRecorderRecording;
1546     }
1547 
startVideoRecording()1548     private void startVideoRecording() {
1549         Log.v(TAG, "startVideoRecording");
1550         mActivity.setSwipingEnabled(false);
1551 
1552         mActivity.updateStorageSpaceAndHint();
1553         if (mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) {
1554             Log.v(TAG, "Storage issue, ignore the start request");
1555             return;
1556         }
1557 
1558         mCurrentVideoUri = null;
1559         if (effectsActive()) {
1560             initializeEffectsRecording();
1561             if (mEffectsRecorder == null) {
1562                 Log.e(TAG, "Fail to initialize effect recorder");
1563                 return;
1564             }
1565         } else {
1566             initializeRecorder();
1567             if (mMediaRecorder == null) {
1568                 Log.e(TAG, "Fail to initialize media recorder");
1569                 return;
1570             }
1571         }
1572 
1573         pauseAudioPlayback();
1574 
1575         if (effectsActive()) {
1576             try {
1577                 mEffectsRecorder.startRecording();
1578             } catch (RuntimeException e) {
1579                 Log.e(TAG, "Could not start effects recorder. ", e);
1580                 releaseEffectsRecorder();
1581                 return;
1582             }
1583         } else {
1584             try {
1585                 mMediaRecorder.start(); // Recording is now started
1586             } catch (RuntimeException e) {
1587                 Log.e(TAG, "Could not start media recorder. ", e);
1588                 releaseMediaRecorder();
1589                 // If start fails, frameworks will not lock the camera for us.
1590                 mActivity.mCameraDevice.lock();
1591                 return;
1592             }
1593         }
1594 
1595         // Make sure the video recording has started before announcing
1596         // this in accessibility.
1597         AccessibilityUtils.makeAnnouncement(mShutterButton,
1598                 mActivity.getString(R.string.video_recording_started));
1599 
1600         // The parameters may have been changed by MediaRecorder upon starting
1601         // recording. We need to alter the parameters if we support camcorder
1602         // zoom. To reduce latency when setting the parameters during zoom, we
1603         // update mParameters here once.
1604         if (ApiHelper.HAS_ZOOM_WHEN_RECORDING) {
1605             mParameters = mActivity.mCameraDevice.getParameters();
1606         }
1607 
1608         enableCameraControls(false);
1609 
1610         mMediaRecorderRecording = true;
1611         mActivity.getOrientationManager().lockOrientation();
1612         mRecordingStartTime = SystemClock.uptimeMillis();
1613         showRecordingUI(true);
1614 
1615         updateRecordingTime();
1616         keepScreenOn();
1617     }
1618 
showRecordingUI(boolean recording)1619     private void showRecordingUI(boolean recording) {
1620         mMenu.setVisibility(recording ? View.GONE : View.VISIBLE);
1621         mOnScreenIndicators.setVisibility(recording ? View.GONE : View.VISIBLE);
1622         if (recording) {
1623             mShutterButton.setImageResource(R.drawable.btn_shutter_video_recording);
1624             mActivity.hideSwitcher();
1625             mRecordingTimeView.setText("");
1626             mRecordingTimeView.setVisibility(View.VISIBLE);
1627             if (mReviewControl != null) mReviewControl.setVisibility(View.GONE);
1628             // The camera is not allowed to be accessed in older api levels during
1629             // recording. It is therefore necessary to hide the zoom UI on older
1630             // platforms.
1631             // See the documentation of android.media.MediaRecorder.start() for
1632             // further explanation.
1633             if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING
1634                     && mParameters.isZoomSupported()) {
1635                 // TODO: disable zoom UI here.
1636             }
1637         } else {
1638             mShutterButton.setImageResource(R.drawable.btn_new_shutter_video);
1639             mActivity.showSwitcher();
1640             mRecordingTimeView.setVisibility(View.GONE);
1641             if (mReviewControl != null) mReviewControl.setVisibility(View.VISIBLE);
1642             if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING
1643                     && mParameters.isZoomSupported()) {
1644                 // TODO: enable zoom UI here.
1645             }
1646         }
1647     }
1648 
showAlert()1649     private void showAlert() {
1650         Bitmap bitmap = null;
1651         if (mVideoFileDescriptor != null) {
1652             bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
1653                     mPreviewFrameLayout.getWidth());
1654         } else if (mCurrentVideoFilename != null) {
1655             bitmap = Thumbnail.createVideoThumbnailBitmap(mCurrentVideoFilename,
1656                     mPreviewFrameLayout.getWidth());
1657         }
1658         if (bitmap != null) {
1659             // MetadataRetriever already rotates the thumbnail. We should rotate
1660             // it to match the UI orientation (and mirror if it is front-facing camera).
1661             CameraInfo[] info = CameraHolder.instance().getCameraInfo();
1662             boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT);
1663             bitmap = Util.rotateAndMirror(bitmap, 0, mirror);
1664             mReviewImage.setImageBitmap(bitmap);
1665             mReviewImage.setVisibility(View.VISIBLE);
1666         }
1667 
1668         Util.fadeOut(mShutterButton);
1669 
1670         Util.fadeIn((View) mReviewDoneButton);
1671         Util.fadeIn(mReviewPlayButton);
1672         mMenu.setVisibility(View.GONE);
1673         mOnScreenIndicators.setVisibility(View.GONE);
1674         enableCameraControls(false);
1675 
1676         showTimeLapseUI(false);
1677     }
1678 
hideAlert()1679     private void hideAlert() {
1680         mReviewImage.setVisibility(View.GONE);
1681         mShutterButton.setEnabled(true);
1682         mMenu.setVisibility(View.VISIBLE);
1683         mOnScreenIndicators.setVisibility(View.VISIBLE);
1684         enableCameraControls(true);
1685 
1686         Util.fadeOut((View) mReviewDoneButton);
1687         Util.fadeOut(mReviewPlayButton);
1688 
1689         Util.fadeIn(mShutterButton);
1690 
1691         if (mCaptureTimeLapse) {
1692             showTimeLapseUI(true);
1693         }
1694     }
1695 
stopVideoRecording()1696     private boolean stopVideoRecording() {
1697         Log.v(TAG, "stopVideoRecording");
1698         mActivity.setSwipingEnabled(true);
1699         mActivity.showSwitcher();
1700 
1701         boolean fail = false;
1702         if (mMediaRecorderRecording) {
1703             boolean shouldAddToMediaStoreNow = false;
1704 
1705             try {
1706                 if (effectsActive()) {
1707                     // This is asynchronous, so we can't add to media store now because thumbnail
1708                     // may not be ready. In such case addVideoToMediaStore is called later
1709                     // through a callback from the MediaEncoderFilter to EffectsRecorder,
1710                     // and then to the VideoModule.
1711                     mEffectsRecorder.stopRecording();
1712                 } else {
1713                     mMediaRecorder.setOnErrorListener(null);
1714                     mMediaRecorder.setOnInfoListener(null);
1715                     mMediaRecorder.stop();
1716                     shouldAddToMediaStoreNow = true;
1717                 }
1718                 mCurrentVideoFilename = mVideoFilename;
1719                 Log.v(TAG, "stopVideoRecording: Setting current video filename: "
1720                         + mCurrentVideoFilename);
1721                 AccessibilityUtils.makeAnnouncement(mShutterButton,
1722                         mActivity.getString(R.string.video_recording_stopped));
1723             } catch (RuntimeException e) {
1724                 Log.e(TAG, "stop fail",  e);
1725                 if (mVideoFilename != null) deleteVideoFile(mVideoFilename);
1726                 fail = true;
1727             }
1728             mMediaRecorderRecording = false;
1729             mActivity.getOrientationManager().unlockOrientation();
1730 
1731             // If the activity is paused, this means activity is interrupted
1732             // during recording. Release the camera as soon as possible because
1733             // face unlock or other applications may need to use the camera.
1734             // However, if the effects are active, then we can only release the
1735             // camera and cannot release the effects recorder since that will
1736             // stop the graph. It is possible to separate out the Camera release
1737             // part and the effects release part. However, the effects recorder
1738             // does hold on to the camera, hence, it needs to be "disconnected"
1739             // from the camera in the closeCamera call.
1740             if (mPaused) {
1741                 // Closing only the camera part if effects active. Effects will
1742                 // be closed in the callback from effects.
1743                 boolean closeEffects = !effectsActive();
1744                 closeCamera(closeEffects);
1745             }
1746 
1747             showRecordingUI(false);
1748             if (!mIsVideoCaptureIntent) {
1749                 enableCameraControls(true);
1750             }
1751             // The orientation was fixed during video recording. Now make it
1752             // reflect the device orientation as video recording is stopped.
1753             setOrientationIndicator(0, true);
1754             keepScreenOnAwhile();
1755             if (shouldAddToMediaStoreNow) {
1756                 if (addVideoToMediaStore()) fail = true;
1757             }
1758         }
1759         // always release media recorder if no effects running
1760         if (!effectsActive()) {
1761             releaseMediaRecorder();
1762             if (!mPaused) {
1763                 mActivity.mCameraDevice.lock();
1764                 if (ApiHelper.HAS_SURFACE_TEXTURE &&
1765                     !ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
1766                     stopPreview();
1767                     // Switch back to use SurfaceTexture for preview.
1768                     ((CameraScreenNail) mActivity.mCameraScreenNail).setOneTimeOnFrameDrawnListener(
1769                             mFrameDrawnListener);
1770                     startPreview();
1771                 }
1772             }
1773         }
1774         // Update the parameters here because the parameters might have been altered
1775         // by MediaRecorder.
1776         if (!mPaused) mParameters = mActivity.mCameraDevice.getParameters();
1777         return fail;
1778     }
1779 
resetScreenOn()1780     private void resetScreenOn() {
1781         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1782         mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1783     }
1784 
keepScreenOnAwhile()1785     private void keepScreenOnAwhile() {
1786         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1787         mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1788         mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1789     }
1790 
keepScreenOn()1791     private void keepScreenOn() {
1792         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1793         mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1794     }
1795 
millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds)1796     private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
1797         long seconds = milliSeconds / 1000; // round down to compute seconds
1798         long minutes = seconds / 60;
1799         long hours = minutes / 60;
1800         long remainderMinutes = minutes - (hours * 60);
1801         long remainderSeconds = seconds - (minutes * 60);
1802 
1803         StringBuilder timeStringBuilder = new StringBuilder();
1804 
1805         // Hours
1806         if (hours > 0) {
1807             if (hours < 10) {
1808                 timeStringBuilder.append('0');
1809             }
1810             timeStringBuilder.append(hours);
1811 
1812             timeStringBuilder.append(':');
1813         }
1814 
1815         // Minutes
1816         if (remainderMinutes < 10) {
1817             timeStringBuilder.append('0');
1818         }
1819         timeStringBuilder.append(remainderMinutes);
1820         timeStringBuilder.append(':');
1821 
1822         // Seconds
1823         if (remainderSeconds < 10) {
1824             timeStringBuilder.append('0');
1825         }
1826         timeStringBuilder.append(remainderSeconds);
1827 
1828         // Centi seconds
1829         if (displayCentiSeconds) {
1830             timeStringBuilder.append('.');
1831             long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
1832             if (remainderCentiSeconds < 10) {
1833                 timeStringBuilder.append('0');
1834             }
1835             timeStringBuilder.append(remainderCentiSeconds);
1836         }
1837 
1838         return timeStringBuilder.toString();
1839     }
1840 
getTimeLapseVideoLength(long deltaMs)1841     private long getTimeLapseVideoLength(long deltaMs) {
1842         // For better approximation calculate fractional number of frames captured.
1843         // This will update the video time at a higher resolution.
1844         double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
1845         return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
1846     }
1847 
updateRecordingTime()1848     private void updateRecordingTime() {
1849         if (!mMediaRecorderRecording) {
1850             return;
1851         }
1852         long now = SystemClock.uptimeMillis();
1853         long delta = now - mRecordingStartTime;
1854 
1855         // Starting a minute before reaching the max duration
1856         // limit, we'll countdown the remaining time instead.
1857         boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1858                 && delta >= mMaxVideoDurationInMs - 60000);
1859 
1860         long deltaAdjusted = delta;
1861         if (countdownRemainingTime) {
1862             deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
1863         }
1864         String text;
1865 
1866         long targetNextUpdateDelay;
1867         if (!mCaptureTimeLapse) {
1868             text = millisecondToTimeString(deltaAdjusted, false);
1869             targetNextUpdateDelay = 1000;
1870         } else {
1871             // The length of time lapse video is different from the length
1872             // of the actual wall clock time elapsed. Display the video length
1873             // only in format hh:mm:ss.dd, where dd are the centi seconds.
1874             text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
1875             targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
1876         }
1877 
1878         mRecordingTimeView.setText(text);
1879 
1880         if (mRecordingTimeCountsDown != countdownRemainingTime) {
1881             // Avoid setting the color on every update, do it only
1882             // when it needs changing.
1883             mRecordingTimeCountsDown = countdownRemainingTime;
1884 
1885             int color = mActivity.getResources().getColor(countdownRemainingTime
1886                     ? R.color.recording_time_remaining_text
1887                     : R.color.recording_time_elapsed_text);
1888 
1889             mRecordingTimeView.setTextColor(color);
1890         }
1891 
1892         long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
1893         mHandler.sendEmptyMessageDelayed(
1894                 UPDATE_RECORD_TIME, actualNextUpdateDelay);
1895     }
1896 
isSupported(String value, List<String> supported)1897     private static boolean isSupported(String value, List<String> supported) {
1898         return supported == null ? false : supported.indexOf(value) >= 0;
1899     }
1900 
1901     @SuppressWarnings("deprecation")
setCameraParameters()1902     private void setCameraParameters() {
1903         mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
1904         mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
1905 
1906         // Set flash mode.
1907         String flashMode;
1908         if (mActivity.mShowCameraAppView) {
1909             flashMode = mPreferences.getString(
1910                     CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
1911                     mActivity.getString(R.string.pref_camera_video_flashmode_default));
1912         } else {
1913             flashMode = Parameters.FLASH_MODE_OFF;
1914         }
1915         List<String> supportedFlash = mParameters.getSupportedFlashModes();
1916         if (isSupported(flashMode, supportedFlash)) {
1917             mParameters.setFlashMode(flashMode);
1918         } else {
1919             flashMode = mParameters.getFlashMode();
1920             if (flashMode == null) {
1921                 flashMode = mActivity.getString(
1922                         R.string.pref_camera_flashmode_no_flash);
1923             }
1924         }
1925 
1926         // Set white balance parameter.
1927         String whiteBalance = mPreferences.getString(
1928                 CameraSettings.KEY_WHITE_BALANCE,
1929                 mActivity.getString(R.string.pref_camera_whitebalance_default));
1930         if (isSupported(whiteBalance,
1931                 mParameters.getSupportedWhiteBalance())) {
1932             mParameters.setWhiteBalance(whiteBalance);
1933         } else {
1934             whiteBalance = mParameters.getWhiteBalance();
1935             if (whiteBalance == null) {
1936                 whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1937             }
1938         }
1939 
1940         // Set zoom.
1941         if (mParameters.isZoomSupported()) {
1942             mParameters.setZoom(mZoomValue);
1943         }
1944 
1945         // Set continuous autofocus.
1946         List<String> supportedFocus = mParameters.getSupportedFocusModes();
1947         if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
1948             mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
1949         }
1950 
1951         mParameters.set(Util.RECORDING_HINT, Util.TRUE);
1952 
1953         // Enable video stabilization. Convenience methods not available in API
1954         // level <= 14
1955         String vstabSupported = mParameters.get("video-stabilization-supported");
1956         if ("true".equals(vstabSupported)) {
1957             mParameters.set("video-stabilization", "true");
1958         }
1959 
1960         // Set picture size.
1961         // The logic here is different from the logic in still-mode camera.
1962         // There we determine the preview size based on the picture size, but
1963         // here we determine the picture size based on the preview size.
1964         List<Size> supported = mParameters.getSupportedPictureSizes();
1965         Size optimalSize = Util.getOptimalVideoSnapshotPictureSize(supported,
1966                 (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
1967         Size original = mParameters.getPictureSize();
1968         if (!original.equals(optimalSize)) {
1969             mParameters.setPictureSize(optimalSize.width, optimalSize.height);
1970         }
1971         Log.v(TAG, "Video snapshot size is " + optimalSize.width + "x" +
1972                 optimalSize.height);
1973 
1974         // Set JPEG quality.
1975         int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1976                 CameraProfile.QUALITY_HIGH);
1977         mParameters.setJpegQuality(jpegQuality);
1978 
1979         mActivity.mCameraDevice.setParameters(mParameters);
1980         // Keep preview size up to date.
1981         mParameters = mActivity.mCameraDevice.getParameters();
1982 
1983         updateCameraScreenNailSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
1984     }
1985 
updateCameraScreenNailSize(int width, int height)1986     private void updateCameraScreenNailSize(int width, int height) {
1987         if (!ApiHelper.HAS_SURFACE_TEXTURE) return;
1988 
1989         if (mCameraDisplayOrientation % 180 != 0) {
1990             int tmp = width;
1991             width = height;
1992             height = tmp;
1993         }
1994 
1995         CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
1996         int oldWidth = screenNail.getWidth();
1997         int oldHeight = screenNail.getHeight();
1998 
1999         if (oldWidth != width || oldHeight != height) {
2000             screenNail.setSize(width, height);
2001             screenNail.enableAspectRatioClamping();
2002             mActivity.notifyScreenNailChanged();
2003         }
2004 
2005         if (screenNail.getSurfaceTexture() == null) {
2006             screenNail.acquireSurfaceTexture();
2007         }
2008     }
2009 
2010     @Override
onActivityResult(int requestCode, int resultCode, Intent data)2011     public void onActivityResult(int requestCode, int resultCode, Intent data) {
2012         switch (requestCode) {
2013             case REQUEST_EFFECT_BACKDROPPER:
2014                 if (resultCode == Activity.RESULT_OK) {
2015                     // onActivityResult() runs before onResume(), so this parameter will be
2016                     // seen by startPreview from onResume()
2017                     mEffectUriFromGallery = data.getData().toString();
2018                     Log.v(TAG, "Received URI from gallery: " + mEffectUriFromGallery);
2019                     mResetEffect = false;
2020                 } else {
2021                     mEffectUriFromGallery = null;
2022                     Log.w(TAG, "No URI from gallery");
2023                     mResetEffect = true;
2024                 }
2025                 break;
2026         }
2027     }
2028 
2029     @Override
onEffectsUpdate(int effectId, int effectMsg)2030     public void onEffectsUpdate(int effectId, int effectMsg) {
2031         Log.v(TAG, "onEffectsUpdate. Effect Message = " + effectMsg);
2032         if (effectMsg == EffectsRecorder.EFFECT_MSG_EFFECTS_STOPPED) {
2033             // Effects have shut down. Hide learning message if any,
2034             // and restart regular preview.
2035             mBgLearningMessageFrame.setVisibility(View.GONE);
2036             checkQualityAndStartPreview();
2037         } else if (effectMsg == EffectsRecorder.EFFECT_MSG_RECORDING_DONE) {
2038             // This follows the codepath from onStopVideoRecording.
2039             if (mEffectsDisplayResult && !addVideoToMediaStore()) {
2040                 if (mIsVideoCaptureIntent) {
2041                     if (mQuickCapture) {
2042                         doReturnToCaller(true);
2043                     } else {
2044                         showAlert();
2045                     }
2046                 }
2047             }
2048             mEffectsDisplayResult = false;
2049             // In onPause, these were not called if the effects were active. We
2050             // had to wait till the effects recording is complete to do this.
2051             if (mPaused) {
2052                 closeVideoFileDescriptor();
2053                 clearVideoNamer();
2054             }
2055         } else if (effectMsg == EffectsRecorder.EFFECT_MSG_PREVIEW_RUNNING) {
2056             // Enable the shutter button once the preview is complete.
2057             mShutterButton.setEnabled(true);
2058         } else if (effectId == EffectsRecorder.EFFECT_BACKDROPPER) {
2059             switch (effectMsg) {
2060                 case EffectsRecorder.EFFECT_MSG_STARTED_LEARNING:
2061                     mBgLearningMessageFrame.setVisibility(View.VISIBLE);
2062                     break;
2063                 case EffectsRecorder.EFFECT_MSG_DONE_LEARNING:
2064                 case EffectsRecorder.EFFECT_MSG_SWITCHING_EFFECT:
2065                     mBgLearningMessageFrame.setVisibility(View.GONE);
2066                     break;
2067             }
2068         }
2069         // In onPause, this was not called if the effects were active. We had to
2070         // wait till the effects completed to do this.
2071         if (mPaused) {
2072             Log.v(TAG, "OnEffectsUpdate: closing effects if activity paused");
2073             closeEffects();
2074         }
2075     }
2076 
onCancelBgTraining(View v)2077     public void onCancelBgTraining(View v) {
2078         // Remove training message
2079         mBgLearningMessageFrame.setVisibility(View.GONE);
2080         // Write default effect out to shared prefs
2081         writeDefaultEffectToPrefs();
2082         // Tell VideoCamer to re-init based on new shared pref values.
2083         onSharedPreferenceChanged();
2084     }
2085 
2086     @Override
onEffectsError(Exception exception, String fileName)2087     public synchronized void onEffectsError(Exception exception, String fileName) {
2088         // TODO: Eventually we may want to show the user an error dialog, and then restart the
2089         // camera and encoder gracefully. For now, we just delete the file and bail out.
2090         if (fileName != null && new File(fileName).exists()) {
2091             deleteVideoFile(fileName);
2092         }
2093         try {
2094             if (Class.forName("android.filterpacks.videosink.MediaRecorderStopException")
2095                     .isInstance(exception)) {
2096                 Log.w(TAG, "Problem recoding video file. Removing incomplete file.");
2097                 return;
2098             }
2099         } catch (ClassNotFoundException ex) {
2100             Log.w(TAG, ex);
2101         }
2102         throw new RuntimeException("Error during recording!", exception);
2103     }
2104 
initializeControlByIntent()2105     private void initializeControlByIntent() {
2106         mBlocker = mRootView.findViewById(R.id.blocker);
2107         mMenu = mRootView.findViewById(R.id.menu);
2108         mMenu.setOnClickListener(new OnClickListener() {
2109             @Override
2110             public void onClick(View v) {
2111                 if (mPieRenderer != null) {
2112                     mPieRenderer.showInCenter();
2113                 }
2114             }
2115         });
2116         mOnScreenIndicators = mRootView.findViewById(R.id.on_screen_indicators);
2117         mFlashIndicator = (ImageView) mRootView.findViewById(R.id.menu_flash_indicator);
2118         if (mIsVideoCaptureIntent) {
2119             mActivity.hideSwitcher();
2120             // Cannot use RotateImageView for "done" and "cancel" button because
2121             // the tablet layout uses RotateLayout, which cannot be cast to
2122             // RotateImageView.
2123             mReviewDoneButton = (Rotatable) mRootView.findViewById(R.id.btn_done);
2124             mReviewCancelButton = (Rotatable) mRootView.findViewById(R.id.btn_cancel);
2125             mReviewPlayButton = (RotateImageView) mRootView.findViewById(R.id.btn_play);
2126 
2127             ((View) mReviewCancelButton).setVisibility(View.VISIBLE);
2128 
2129             ((View) mReviewDoneButton).setOnClickListener(new OnClickListener() {
2130                 @Override
2131                 public void onClick(View v) {
2132                     onReviewDoneClicked(v);
2133                 }
2134             });
2135             ((View) mReviewCancelButton).setOnClickListener(new OnClickListener() {
2136                 @Override
2137                 public void onClick(View v) {
2138                     onReviewCancelClicked(v);
2139                 }
2140             });
2141 
2142             ((View) mReviewPlayButton).setOnClickListener(new OnClickListener() {
2143                 @Override
2144                 public void onClick(View v) {
2145                     onReviewPlayClicked(v);
2146                 }
2147             });
2148 
2149 
2150             // Not grayed out upon disabled, to make the follow-up fade-out
2151             // effect look smooth. Note that the review done button in tablet
2152             // layout is not a TwoStateImageView.
2153             if (mReviewDoneButton instanceof TwoStateImageView) {
2154                 ((TwoStateImageView) mReviewDoneButton).enableFilter(false);
2155             }
2156         }
2157     }
2158 
initializeMiscControls()2159     private void initializeMiscControls() {
2160         mPreviewFrameLayout = (PreviewFrameLayout) mRootView.findViewById(R.id.frame);
2161         mPreviewFrameLayout.setOnLayoutChangeListener(mActivity);
2162         mReviewImage = (ImageView) mRootView.findViewById(R.id.review_image);
2163 
2164         mShutterButton = mActivity.getShutterButton();
2165         mShutterButton.setImageResource(R.drawable.btn_new_shutter_video);
2166         mShutterButton.setOnShutterButtonListener(this);
2167         mShutterButton.requestFocus();
2168 
2169         // Disable the shutter button if effects are ON since it might take
2170         // a little more time for the effects preview to be ready. We do not
2171         // want to allow recording before that happens. The shutter button
2172         // will be enabled when we get the message from effectsrecorder that
2173         // the preview is running. This becomes critical when the camera is
2174         // swapped.
2175         if (effectsActive()) {
2176             mShutterButton.setEnabled(false);
2177         }
2178 
2179         mRecordingTimeView = (TextView) mRootView.findViewById(R.id.recording_time);
2180         mRecordingTimeRect = (RotateLayout) mRootView.findViewById(R.id.recording_time_rect);
2181         mTimeLapseLabel = mRootView.findViewById(R.id.time_lapse_label);
2182         // The R.id.labels can only be found in phone layout.
2183         // That is, mLabelsLinearLayout should be null in tablet layout.
2184         mLabelsLinearLayout = (LinearLayout) mRootView.findViewById(R.id.labels);
2185 
2186         mBgLearningMessageRotater = (RotateLayout) mRootView.findViewById(R.id.bg_replace_message);
2187         mBgLearningMessageFrame = mRootView.findViewById(R.id.bg_replace_message_frame);
2188     }
2189 
2190     @Override
onConfigurationChanged(Configuration newConfig)2191     public void onConfigurationChanged(Configuration newConfig) {
2192         setDisplayOrientation();
2193 
2194         // Change layout in response to configuration change
2195         LayoutInflater inflater = mActivity.getLayoutInflater();
2196         ((ViewGroup) mRootView).removeAllViews();
2197         inflater.inflate(R.layout.video_module, (ViewGroup) mRootView);
2198 
2199         // from onCreate()
2200         initializeControlByIntent();
2201         initializeOverlay();
2202         initializeSurfaceView();
2203         initializeMiscControls();
2204         showTimeLapseUI(mCaptureTimeLapse);
2205         initializeVideoSnapshot();
2206         resizeForPreviewAspectRatio();
2207 
2208         // from onResume()
2209         showVideoSnapshotUI(false);
2210         initializeZoom();
2211         onFullScreenChanged(mActivity.isInCameraApp());
2212         updateOnScreenIndicators();
2213     }
2214 
2215     @Override
onOverriddenPreferencesClicked()2216     public void onOverriddenPreferencesClicked() {
2217     }
2218 
2219     @Override
2220     // TODO: Delete this after old camera code is removed
onRestorePreferencesClicked()2221     public void onRestorePreferencesClicked() {
2222     }
2223 
effectsActive()2224     private boolean effectsActive() {
2225         return (mEffectType != EffectsRecorder.EFFECT_NONE);
2226     }
2227 
2228     @Override
onSharedPreferenceChanged()2229     public void onSharedPreferenceChanged() {
2230         // ignore the events after "onPause()" or preview has not started yet
2231         if (mPaused) return;
2232         synchronized (mPreferences) {
2233             // If mCameraDevice is not ready then we can set the parameter in
2234             // startPreview().
2235             if (mActivity.mCameraDevice == null) return;
2236 
2237             boolean recordLocation = RecordLocationPreference.get(
2238                     mPreferences, mContentResolver);
2239             mLocationManager.recordLocation(recordLocation);
2240 
2241             // Check if the current effects selection has changed
2242             if (updateEffectSelection()) return;
2243 
2244             readVideoPreferences();
2245             showTimeLapseUI(mCaptureTimeLapse);
2246             // We need to restart the preview if preview size is changed.
2247             Size size = mParameters.getPreviewSize();
2248             if (size.width != mDesiredPreviewWidth
2249                     || size.height != mDesiredPreviewHeight) {
2250                 if (!effectsActive()) {
2251                     stopPreview();
2252                 } else {
2253                     mEffectsRecorder.release();
2254                     mEffectsRecorder = null;
2255                 }
2256                 resizeForPreviewAspectRatio();
2257                 startPreview(); // Parameters will be set in startPreview().
2258             } else {
2259                 setCameraParameters();
2260             }
2261             updateOnScreenIndicators();
2262         }
2263     }
2264 
updateOnScreenIndicators()2265     private void updateOnScreenIndicators() {
2266         updateFlashOnScreenIndicator(mParameters.getFlashMode());
2267     }
2268 
updateFlashOnScreenIndicator(String value)2269     private void updateFlashOnScreenIndicator(String value) {
2270         if (mFlashIndicator == null) {
2271             return;
2272         }
2273         if (value == null || Parameters.FLASH_MODE_OFF.equals(value)) {
2274             mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off);
2275         } else {
2276             if (Parameters.FLASH_MODE_AUTO.equals(value)) {
2277                 mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_auto);
2278             } else if (Parameters.FLASH_MODE_ON.equals(value) ||
2279                     Parameters.FLASH_MODE_TORCH.equals(value)) {
2280                 mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_on);
2281             } else {
2282                 mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off);
2283             }
2284         }
2285     }
2286 
switchCamera()2287     private void switchCamera() {
2288         if (mPaused) return;
2289 
2290         Log.d(TAG, "Start to switch camera.");
2291         mCameraId = mPendingSwitchCameraId;
2292         mPendingSwitchCameraId = -1;
2293         mVideoControl.setCameraId(mCameraId);
2294 
2295         closeCamera();
2296 
2297         // Restart the camera and initialize the UI. From onCreate.
2298         mPreferences.setLocalId(mActivity, mCameraId);
2299         CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
2300         openCamera();
2301         readVideoPreferences();
2302         startPreview();
2303         initializeVideoSnapshot();
2304         resizeForPreviewAspectRatio();
2305         initializeVideoControl();
2306 
2307         // From onResume
2308         initializeZoom();
2309         setOrientationIndicator(0, false);
2310 
2311         if (ApiHelper.HAS_SURFACE_TEXTURE) {
2312             // Start switch camera animation. Post a message because
2313             // onFrameAvailable from the old camera may already exist.
2314             mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
2315         }
2316         updateOnScreenIndicators();
2317     }
2318 
2319     // Preview texture has been copied. Now camera can be released and the
2320     // animation can be started.
2321     @Override
onPreviewTextureCopied()2322     public void onPreviewTextureCopied() {
2323         mHandler.sendEmptyMessage(SWITCH_CAMERA);
2324     }
2325 
2326     @Override
onCaptureTextureCopied()2327     public void onCaptureTextureCopied() {
2328     }
2329 
updateEffectSelection()2330     private boolean updateEffectSelection() {
2331         int previousEffectType = mEffectType;
2332         Object previousEffectParameter = mEffectParameter;
2333         mEffectType = CameraSettings.readEffectType(mPreferences);
2334         mEffectParameter = CameraSettings.readEffectParameter(mPreferences);
2335 
2336         if (mEffectType == previousEffectType) {
2337             if (mEffectType == EffectsRecorder.EFFECT_NONE) return false;
2338             if (mEffectParameter.equals(previousEffectParameter)) return false;
2339         }
2340         Log.v(TAG, "New effect selection: " + mPreferences.getString(
2341                 CameraSettings.KEY_VIDEO_EFFECT, "none"));
2342 
2343         if (mEffectType == EffectsRecorder.EFFECT_NONE) {
2344             // Stop effects and return to normal preview
2345             mEffectsRecorder.stopPreview();
2346             mPreviewing = false;
2347             return true;
2348         }
2349         if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER &&
2350             ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) {
2351             // Request video from gallery to use for background
2352             Intent i = new Intent(Intent.ACTION_PICK);
2353             i.setDataAndType(Video.Media.EXTERNAL_CONTENT_URI,
2354                              "video/*");
2355             i.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
2356             mActivity.startActivityForResult(i, REQUEST_EFFECT_BACKDROPPER);
2357             return true;
2358         }
2359         if (previousEffectType == EffectsRecorder.EFFECT_NONE) {
2360             // Stop regular preview and start effects.
2361             stopPreview();
2362             checkQualityAndStartPreview();
2363         } else {
2364             // Switch currently running effect
2365             mEffectsRecorder.setEffect(mEffectType, mEffectParameter);
2366         }
2367         return true;
2368     }
2369 
2370     // Verifies that the current preview view size is correct before starting
2371     // preview. If not, resets the surface texture and resizes the view.
checkQualityAndStartPreview()2372     private void checkQualityAndStartPreview() {
2373         readVideoPreferences();
2374         showTimeLapseUI(mCaptureTimeLapse);
2375         Size size = mParameters.getPreviewSize();
2376         if (size.width != mDesiredPreviewWidth
2377                 || size.height != mDesiredPreviewHeight) {
2378             resizeForPreviewAspectRatio();
2379         }
2380         // Start up preview again
2381         startPreview();
2382     }
2383 
showTimeLapseUI(boolean enable)2384     private void showTimeLapseUI(boolean enable) {
2385         if (mTimeLapseLabel != null) {
2386             mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.GONE);
2387         }
2388     }
2389 
2390     @Override
dispatchTouchEvent(MotionEvent m)2391     public boolean dispatchTouchEvent(MotionEvent m) {
2392         if (mSwitchingCamera) return true;
2393         if (mPopup == null && mGestures != null && mRenderOverlay != null) {
2394             return mGestures.dispatchTouch(m);
2395         } else if (mPopup != null) {
2396             return mActivity.superDispatchTouchEvent(m);
2397         }
2398         return false;
2399     }
2400 
2401     private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener {
2402         @Override
onZoomValueChanged(int value)2403         public void onZoomValueChanged(int value) {
2404             // Not useful to change zoom value when the activity is paused.
2405             if (mPaused) return;
2406             mZoomValue = value;
2407             // Set zoom parameters asynchronously
2408             mParameters.setZoom(mZoomValue);
2409             mActivity.mCameraDevice.setParametersAsync(mParameters);
2410             Parameters p = mActivity.mCameraDevice.getParameters();
2411             mZoomRenderer.setZoomValue(mZoomRatios.get(p.getZoom()));
2412         }
2413 
2414         @Override
onZoomStart()2415         public void onZoomStart() {
2416         }
2417         @Override
onZoomEnd()2418         public void onZoomEnd() {
2419         }
2420     }
2421 
initializeZoom()2422     private void initializeZoom() {
2423         if (!mParameters.isZoomSupported()) return;
2424         mZoomMax = mParameters.getMaxZoom();
2425         mZoomRatios = mParameters.getZoomRatios();
2426         // Currently we use immediate zoom for fast zooming to get better UX and
2427         // there is no plan to take advantage of the smooth zoom.
2428         mZoomRenderer.setZoomMax(mZoomMax);
2429         mZoomRenderer.setZoom(mParameters.getZoom());
2430         mZoomRenderer.setZoomValue(mZoomRatios.get(mParameters.getZoom()));
2431         mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener());
2432     }
2433 
initializeVideoSnapshot()2434     private void initializeVideoSnapshot() {
2435         if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
2436             mActivity.setSingleTapUpListener(mPreviewFrameLayout);
2437             // Show the tap to focus toast if this is the first start.
2438             if (mPreferences.getBoolean(
2439                         CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, true)) {
2440                 // Delay the toast for one second to wait for orientation.
2441                 mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_SNAPSHOT_TOAST, 1000);
2442             }
2443         } else {
2444             mActivity.setSingleTapUpListener(null);
2445         }
2446     }
2447 
showVideoSnapshotUI(boolean enabled)2448     void showVideoSnapshotUI(boolean enabled) {
2449         if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
2450             if (ApiHelper.HAS_SURFACE_TEXTURE && enabled) {
2451                 ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation);
2452             } else {
2453                 mPreviewFrameLayout.showBorder(enabled);
2454             }
2455             mShutterButton.setEnabled(!enabled);
2456         }
2457     }
2458 
2459     // Preview area is touched. Take a picture.
2460     @Override
onSingleTapUp(View view, int x, int y)2461     public void onSingleTapUp(View view, int x, int y) {
2462         if (mMediaRecorderRecording && effectsActive()) {
2463             new RotateTextToast(mActivity, R.string.disable_video_snapshot_hint,
2464                     mOrientation).show();
2465             return;
2466         }
2467 
2468         if (mPaused || mSnapshotInProgress || effectsActive()) {
2469             return;
2470         }
2471 
2472         if (!mMediaRecorderRecording) {
2473             // check for dismissing popup
2474             if (mPopup != null) {
2475                 dismissPopup(true);
2476             }
2477             return;
2478         }
2479 
2480         // Set rotation and gps data.
2481         int rotation = Util.getJpegRotation(mCameraId, mOrientation);
2482         mParameters.setRotation(rotation);
2483         Location loc = mLocationManager.getCurrentLocation();
2484         Util.setGpsParameters(mParameters, loc);
2485         mActivity.mCameraDevice.setParameters(mParameters);
2486 
2487         Log.v(TAG, "Video snapshot start");
2488         mActivity.mCameraDevice.takePicture(null, null, null, new JpegPictureCallback(loc));
2489         showVideoSnapshotUI(true);
2490         mSnapshotInProgress = true;
2491     }
2492 
2493     @Override
updateCameraAppView()2494     public void updateCameraAppView() {
2495         if (!mPreviewing || mParameters.getFlashMode() == null) return;
2496 
2497         // When going to and back from gallery, we need to turn off/on the flash.
2498         if (!mActivity.mShowCameraAppView) {
2499             if (mParameters.getFlashMode().equals(Parameters.FLASH_MODE_OFF)) {
2500                 mRestoreFlash = false;
2501                 return;
2502             }
2503             mRestoreFlash = true;
2504             setCameraParameters();
2505         } else if (mRestoreFlash) {
2506             mRestoreFlash = false;
2507             setCameraParameters();
2508         }
2509     }
2510 
setShowMenu(boolean show)2511     private void setShowMenu(boolean show) {
2512         if (mOnScreenIndicators != null) {
2513             mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE);
2514         }
2515         if (mMenu != null) {
2516             mMenu.setVisibility(show ? View.VISIBLE : View.GONE);
2517         }
2518     }
2519 
2520     @Override
onFullScreenChanged(boolean full)2521     public void onFullScreenChanged(boolean full) {
2522         if (mGestures != null) {
2523             mGestures.setEnabled(full);
2524         }
2525         if (mPopup != null) {
2526             dismissPopup(false, full);
2527         }
2528         if (mRenderOverlay != null) {
2529             // this can not happen in capture mode
2530             mRenderOverlay.setVisibility(full ? View.VISIBLE : View.GONE);
2531         }
2532         setShowMenu(full);
2533         if (mBlocker != null) {
2534             // this can not happen in capture mode
2535             mBlocker.setVisibility(full ? View.VISIBLE : View.GONE);
2536         }
2537         if (ApiHelper.HAS_SURFACE_TEXTURE) {
2538             if (mActivity.mCameraScreenNail != null) {
2539                 ((CameraScreenNail) mActivity.mCameraScreenNail).setFullScreen(full);
2540             }
2541             return;
2542         }
2543         if (full) {
2544             mPreviewSurfaceView.expand();
2545         } else {
2546             mPreviewSurfaceView.shrink();
2547         }
2548     }
2549 
2550     private final class JpegPictureCallback implements PictureCallback {
2551         Location mLocation;
2552 
JpegPictureCallback(Location loc)2553         public JpegPictureCallback(Location loc) {
2554             mLocation = loc;
2555         }
2556 
2557         @Override
onPictureTaken(byte [] jpegData, android.hardware.Camera camera)2558         public void onPictureTaken(byte [] jpegData, android.hardware.Camera camera) {
2559             Log.v(TAG, "onPictureTaken");
2560             mSnapshotInProgress = false;
2561             showVideoSnapshotUI(false);
2562             storeImage(jpegData, mLocation);
2563         }
2564     }
2565 
storeImage(final byte[] data, Location loc)2566     private void storeImage(final byte[] data, Location loc) {
2567         long dateTaken = System.currentTimeMillis();
2568         String title = Util.createJpegName(dateTaken);
2569         int orientation = Exif.getOrientation(data);
2570         Size s = mParameters.getPictureSize();
2571         Uri uri = Storage.addImage(mContentResolver, title, dateTaken, loc, orientation, data,
2572                 s.width, s.height);
2573         if (uri != null) {
2574             Util.broadcastNewPicture(mActivity, uri);
2575         }
2576     }
2577 
resetEffect()2578     private boolean resetEffect() {
2579         if (mResetEffect) {
2580             String value = mPreferences.getString(CameraSettings.KEY_VIDEO_EFFECT,
2581                     mPrefVideoEffectDefault);
2582             if (!mPrefVideoEffectDefault.equals(value)) {
2583                 writeDefaultEffectToPrefs();
2584                 return true;
2585             }
2586         }
2587         mResetEffect = true;
2588         return false;
2589     }
2590 
convertOutputFormatToMimeType(int outputFileFormat)2591     private String convertOutputFormatToMimeType(int outputFileFormat) {
2592         if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
2593             return "video/mp4";
2594         }
2595         return "video/3gpp";
2596     }
2597 
convertOutputFormatToFileExt(int outputFileFormat)2598     private String convertOutputFormatToFileExt(int outputFileFormat) {
2599         if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
2600             return ".mp4";
2601         }
2602         return ".3gp";
2603     }
2604 
closeVideoFileDescriptor()2605     private void closeVideoFileDescriptor() {
2606         if (mVideoFileDescriptor != null) {
2607             try {
2608                 mVideoFileDescriptor.close();
2609             } catch (IOException e) {
2610                 Log.e(TAG, "Fail to close fd", e);
2611             }
2612             mVideoFileDescriptor = null;
2613         }
2614     }
2615 
showTapToSnapshotToast()2616     private void showTapToSnapshotToast() {
2617         new RotateTextToast(mActivity, R.string.video_snapshot_hint, 0)
2618                 .show();
2619         // Clear the preference.
2620         Editor editor = mPreferences.edit();
2621         editor.putBoolean(CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, false);
2622         editor.apply();
2623     }
2624 
clearVideoNamer()2625     private void clearVideoNamer() {
2626         if (mVideoNamer != null) {
2627             mVideoNamer.finish();
2628             mVideoNamer = null;
2629         }
2630     }
2631 
2632     private static class VideoNamer extends Thread {
2633         private boolean mRequestPending;
2634         private ContentResolver mResolver;
2635         private ContentValues mValues;
2636         private boolean mStop;
2637         private Uri mUri;
2638 
2639         // Runs in main thread
VideoNamer()2640         public VideoNamer() {
2641             start();
2642         }
2643 
2644         // Runs in main thread
prepareUri( ContentResolver resolver, ContentValues values)2645         public synchronized void prepareUri(
2646                 ContentResolver resolver, ContentValues values) {
2647             mRequestPending = true;
2648             mResolver = resolver;
2649             mValues = new ContentValues(values);
2650             notifyAll();
2651         }
2652 
2653         // Runs in main thread
getUri()2654         public synchronized Uri getUri() {
2655             // wait until the request is done.
2656             while (mRequestPending) {
2657                 try {
2658                     wait();
2659                 } catch (InterruptedException ex) {
2660                     // ignore.
2661                 }
2662             }
2663             Uri uri = mUri;
2664             mUri = null;
2665             return uri;
2666         }
2667 
2668         // Runs in namer thread
2669         @Override
run()2670         public synchronized void run() {
2671             while (true) {
2672                 if (mStop) break;
2673                 if (!mRequestPending) {
2674                     try {
2675                         wait();
2676                     } catch (InterruptedException ex) {
2677                         // ignore.
2678                     }
2679                     continue;
2680                 }
2681                 cleanOldUri();
2682                 generateUri();
2683                 mRequestPending = false;
2684                 notifyAll();
2685             }
2686             cleanOldUri();
2687         }
2688 
2689         // Runs in main thread
finish()2690         public synchronized void finish() {
2691             mStop = true;
2692             notifyAll();
2693         }
2694 
2695         // Runs in namer thread
generateUri()2696         private void generateUri() {
2697             Uri videoTable = Uri.parse("content://media/external/video/media");
2698             mUri = mResolver.insert(videoTable, mValues);
2699         }
2700 
2701         // Runs in namer thread
cleanOldUri()2702         private void cleanOldUri() {
2703             if (mUri == null) return;
2704             mResolver.delete(mUri, null, null);
2705             mUri = null;
2706         }
2707     }
2708 
2709     private class SurfaceViewCallback implements SurfaceHolder.Callback {
SurfaceViewCallback()2710         public SurfaceViewCallback() {}
2711 
2712         @Override
surfaceChanged(SurfaceHolder holder, int format, int width, int height)2713         public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
2714             Log.v(TAG, "Surface changed. width=" + width + ". height=" + height);
2715         }
2716 
2717         @Override
surfaceCreated(SurfaceHolder holder)2718         public void surfaceCreated(SurfaceHolder holder) {
2719             Log.v(TAG, "Surface created");
2720             mSurfaceViewReady = true;
2721             if (mPaused) return;
2722             if (!ApiHelper.HAS_SURFACE_TEXTURE) {
2723                 mActivity.mCameraDevice.setPreviewDisplayAsync(mPreviewSurfaceView.getHolder());
2724                 if (!mPreviewing) {
2725                     startPreview();
2726                 }
2727             }
2728         }
2729 
2730         @Override
surfaceDestroyed(SurfaceHolder holder)2731         public void surfaceDestroyed(SurfaceHolder holder) {
2732             Log.v(TAG, "Surface destroyed");
2733             mSurfaceViewReady = false;
2734             if (mPaused) return;
2735             if (!ApiHelper.HAS_SURFACE_TEXTURE) {
2736                 stopVideoRecording();
2737                 stopPreview();
2738             }
2739         }
2740     }
2741 
2742     @Override
updateStorageHintOnResume()2743     public boolean updateStorageHintOnResume() {
2744         return true;
2745     }
2746 
2747     // required by OnPreferenceChangedListener
2748     @Override
onCameraPickerClicked(int cameraId)2749     public void onCameraPickerClicked(int cameraId) {
2750         if (mPaused || mPendingSwitchCameraId != -1) return;
2751 
2752         mPendingSwitchCameraId = cameraId;
2753         if (ApiHelper.HAS_SURFACE_TEXTURE) {
2754             Log.d(TAG, "Start to copy texture.");
2755             // We need to keep a preview frame for the animation before
2756             // releasing the camera. This will trigger onPreviewTextureCopied.
2757             ((CameraScreenNail) mActivity.mCameraScreenNail).copyTexture();
2758             // Disable all camera controls.
2759             mSwitchingCamera = true;
2760         } else {
2761             switchCamera();
2762         }
2763     }
2764 
2765     @Override
needsSwitcher()2766     public boolean needsSwitcher() {
2767         return !mIsVideoCaptureIntent;
2768     }
2769 
2770     @Override
onPieOpened(int centerX, int centerY)2771     public void onPieOpened(int centerX, int centerY) {
2772         mActivity.cancelActivityTouchHandling();
2773         mActivity.setSwipingEnabled(false);
2774     }
2775 
2776     @Override
onPieClosed()2777     public void onPieClosed() {
2778         mActivity.setSwipingEnabled(true);
2779     }
2780 
showPopup(AbstractSettingPopup popup)2781     public void showPopup(AbstractSettingPopup popup) {
2782         mActivity.hideUI();
2783         mBlocker.setVisibility(View.INVISIBLE);
2784         setShowMenu(false);
2785         mPopup = popup;
2786         mPopup.setVisibility(View.VISIBLE);
2787         FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
2788                 LayoutParams.WRAP_CONTENT);
2789         lp.gravity = Gravity.CENTER;
2790         ((FrameLayout) mRootView).addView(mPopup, lp);
2791     }
2792 
dismissPopup(boolean topLevelOnly)2793     public void dismissPopup(boolean topLevelOnly) {
2794         dismissPopup(topLevelOnly, true);
2795     }
2796 
dismissPopup(boolean topLevelPopupOnly, boolean fullScreen)2797     public void dismissPopup(boolean topLevelPopupOnly, boolean fullScreen) {
2798         if (fullScreen) {
2799             mActivity.showUI();
2800             mBlocker.setVisibility(View.VISIBLE);
2801         }
2802         setShowMenu(fullScreen);
2803         if (mPopup != null) {
2804             ((FrameLayout) mRootView).removeView(mPopup);
2805             mPopup = null;
2806         }
2807         mVideoControl.popupDismissed(topLevelPopupOnly);
2808     }
2809 
2810     @Override
onShowSwitcherPopup()2811     public void onShowSwitcherPopup() {
2812         if (mPieRenderer.showsItems()) {
2813             mPieRenderer.hide();
2814         }
2815     }
2816 }
2817