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