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