From 6827ce281bd3a83ca2ffa552cd090e2b427c5f83 Mon Sep 17 00:00:00 2001 From: Doris Liu Date: Tue, 12 Mar 2013 19:24:28 -0700 Subject: Use Model-View-Controller pattern on video module Change-Id: Id673efd1da9f53d4f74aab880504850152d1edfa --- src/com/android/camera/VideoController.java | 36 ++ src/com/android/camera/VideoMenu.java | 31 +- src/com/android/camera/VideoModule.java | 700 ++++++---------------------- src/com/android/camera/VideoUI.java | 510 ++++++++++++++++++++ 4 files changed, 690 insertions(+), 587 deletions(-) create mode 100644 src/com/android/camera/VideoController.java create mode 100644 src/com/android/camera/VideoUI.java diff --git a/src/com/android/camera/VideoController.java b/src/com/android/camera/VideoController.java new file mode 100644 index 000000000..8bde8090f --- /dev/null +++ b/src/com/android/camera/VideoController.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.camera; + +import android.view.View; + +import com.android.camera.ShutterButton.OnShutterButtonListener; + +public interface VideoController extends OnShutterButtonListener { + + public void onReviewDoneClicked(View view); + public void onReviewCancelClicked(View viwe); + public void onReviewPlayClicked(View view); + + public boolean isVideoCaptureIntent(); + + public int onZoomChanged(int index); + + public void onSingleTapUp(View view, int x, int y); + + public void stopPreview(); +} diff --git a/src/com/android/camera/VideoMenu.java b/src/com/android/camera/VideoMenu.java index ab53e1546..aa3a80716 100644 --- a/src/com/android/camera/VideoMenu.java +++ b/src/com/android/camera/VideoMenu.java @@ -33,11 +33,10 @@ public class VideoMenu extends PieController ListPrefSettingPopup.Listener, TimeIntervalPopup.Listener { - - private static String TAG = "CAM_videocontrol"; + private static String TAG = "CAM_VideoMenu"; private static float FLOAT_PI_DIVIDED_BY_TWO = (float) Math.PI / 2; - private VideoModule mModule; + private VideoUI mUI; private String[] mOtherKeys; private AbstractSettingPopup mPopup; @@ -46,9 +45,9 @@ public class VideoMenu extends PieController private static final int POPUP_SECOND_LEVEL = 2; private int mPopupStatus; - public VideoMenu(CameraActivity activity, VideoModule module, PieRenderer pie) { + public VideoMenu(CameraActivity activity, VideoUI ui, PieRenderer pie) { super(activity, pie); - mModule = module; + mUI = ui; } public void initialize(PreferenceGroup group) { @@ -60,7 +59,7 @@ public class VideoMenu extends PieController addItem(CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE, FLOAT_PI_DIVIDED_BY_TWO - sweep, sweep); addItem(CameraSettings.KEY_WHITE_BALANCE, 3 * FLOAT_PI_DIVIDED_BY_TWO + sweep, sweep); PieItem item = makeItem(R.drawable.ic_switch_video_facing_holo_light); - item.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO + sweep, sweep); + item.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO + sweep, sweep); item.setOnClickListener(new OnClickListener() { @Override @@ -81,7 +80,8 @@ public class VideoMenu extends PieController CameraSettings.KEY_VIDEO_EFFECT, CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL, CameraSettings.KEY_VIDEO_QUALITY, - CameraSettings.KEY_RECORD_LOCATION}; + CameraSettings.KEY_RECORD_LOCATION + }; item = makeItem(R.drawable.ic_settings_holo_light); item.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO * 3, sweep); @@ -92,17 +92,12 @@ public class VideoMenu extends PieController initializePopup(); mPopupStatus = POPUP_FIRST_LEVEL; } - mModule.showPopup(mPopup); + mUI.showPopup(mPopup); } }); mRenderer.addItem(item); } - protected void setCameraId(int cameraId) { - ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID); - pref.setValue("" + cameraId); - } - @Override public void reloadPreferences() { super.reloadPreferences(); @@ -126,7 +121,7 @@ public class VideoMenu extends PieController public void onListPrefChanged(ListPreference pref) { if (mPopup != null) { if (mPopupStatus == POPUP_SECOND_LEVEL) { - mModule.dismissPopup(true); + mUI.dismissPopup(true); } } super.onSettingChanged(pref); @@ -152,7 +147,7 @@ public class VideoMenu extends PieController if (mPopupStatus == POPUP_SECOND_LEVEL) { initializePopup(); mPopupStatus = POPUP_FIRST_LEVEL; - if (topPopupOnly) mModule.showPopup(mPopup); + if (topPopupOnly) mUI.showPopup(mPopup); } } @@ -170,17 +165,17 @@ public class VideoMenu extends PieController R.layout.time_interval_popup, null, false); timeInterval.initialize((IconListPreference) pref); timeInterval.setSettingChangedListener(this); - mModule.dismissPopup(true); + mUI.dismissPopup(true); mPopup = timeInterval; } else { ListPrefSettingPopup basic = (ListPrefSettingPopup) inflater.inflate( R.layout.list_pref_setting_popup, null, false); basic.initialize(pref); basic.setSettingChangedListener(this); - mModule.dismissPopup(true); + mUI.dismissPopup(true); mPopup = basic; } - mModule.showPopup(mPopup); + mUI.showPopup(mPopup); mPopupStatus = POPUP_SECOND_LEVEL; } diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java index 4ae46a897..115b7d0a7 100644 --- a/src/com/android/camera/VideoModule.java +++ b/src/com/android/camera/VideoModule.java @@ -47,33 +47,15 @@ import android.os.SystemClock; import android.provider.MediaStore; import android.provider.MediaStore.Video; import android.util.Log; -import android.view.Gravity; import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.OrientationEventListener; -import android.view.SurfaceHolder; import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; import android.view.WindowManager; -import android.widget.FrameLayout; -import android.widget.FrameLayout.LayoutParams; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; import android.widget.Toast; -import com.android.camera.ui.AbstractSettingPopup; -import com.android.camera.ui.PieRenderer; import com.android.camera.ui.PopupManager; -import com.android.camera.ui.PreviewSurfaceView; -import com.android.camera.ui.RenderOverlay; -import com.android.camera.ui.Rotatable; -import com.android.camera.ui.RotateImageView; -import com.android.camera.ui.RotateLayout; import com.android.camera.ui.RotateTextToast; -import com.android.camera.ui.ZoomRenderer; import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.util.AccessibilityUtils; @@ -87,13 +69,12 @@ import java.util.Iterator; import java.util.List; public class VideoModule implements CameraModule, + VideoController, CameraPreference.OnPreferenceChangedListener, ShutterButton.OnShutterButtonListener, MediaRecorder.OnErrorListener, MediaRecorder.OnInfoListener, - EffectsRecorder.EffectsListener, - PieRenderer.PieListener, - PreviewGestures.SingleTapListener { + EffectsRecorder.EffectsListener { private static final String TAG = "CAM_VideoModule"; @@ -124,7 +105,6 @@ public class VideoModule implements CameraModule, private static final int MIN_THUMB_SIZE = 64; // module fields private CameraActivity mActivity; - private View mRootView; private boolean mPaused; private int mCameraId; private Parameters mParameters; @@ -140,24 +120,8 @@ public class VideoModule implements CameraModule, private ComboPreferences mPreferences; private PreferenceGroup mPreferenceGroup; - private PreviewFrameLayout mPreviewFrameLayout; - private boolean mSurfaceViewReady; - private SurfaceHolder.Callback mSurfaceViewCallback; - private PreviewSurfaceView mPreviewSurfaceView; private CameraScreenNail.OnFrameDrawnListener mFrameDrawnListener; - // An review image having same size as preview. It is displayed when - // recording is stopped in capture intent. - private ImageView mReviewImage; - private View mReviewCancelButton; - private View mReviewDoneButton; - private View mReviewPlayButton; - private ShutterButton mShutterButton; - private TextView mRecordingTimeView; - private RotateLayout mBgLearningMessageRotater; - private View mBgLearningMessageFrame; - private LinearLayout mLabelsLinearLayout; - private boolean mIsVideoCaptureIntent; private boolean mQuickCapture; @@ -175,7 +139,6 @@ public class VideoModule implements CameraModule, private boolean mMediaRecorderRecording = false; private long mRecordingStartTime; private boolean mRecordingTimeCountsDown = false; - private RotateLayout mRecordingTimeRect; private long mOnResumeTime; // The video file that the hardware camera is about to record into // (or is recording into.) @@ -197,10 +160,6 @@ public class VideoModule implements CameraModule, private boolean mCaptureTimeLapse = false; // Default 0. If it is larger than 0, the camcorder is in time lapse mode. private int mTimeBetweenTimeLapseFrameCaptureMs = 0; - private View mTimeLapseLabel; - - private int mDesiredPreviewWidth; - private int mDesiredPreviewHeight; boolean mPreviewing = false; // True if preview is started. // The display rotation in degrees. This is only valid when mPreviewing is @@ -208,35 +167,23 @@ public class VideoModule implements CameraModule, private int mDisplayRotation; private int mCameraDisplayOrientation; + private int mDesiredPreviewWidth; + private int mDesiredPreviewHeight; private ContentResolver mContentResolver; private LocationManager mLocationManager; private VideoNamer mVideoNamer; - private RenderOverlay mRenderOverlay; - private PieRenderer mPieRenderer; - - private VideoMenu mVideoControl; - private AbstractSettingPopup mPopup; private int mPendingSwitchCameraId; - private ZoomRenderer mZoomRenderer; - - private PreviewGestures mGestures; - private View mMenu; - private View mBlocker; - private View mOnScreenIndicators; - private ImageView mFlashIndicator; - private final Handler mHandler = new MainHandler(); - + private VideoUI mUI; // The degrees of the device rotated clockwise from its natural orientation. private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN; private int mZoomValue; // The current zoom value. - private int mZoomMax; - private List mZoomRatios; + private boolean mRestoreFlash; // This is used to check if we need to restore the flash // status when going back from gallery. @@ -282,7 +229,7 @@ public class VideoModule implements CameraModule, switch (msg.what) { case ENABLE_SHUTTER_BUTTON: - mShutterButton.setEnabled(true); + mUI.enableShutter(true); break; case CLEAR_SCREEN_DELAY: { @@ -331,7 +278,7 @@ public class VideoModule implements CameraModule, } case HIDE_SURFACE_VIEW: { - mPreviewSurfaceView.setVisibility(View.GONE); + mUI.hideSurfaceView(); break; } @@ -377,65 +324,21 @@ public class VideoModule implements CameraModule, } private void initializeSurfaceView() { - mPreviewSurfaceView = (PreviewSurfaceView) mRootView.findViewById(R.id.preview_surface_view); - if (!ApiHelper.HAS_SURFACE_TEXTURE) { // API level < 11 - if (mSurfaceViewCallback == null) { - mSurfaceViewCallback = new SurfaceViewCallback(); - } - mPreviewSurfaceView.getHolder().addCallback(mSurfaceViewCallback); - mPreviewSurfaceView.setVisibility(View.VISIBLE); - } else if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { // API level < 16 - if (mSurfaceViewCallback == null) { - mSurfaceViewCallback = new SurfaceViewCallback(); + if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { // API level < 16 mFrameDrawnListener = new CameraScreenNail.OnFrameDrawnListener() { @Override public void onFrameDrawn(CameraScreenNail c) { mHandler.sendEmptyMessage(HIDE_SURFACE_VIEW); } }; - } - mPreviewSurfaceView.getHolder().addCallback(mSurfaceViewCallback); - } - } - - private void initializeOverlay() { - mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay); - if (mPieRenderer == null) { - mPieRenderer = new PieRenderer(mActivity); - mVideoControl = new VideoMenu(mActivity, this, mPieRenderer); - mVideoControl.setListener(this); - mPieRenderer.setPieListener(this); - } - mRenderOverlay.addRenderer(mPieRenderer); - if (mZoomRenderer == null) { - mZoomRenderer = new ZoomRenderer(mActivity); - } - mRenderOverlay.addRenderer(mZoomRenderer); - if (mGestures == null) { - mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer); - } - mGestures.setRenderOverlay(mRenderOverlay); - mGestures.clearTouchReceivers(); - mGestures.addTouchReceiver(mMenu); - mGestures.addTouchReceiver(mBlocker); - - if (isVideoCaptureIntent()) { - if (mReviewCancelButton != null) { - mGestures.addTouchReceiver(mReviewCancelButton); - } - if (mReviewDoneButton != null) { - mGestures.addTouchReceiver(mReviewDoneButton); - } - if (mReviewPlayButton != null) { - mGestures.addTouchReceiver(mReviewPlayButton); - } + mUI.getSurfaceHolder().addCallback(mUI); } } @Override public void init(CameraActivity activity, View root, boolean reuseScreenNail) { mActivity = activity; - mRootView = root; + mUI = new VideoUI(activity, this, root); mPreferences = new ComboPreferences(mActivity); CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal()); mCameraId = getPreferredCameraId(mPreferences); @@ -456,8 +359,6 @@ public class VideoModule implements CameraModule, mContentResolver = mActivity.getContentResolver(); - mActivity.getLayoutInflater().inflate(R.layout.video_module, (ViewGroup) mRootView, true); - // Surface texture is from camera screen nail and startPreview needs it. // This must be done before startPreview. mIsVideoCaptureIntent = isVideoCaptureIntent(); @@ -483,6 +384,7 @@ public class VideoModule implements CameraModule, } readVideoPreferences(); + mUI.setPrefChangedListener(this); new Thread(new Runnable() { @Override public void run() { @@ -490,23 +392,63 @@ public class VideoModule implements CameraModule, } }).start(); - initializeControlByIntent(); - initializeOverlay(); - initializeMiscControls(); - mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false); mLocationManager = new LocationManager(mActivity, null); - setOrientationIndicator(0, false); + mUI.setOrientationIndicator(0, false); setDisplayOrientation(); - showTimeLapseUI(mCaptureTimeLapse); + mUI.showTimeLapseUI(mCaptureTimeLapse); initializeVideoSnapshot(); resizeForPreviewAspectRatio(); initializeVideoControl(); mPendingSwitchCameraId = -1; - updateOnScreenIndicators(); + mUI.updateOnScreenIndicators(mParameters); + + // Disable the shutter button if effects are ON since it might take + // a little more time for the effects preview to be ready. We do not + // want to allow recording before that happens. The shutter button + // will be enabled when we get the message from effectsrecorder that + // the preview is running. This becomes critical when the camera is + // swapped. + if (effectsActive()) { + mUI.enableShutter(false); + } + } + + // SingleTapListener + // Preview area is touched. Take a picture. + @Override + public void onSingleTapUp(View view, int x, int y) { + if (mMediaRecorderRecording && effectsActive()) { + new RotateTextToast(mActivity, R.string.disable_video_snapshot_hint, + mOrientation).show(); + return; + } + + MediaSaveService s = mActivity.getMediaSaveService(); + if (mPaused || mSnapshotInProgress || effectsActive() || s == null || s.isQueueFull()) { + return; + } + + if (!mMediaRecorderRecording) { + // check for dismissing popup + mUI.dismissPopup(true); + return; + } + + // Set rotation and gps data. + int rotation = Util.getJpegRotation(mCameraId, mOrientation); + mParameters.setRotation(rotation); + Location loc = mLocationManager.getCurrentLocation(); + Util.setGpsParameters(mParameters, loc); + mActivity.mCameraDevice.setParameters(mParameters); + + Log.v(TAG, "Video snapshot start"); + mActivity.mCameraDevice.takePicture(null, null, null, new JpegPictureCallback(loc)); + showVideoSnapshotUI(true); + mSnapshotInProgress = true; } @Override @@ -520,37 +462,11 @@ public class VideoModule implements CameraModule, settings.getPreferenceGroup(R.xml.video_preferences)); } - public boolean collapseCameraControls() { - boolean ret = false; - if (mPopup != null) { - dismissPopup(false); - ret = true; - } - return ret; - } - - public boolean removeTopLevelPopup() { - if (mPopup != null) { - dismissPopup(true); - return true; - } - return false; - } - - private void enableCameraControls(boolean enable) { - if (mGestures != null) { - mGestures.setZoomOnly(!enable); - } - if (mPieRenderer != null && mPieRenderer.showsItems()) { - mPieRenderer.hide(); - } - } - private void initializeVideoControl() { loadCameraPreferences(); - mVideoControl.initialize(mPreferenceGroup); + mUI.initializePopup(mPreferenceGroup); if (effectsActive()) { - mVideoControl.overrideSettings( + mUI.overrideSettings( CameraSettings.KEY_VIDEO_QUALITY, Integer.toString(getLowVideoQuality())); } @@ -593,28 +509,6 @@ public class VideoModule implements CameraModule, } } - private void setOrientationIndicator(int orientation, boolean animation) { - Rotatable[] indicators = { - mBgLearningMessageRotater}; - for (Rotatable indicator : indicators) { - if (indicator != null) indicator.setOrientation(orientation, animation); - } - if (mGestures != null) { - mGestures.setOrientation(orientation); - } - - // We change the orientation of the linearlayout only for phone UI because when in portrait - // the width is not enough. - if (mLabelsLinearLayout != null) { - if (((orientation / 90) & 1) == 0) { - mLabelsLinearLayout.setOrientation(LinearLayout.VERTICAL); - } else { - mLabelsLinearLayout.setOrientation(LinearLayout.HORIZONTAL); - } - } - mRecordingTimeRect.setOrientation(0, animation); - } - private void startPlayVideoActivity() { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat)); @@ -671,7 +565,7 @@ public class VideoModule implements CameraModule, @Override public void onShutterButtonClick() { - if (collapseCameraControls() || mSwitchingCamera) return; + if (mUI.collapseCameraControls() || mSwitchingCamera) return; boolean stop = mMediaRecorderRecording; @@ -680,7 +574,7 @@ public class VideoModule implements CameraModule, } else { startVideoRecording(); } - mShutterButton.setEnabled(false); + mUI.enableShutter(false); // Keep the shutter button disabled when in video capture intent // mode and recording is stopped. It'll be re-enabled when @@ -794,7 +688,7 @@ public class VideoModule implements CameraModule, } private void resizeForPreviewAspectRatio() { - mPreviewFrameLayout.setAspectRatio( + mUI.setAspectRatio( (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight); } @@ -818,18 +712,13 @@ public class VideoModule implements CameraModule, public void onResumeAfterSuper() { if (mActivity.mOpenCameraFail || mActivity.mCameraDisabled) return; - if (mShutterButton != null) { - mShutterButton.setEnabled(false); - } + mUI.enableShutter(false); mZoomValue = 0; showVideoSnapshotUI(false); - if (!mPreviewing) { - if (resetEffect()) { - mBgLearningMessageFrame.setVisibility(View.GONE); - } + resetEffect(); openCamera(); if (mActivity.mOpenCameraFail) { Util.showErrorAndFinish(mActivity, @@ -850,7 +739,7 @@ public class VideoModule implements CameraModule, } // Initializing it here after the preview is started. - initializeZoom(); + mUI.initializeZoom(mParameters); keepScreenOnAwhile(); @@ -884,6 +773,19 @@ public class VideoModule implements CameraModule, mActivity.getGLRoot().requestLayoutContentPane(); } + @Override + public int onZoomChanged(int index) { + // Not useful to change zoom value when the activity is paused. + if (mPaused) return index; + mZoomValue = index; + if (mParameters == null || mActivity.mCameraDevice == null) return index; + // Set zoom parameters asynchronously + mParameters.setZoom(mZoomValue); + mActivity.mCameraDevice.setParametersAsync(mParameters); + Parameters p = mActivity.mCameraDevice.getParameters(); + if (p != null) return p.getZoom(); + return index; + } private void startPreview() { Log.v(TAG, "startPreview"); @@ -896,7 +798,6 @@ public class VideoModule implements CameraModule, } } - setDisplayOrientation(); mActivity.mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation); setCameraParameters(); @@ -911,7 +812,7 @@ public class VideoModule implements CameraModule, } mActivity.mCameraDevice.setPreviewTextureAsync(surfaceTexture); } else { - mActivity.mCameraDevice.setPreviewDisplayAsync(mPreviewSurfaceView.getHolder()); + mActivity.mCameraDevice.setPreviewDisplayAsync(mUI.getSurfaceHolder()); } mActivity.mCameraDevice.startPreviewAsync(); mPreviewing = true; @@ -941,12 +842,11 @@ public class VideoModule implements CameraModule, } private void onPreviewStarted() { - if (mShutterButton != null) { - mShutterButton.setEnabled(true); - } + mUI.enableShutter(true); } - private void stopPreview() { + @Override + public void stopPreview() { mActivity.mCameraDevice.stopPreview(); mPreviewing = false; } @@ -1013,7 +913,7 @@ public class VideoModule implements CameraModule, screenNail.releaseSurfaceTexture(); if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { mHandler.removeMessages(HIDE_SURFACE_VIEW); - mPreviewSurfaceView.setVisibility(View.GONE); + mUI.hideSurfaceView(); } } } @@ -1083,11 +983,10 @@ public class VideoModule implements CameraModule, if (mMediaRecorderRecording) { onStopVideoRecording(); return true; - } else if (mPieRenderer != null && mPieRenderer.showsItems()) { - mPieRenderer.hide(); + } else if (mUI.hidePieRenderer()) { return true; } else { - return removeTopLevelPopup(); + return mUI.removeTopLevelPopup(); } } @@ -1101,13 +1000,13 @@ public class VideoModule implements CameraModule, switch (keyCode) { case KeyEvent.KEYCODE_CAMERA: if (event.getRepeatCount() == 0) { - mShutterButton.performClick(); + mUI.clickShutter(); return true; } break; case KeyEvent.KEYCODE_DPAD_CENTER: if (event.getRepeatCount() == 0) { - mShutterButton.performClick(); + mUI.clickShutter(); return true; } break; @@ -1122,13 +1021,14 @@ public class VideoModule implements CameraModule, public boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_CAMERA: - mShutterButton.setPressed(false); + mUI.pressShutter(false); return true; } return false; } - private boolean isVideoCaptureIntent() { + @Override + public boolean isVideoCaptureIntent() { String action = mActivity.getIntent().getAction(); return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action)); } @@ -1159,12 +1059,12 @@ public class VideoModule implements CameraModule, private void setupMediaRecorderPreviewDisplay() { // Nothing to do here if using SurfaceTexture. if (!ApiHelper.HAS_SURFACE_TEXTURE) { - mMediaRecorder.setPreviewDisplay(mPreviewSurfaceView.getHolder().getSurface()); + mMediaRecorder.setPreviewDisplay(mUI.getSurfaceHolder().getSurface()); } else if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { // We stop the preview here before unlocking the device because we // need to change the SurfaceTexture to SurfaceView for preview. stopPreview(); - mActivity.mCameraDevice.setPreviewDisplayAsync(mPreviewSurfaceView.getHolder()); + mActivity.mCameraDevice.setPreviewDisplayAsync(mUI.getSurfaceHolder()); // The orientation for SurfaceTexture is different from that for // SurfaceView. For SurfaceTexture we don't need to consider the // display rotation. Just consider the sensor's orientation and we @@ -1176,7 +1076,7 @@ public class VideoModule implements CameraModule, Util.getDisplayOrientation(mDisplayRotation, mCameraId)); mActivity.mCameraDevice.startPreviewAsync(); mPreviewing = true; - mMediaRecorder.setPreviewDisplay(mPreviewSurfaceView.getHolder().getSurface()); + mMediaRecorder.setPreviewDisplay(mUI.getSurfaceHolder().getSurface()); } } @@ -1191,8 +1091,8 @@ public class VideoModule implements CameraModule, // surfaceCreated() is called immediately when the visibility is // changed to visible. Thus, mSurfaceViewReady should become true // right after calling setVisibility(). - mPreviewSurfaceView.setVisibility(View.VISIBLE); - if (!mSurfaceViewReady) return; + mUI.showSurfaceView(); + if (!mUI.isSurfaceViewReady()) return; } Intent intent = mActivity.getIntent(); @@ -1622,7 +1522,7 @@ public class VideoModule implements CameraModule, // Make sure the video recording has started before announcing // this in accessibility. - AccessibilityUtils.makeAnnouncement(mShutterButton, + AccessibilityUtils.makeAnnouncement(mActivity.getShutterButton(), mActivity.getString(R.string.video_recording_started)); // The parameters may have been changed by MediaRecorder upon starting @@ -1633,45 +1533,17 @@ public class VideoModule implements CameraModule, mParameters = mActivity.mCameraDevice.getParameters(); } - enableCameraControls(false); + mUI.enableCameraControls(false); mMediaRecorderRecording = true; mActivity.getOrientationManager().lockOrientation(); mRecordingStartTime = SystemClock.uptimeMillis(); - showRecordingUI(true); + mUI.showRecordingUI(true, mParameters.isZoomSupported()); updateRecordingTime(); keepScreenOn(); } - private void showRecordingUI(boolean recording) { - mMenu.setVisibility(recording ? View.GONE : View.VISIBLE); - mOnScreenIndicators.setVisibility(recording ? View.GONE : View.VISIBLE); - if (recording) { - mShutterButton.setImageResource(R.drawable.btn_shutter_video_recording); - mActivity.hideSwitcher(); - mRecordingTimeView.setText(""); - mRecordingTimeView.setVisibility(View.VISIBLE); - // The camera is not allowed to be accessed in older api levels during - // recording. It is therefore necessary to hide the zoom UI on older - // platforms. - // See the documentation of android.media.MediaRecorder.start() for - // further explanation. - if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING - && mParameters.isZoomSupported()) { - // TODO: disable zoom UI here. - } - } else { - mShutterButton.setImageResource(R.drawable.btn_new_shutter_video); - mActivity.showSwitcher(); - mRecordingTimeView.setVisibility(View.GONE); - if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING - && mParameters.isZoomSupported()) { - // TODO: enable zoom UI here. - } - } - } - private void showCaptureResult() { Bitmap bitmap = null; if (mVideoFileDescriptor != null) { @@ -1687,35 +1559,19 @@ public class VideoModule implements CameraModule, CameraInfo[] info = CameraHolder.instance().getCameraInfo(); boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT); bitmap = Util.rotateAndMirror(bitmap, 0, mirror); - mReviewImage.setImageBitmap(bitmap); - mReviewImage.setVisibility(View.VISIBLE); + mUI.showReviewImage(bitmap); } - Util.fadeOut(mShutterButton); - - Util.fadeIn(mReviewDoneButton); - Util.fadeIn(mReviewPlayButton); - mMenu.setVisibility(View.GONE); - mOnScreenIndicators.setVisibility(View.GONE); - enableCameraControls(false); - - showTimeLapseUI(false); + mUI.showReviewControls(); + mUI.enableCameraControls(false); + mUI.showTimeLapseUI(false); } private void hideAlert() { - mReviewImage.setVisibility(View.GONE); - mShutterButton.setEnabled(true); - mMenu.setVisibility(View.VISIBLE); - mOnScreenIndicators.setVisibility(View.VISIBLE); - enableCameraControls(true); - - Util.fadeOut(mReviewDoneButton); - Util.fadeOut(mReviewPlayButton); - - Util.fadeIn(mShutterButton); - + mUI.enableCameraControls(true); + mUI.hideReviewUI(); if (mCaptureTimeLapse) { - showTimeLapseUI(true); + mUI.showTimeLapseUI(true); } } @@ -1744,7 +1600,7 @@ public class VideoModule implements CameraModule, mCurrentVideoFilename = mVideoFilename; Log.v(TAG, "stopVideoRecording: Setting current video filename: " + mCurrentVideoFilename); - AccessibilityUtils.makeAnnouncement(mShutterButton, + AccessibilityUtils.makeAnnouncement(mActivity.getShutterButton(), mActivity.getString(R.string.video_recording_stopped)); } catch (RuntimeException e) { Log.e(TAG, "stop fail", e); @@ -1770,13 +1626,13 @@ public class VideoModule implements CameraModule, closeCamera(closeEffects); } - showRecordingUI(false); + mUI.showRecordingUI(false, mParameters.isZoomSupported()); if (!mIsVideoCaptureIntent) { - enableCameraControls(true); + mUI.enableCameraControls(true); } // The orientation was fixed during video recording. Now make it // reflect the device orientation as video recording is stopped. - setOrientationIndicator(0, true); + mUI.setOrientationIndicator(0, true); keepScreenOnAwhile(); if (shouldAddToMediaStoreNow) { if (addVideoToMediaStore()) fail = true; @@ -1901,7 +1757,7 @@ public class VideoModule implements CameraModule, targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs; } - mRecordingTimeView.setText(text); + mUI.setRecordingTime(text); if (mRecordingTimeCountsDown != countdownRemainingTime) { // Avoid setting the color on every update, do it only @@ -1912,7 +1768,7 @@ public class VideoModule implements CameraModule, ? R.color.recording_time_remaining_text : R.color.recording_time_elapsed_text); - mRecordingTimeView.setTextColor(color); + mUI.setRecordingTimeTextColor(color); } long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay); @@ -2058,7 +1914,6 @@ public class VideoModule implements CameraModule, if (effectMsg == EffectsRecorder.EFFECT_MSG_EFFECTS_STOPPED) { // Effects have shut down. Hide learning message if any, // and restart regular preview. - mBgLearningMessageFrame.setVisibility(View.GONE); checkQualityAndStartPreview(); } else if (effectMsg == EffectsRecorder.EFFECT_MSG_RECORDING_DONE) { // This follows the codepath from onStopVideoRecording. @@ -2080,17 +1935,7 @@ public class VideoModule implements CameraModule, } } else if (effectMsg == EffectsRecorder.EFFECT_MSG_PREVIEW_RUNNING) { // Enable the shutter button once the preview is complete. - mShutterButton.setEnabled(true); - } else if (effectId == EffectsRecorder.EFFECT_BACKDROPPER) { - switch (effectMsg) { - case EffectsRecorder.EFFECT_MSG_STARTED_LEARNING: - mBgLearningMessageFrame.setVisibility(View.VISIBLE); - break; - case EffectsRecorder.EFFECT_MSG_DONE_LEARNING: - case EffectsRecorder.EFFECT_MSG_SWITCHING_EFFECT: - mBgLearningMessageFrame.setVisibility(View.GONE); - break; - } + mUI.enableShutter(true); } // In onPause, this was not called if the effects were active. We had to // wait till the effects completed to do this. @@ -2101,8 +1946,6 @@ public class VideoModule implements CameraModule, } public void onCancelBgTraining(View v) { - // Remove training message - mBgLearningMessageFrame.setVisibility(View.GONE); // Write default effect out to shared prefs writeDefaultEffectToPrefs(); // Tell VideoCamer to re-init based on new shared pref values. @@ -2128,87 +1971,6 @@ public class VideoModule implements CameraModule, throw new RuntimeException("Error during recording!", exception); } - private void initializeControlByIntent() { - mBlocker = mActivity.findViewById(R.id.blocker); - mMenu = mActivity.findViewById(R.id.menu); - mMenu.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (mPieRenderer != null) { - mPieRenderer.showInCenter(); - } - } - }); - mOnScreenIndicators = mActivity.findViewById(R.id.on_screen_indicators); - mFlashIndicator = (ImageView) mActivity.findViewById(R.id.menu_flash_indicator); - if (mIsVideoCaptureIntent) { - mActivity.hideSwitcher(); - ViewGroup cameraControls = (ViewGroup) mActivity.findViewById(R.id.camera_controls); - mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls); - // Cannot use RotateImageView for "done" and "cancel" button because - // the tablet layout uses RotateLayout, which cannot be cast to - // RotateImageView. - mReviewDoneButton = mActivity.findViewById(R.id.btn_done); - mReviewCancelButton = mActivity.findViewById(R.id.btn_cancel); - mReviewPlayButton = mActivity.findViewById(R.id.btn_play); - - mReviewCancelButton.setVisibility(View.VISIBLE); - - mReviewDoneButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - onReviewDoneClicked(v); - } - }); - mReviewCancelButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - onReviewCancelClicked(v); - } - }); - - mReviewPlayButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - onReviewPlayClicked(v); - } - }); - } - } - - private void initializeMiscControls() { - mPreviewFrameLayout = (PreviewFrameLayout) mRootView.findViewById(R.id.frame); - mPreviewFrameLayout.setOnLayoutChangeListener(mActivity); - mReviewImage = (ImageView) mRootView.findViewById(R.id.review_image); - - mShutterButton = mActivity.getShutterButton(); - mShutterButton.setImageResource(R.drawable.btn_new_shutter_video); - mShutterButton.setOnShutterButtonListener(this); - mShutterButton.setVisibility(View.VISIBLE); - mShutterButton.requestFocus(); - mShutterButton.enableTouch(true); - - // Disable the shutter button if effects are ON since it might take - // a little more time for the effects preview to be ready. We do not - // want to allow recording before that happens. The shutter button - // will be enabled when we get the message from effectsrecorder that - // the preview is running. This becomes critical when the camera is - // swapped. - if (effectsActive()) { - mShutterButton.setEnabled(false); - } - - mRecordingTimeView = (TextView) mRootView.findViewById(R.id.recording_time); - mRecordingTimeRect = (RotateLayout) mRootView.findViewById(R.id.recording_time_rect); - mTimeLapseLabel = mRootView.findViewById(R.id.time_lapse_label); - // The R.id.labels can only be found in phone layout. - // That is, mLabelsLinearLayout should be null in tablet layout. - mLabelsLinearLayout = (LinearLayout) mRootView.findViewById(R.id.labels); - - mBgLearningMessageRotater = (RotateLayout) mRootView.findViewById(R.id.bg_replace_message); - mBgLearningMessageFrame = mRootView.findViewById(R.id.bg_replace_message_frame); - } - @Override public void onConfigurationChanged(Configuration newConfig) { Log.v(TAG, "onConfigurationChanged"); @@ -2245,7 +2007,7 @@ public class VideoModule implements CameraModule, if (updateEffectSelection()) return; readVideoPreferences(); - showTimeLapseUI(mCaptureTimeLapse); + mUI.showTimeLapseUI(mCaptureTimeLapse); // We need to restart the preview if preview size is changed. Size size = mParameters.getPreviewSize(); if (size.width != mDesiredPreviewWidth @@ -2261,31 +2023,13 @@ public class VideoModule implements CameraModule, } else { setCameraParameters(); } - updateOnScreenIndicators(); + mUI.updateOnScreenIndicators(mParameters); } } - private void updateOnScreenIndicators() { - if (mParameters == null) return; - updateFlashOnScreenIndicator(mParameters.getFlashMode()); - } - - private void updateFlashOnScreenIndicator(String value) { - if (mFlashIndicator == null) { - return; - } - if (value == null || Parameters.FLASH_MODE_OFF.equals(value)) { - mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off); - } else { - if (Parameters.FLASH_MODE_AUTO.equals(value)) { - mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_auto); - } else if (Parameters.FLASH_MODE_ON.equals(value) || - Parameters.FLASH_MODE_TORCH.equals(value)) { - mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_on); - } else { - mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off); - } - } + protected void setCameraId(int cameraId) { + ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID); + pref.setValue("" + cameraId); } private void switchCamera() { @@ -2294,10 +2038,10 @@ public class VideoModule implements CameraModule, Log.d(TAG, "Start to switch camera."); mCameraId = mPendingSwitchCameraId; mPendingSwitchCameraId = -1; - mVideoControl.setCameraId(mCameraId); + setCameraId(mCameraId); closeCamera(); - + mUI.collapseCameraControls(); // Restart the camera and initialize the UI. From onCreate. mPreferences.setLocalId(mActivity, mCameraId); CameraSettings.upgradeLocalPreferences(mPreferences.getLocal()); @@ -2309,15 +2053,15 @@ public class VideoModule implements CameraModule, initializeVideoControl(); // From onResume - initializeZoom(); - setOrientationIndicator(0, false); + mUI.initializeZoom(mParameters); + mUI.setOrientationIndicator(0, false); if (ApiHelper.HAS_SURFACE_TEXTURE) { // Start switch camera animation. Post a message because // onFrameAvailable from the old camera may already exist. mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION); } - updateOnScreenIndicators(); + mUI.updateOnScreenIndicators(mParameters); } // Preview texture has been copied. Now camera can be released and the @@ -2375,7 +2119,7 @@ public class VideoModule implements CameraModule, // preview. If not, resets the surface texture and resizes the view. private void checkQualityAndStartPreview() { readVideoPreferences(); - showTimeLapseUI(mCaptureTimeLapse); + mUI.showTimeLapseUI(mCaptureTimeLapse); Size size = mParameters.getPreviewSize(); if (size.width != mDesiredPreviewWidth || size.height != mDesiredPreviewHeight) { @@ -2385,60 +2129,16 @@ public class VideoModule implements CameraModule, startPreview(); } - private void showTimeLapseUI(boolean enable) { - if (mTimeLapseLabel != null) { - mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.GONE); - } - } - @Override public boolean dispatchTouchEvent(MotionEvent m) { if (mSwitchingCamera) return true; - if (mPopup == null && mGestures != null && mRenderOverlay != null) { - return mGestures.dispatchTouch(m); - } else if (mPopup != null) { - return mActivity.superDispatchTouchEvent(m); - } - return false; - } - - private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener { - @Override - public void onZoomValueChanged(int value) { - // Not useful to change zoom value when the activity is paused. - if (mPaused) return; - mZoomValue = value; - // Set zoom parameters asynchronously - mParameters.setZoom(mZoomValue); - mActivity.mCameraDevice.setParametersAsync(mParameters); - Parameters p = mActivity.mCameraDevice.getParameters(); - mZoomRenderer.setZoomValue(mZoomRatios.get(p.getZoom())); - } - - @Override - public void onZoomStart() { - } - @Override - public void onZoomEnd() { - } - } - - private void initializeZoom() { - if (mParameters == null || !mParameters.isZoomSupported()) return; - mZoomMax = mParameters.getMaxZoom(); - mZoomRatios = mParameters.getZoomRatios(); - // Currently we use immediate zoom for fast zooming to get better UX and - // there is no plan to take advantage of the smooth zoom. - mZoomRenderer.setZoomMax(mZoomMax); - mZoomRenderer.setZoom(mParameters.getZoom()); - mZoomRenderer.setZoomValue(mZoomRatios.get(mParameters.getZoom())); - mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener()); + return mUI.dispatchTouchEvent(m); } private void initializeVideoSnapshot() { if (mParameters == null) return; if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) { - mActivity.setSingleTapUpListener(mPreviewFrameLayout); + mActivity.setSingleTapUpListener(mUI.getPreview()); // Show the tap to focus toast if this is the first start. if (mPreferences.getBoolean( CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, true)) { @@ -2456,47 +2156,12 @@ public class VideoModule implements CameraModule, if (ApiHelper.HAS_SURFACE_TEXTURE && enabled) { ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation); } else { - mPreviewFrameLayout.showBorder(enabled); + mUI.showPreviewBorder(enabled); } - mShutterButton.setEnabled(!enabled); + mUI.enableShutter(!enabled); } } - // Preview area is touched. Take a picture. - @Override - public void onSingleTapUp(View view, int x, int y) { - if (mMediaRecorderRecording && effectsActive()) { - new RotateTextToast(mActivity, R.string.disable_video_snapshot_hint, - mOrientation).show(); - return; - } - - MediaSaveService s = mActivity.getMediaSaveService(); - if (mPaused || mSnapshotInProgress || effectsActive() || s == null || s.isQueueFull()) { - return; - } - - if (!mMediaRecorderRecording) { - // check for dismissing popup - if (mPopup != null) { - dismissPopup(true); - } - return; - } - - // Set rotation and gps data. - int rotation = Util.getJpegRotation(mCameraId, mOrientation); - mParameters.setRotation(rotation); - Location loc = mLocationManager.getCurrentLocation(); - Util.setGpsParameters(mParameters, loc); - mActivity.mCameraDevice.setParameters(mParameters); - - Log.v(TAG, "Video snapshot start"); - mActivity.mCameraDevice.takePicture(null, null, null, new JpegPictureCallback(loc)); - showVideoSnapshotUI(true); - mSnapshotInProgress = true; - } - @Override public void updateCameraAppView() { if (!mPreviewing || mParameters.getFlashMode() == null) return; @@ -2515,43 +2180,15 @@ public class VideoModule implements CameraModule, } } - private void setShowMenu(boolean show) { - if (mOnScreenIndicators != null) { - mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE); - } - if (mMenu != null) { - mMenu.setVisibility(show ? View.VISIBLE : View.GONE); - } - } - @Override public void onFullScreenChanged(boolean full) { - if (mGestures != null) { - mGestures.setEnabled(full); - } - if (mPopup != null) { - dismissPopup(false, full); - } - if (mRenderOverlay != null) { - // this can not happen in capture mode - mRenderOverlay.setVisibility(full ? View.VISIBLE : View.GONE); - } - setShowMenu(full); - if (mBlocker != null) { - // this can not happen in capture mode - mBlocker.setVisibility(full ? View.VISIBLE : View.GONE); - } + mUI.onFullScreenChanged(full); if (ApiHelper.HAS_SURFACE_TEXTURE) { if (mActivity.mCameraScreenNail != null) { ((CameraScreenNail) mActivity.mCameraScreenNail).setFullScreen(full); } return; } - if (full) { - mPreviewSurfaceView.expand(); - } else { - mPreviewSurfaceView.shrink(); - } } private final class JpegPictureCallback implements PictureCallback { @@ -2711,39 +2348,6 @@ public class VideoModule implements CameraModule, } } - private class SurfaceViewCallback implements SurfaceHolder.Callback { - public SurfaceViewCallback() {} - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - Log.v(TAG, "Surface changed. width=" + width + ". height=" + height); - } - - @Override - public void surfaceCreated(SurfaceHolder holder) { - Log.v(TAG, "Surface created"); - mSurfaceViewReady = true; - if (mPaused) return; - if (!ApiHelper.HAS_SURFACE_TEXTURE) { - mActivity.mCameraDevice.setPreviewDisplayAsync(mPreviewSurfaceView.getHolder()); - if (!mPreviewing) { - startPreview(); - } - } - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - Log.v(TAG, "Surface destroyed"); - mSurfaceViewReady = false; - if (mPaused) return; - if (!ApiHelper.HAS_SURFACE_TEXTURE) { - stopVideoRecording(); - stopPreview(); - } - } - } - @Override public boolean updateStorageHintOnResume() { return true; @@ -2777,51 +2381,9 @@ public class VideoModule implements CameraModule, return true; } - @Override - public void onPieOpened(int centerX, int centerY) { - mActivity.cancelActivityTouchHandling(); - mActivity.setSwipingEnabled(false); - } - - @Override - public void onPieClosed() { - mActivity.setSwipingEnabled(true); - } - - public void showPopup(AbstractSettingPopup popup) { - mActivity.hideUI(); - mBlocker.setVisibility(View.INVISIBLE); - setShowMenu(false); - mPopup = popup; - mPopup.setVisibility(View.VISIBLE); - FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT); - lp.gravity = Gravity.CENTER; - ((FrameLayout) mRootView).addView(mPopup, lp); - } - - public void dismissPopup(boolean topLevelOnly) { - dismissPopup(topLevelOnly, true); - } - - public void dismissPopup(boolean topLevelPopupOnly, boolean fullScreen) { - if (fullScreen) { - mActivity.showUI(); - mBlocker.setVisibility(View.VISIBLE); - } - setShowMenu(fullScreen); - if (mPopup != null) { - ((FrameLayout) mRootView).removeView(mPopup); - mPopup = null; - } - mVideoControl.popupDismissed(topLevelPopupOnly); - } - @Override public void onShowSwitcherPopup() { - if (mPieRenderer.showsItems()) { - mPieRenderer.hide(); - } + mUI.onShowSwitcherPopup(); } @Override diff --git a/src/com/android/camera/VideoUI.java b/src/com/android/camera/VideoUI.java new file mode 100644 index 000000000..63c190984 --- /dev/null +++ b/src/com/android/camera/VideoUI.java @@ -0,0 +1,510 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.camera; + +import android.graphics.Bitmap; +import android.hardware.Camera.Parameters; +import android.util.Log; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.FrameLayout.LayoutParams; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.camera.CameraPreference.OnPreferenceChangedListener; +import com.android.camera.ui.AbstractSettingPopup; +import com.android.camera.ui.PieRenderer; +import com.android.camera.ui.PreviewSurfaceView; +import com.android.camera.ui.RenderOverlay; +import com.android.camera.ui.RotateLayout; +import com.android.camera.ui.ZoomRenderer; +import com.android.gallery3d.R; +import com.android.gallery3d.common.ApiHelper; + +import java.util.List; + +public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, + PreviewGestures.SingleTapListener { + private final static String TAG = "CAM_VideoUI"; + // module fields + private CameraActivity mActivity; + private View mRootView; + private PreviewFrameLayout mPreviewFrameLayout; + private boolean mSurfaceViewReady; + private SurfaceHolder.Callback mSurfaceViewCallback; + private PreviewSurfaceView mPreviewSurfaceView; + // An review image having same size as preview. It is displayed when + // recording is stopped in capture intent. + private ImageView mReviewImage; + private View mReviewCancelButton; + private View mReviewDoneButton; + private View mReviewPlayButton; + private ShutterButton mShutterButton; + private TextView mRecordingTimeView; + private LinearLayout mLabelsLinearLayout; + private View mTimeLapseLabel; + private RenderOverlay mRenderOverlay; + private PieRenderer mPieRenderer; + private VideoMenu mVideoMenu; + private AbstractSettingPopup mPopup; + private ZoomRenderer mZoomRenderer; + private PreviewGestures mGestures; + private View mMenu; + private View mBlocker; + private View mOnScreenIndicators; + private ImageView mFlashIndicator; + private RotateLayout mRecordingTimeRect; + private VideoController mController; + private int mZoomMax; + private List mZoomRatios; + + public VideoUI(CameraActivity activity, VideoController controller, View parent) { + mActivity = activity; + mController = controller; + mRootView = parent; + mActivity.getLayoutInflater().inflate(R.layout.video_module, (ViewGroup) mRootView, true); + mPreviewSurfaceView = (PreviewSurfaceView) mRootView + .findViewById(R.id.preview_surface_view); + initializeMiscControls(); + initializeControlByIntent(); + initializeOverlay(); + } + + private void initializeControlByIntent() { + mBlocker = mActivity.findViewById(R.id.blocker); + mMenu = mActivity.findViewById(R.id.menu); + mMenu.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mPieRenderer != null) { + mPieRenderer.showInCenter(); + } + } + }); + mOnScreenIndicators = mActivity.findViewById(R.id.on_screen_indicators); + mFlashIndicator = (ImageView) mActivity.findViewById(R.id.menu_flash_indicator); + if (mController.isVideoCaptureIntent()) { + mActivity.hideSwitcher(); + ViewGroup cameraControls = (ViewGroup) mActivity.findViewById(R.id.camera_controls); + mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls); + // Cannot use RotateImageView for "done" and "cancel" button because + // the tablet layout uses RotateLayout, which cannot be cast to + // RotateImageView. + mReviewDoneButton = mActivity.findViewById(R.id.btn_done); + mReviewCancelButton = mActivity.findViewById(R.id.btn_cancel); + mReviewPlayButton = mActivity.findViewById(R.id.btn_play); + mReviewCancelButton.setVisibility(View.VISIBLE); + mReviewDoneButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mController.onReviewDoneClicked(v); + } + }); + mReviewCancelButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mController.onReviewCancelClicked(v); + } + }); + mReviewPlayButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mController.onReviewPlayClicked(v); + } + }); + } + } + + public boolean collapseCameraControls() { + boolean ret = false; + if (mPopup != null) { + dismissPopup(false); + ret = true; + } + return ret; + } + + public boolean removeTopLevelPopup() { + if (mPopup != null) { + dismissPopup(true); + return true; + } + return false; + } + + public void enableCameraControls(boolean enable) { + if (mGestures != null) { + mGestures.setZoomOnly(!enable); + } + if (mPieRenderer != null && mPieRenderer.showsItems()) { + mPieRenderer.hide(); + } + } + + public void overrideSettings(final String... keyvalues) { + mVideoMenu.overrideSettings(keyvalues); + } + + public View getPreview() { + return mPreviewFrameLayout; + } + + public void setOrientationIndicator(int orientation, boolean animation) { + if (mGestures != null) { + mGestures.setOrientation(orientation); + } + // We change the orientation of the linearlayout only for phone UI + // because when in portrait the width is not enough. + if (mLabelsLinearLayout != null) { + if (((orientation / 90) & 1) == 0) { + mLabelsLinearLayout.setOrientation(LinearLayout.VERTICAL); + } else { + mLabelsLinearLayout.setOrientation(LinearLayout.HORIZONTAL); + } + } + mRecordingTimeRect.setOrientation(0, animation); + } + + public SurfaceHolder getSurfaceHolder() { + return mPreviewSurfaceView.getHolder(); + } + + public void hideSurfaceView() { + mPreviewSurfaceView.setVisibility(View.GONE); + } + + public void showSurfaceView() { + mPreviewSurfaceView.setVisibility(View.VISIBLE); + } + + private void initializeOverlay() { + mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay); + if (mPieRenderer == null) { + mPieRenderer = new PieRenderer(mActivity); + mVideoMenu = new VideoMenu(mActivity, this, mPieRenderer); + mPieRenderer.setPieListener(this); + } + mRenderOverlay.addRenderer(mPieRenderer); + if (mZoomRenderer == null) { + mZoomRenderer = new ZoomRenderer(mActivity); + } + mRenderOverlay.addRenderer(mZoomRenderer); + if (mGestures == null) { + mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer); + } + mGestures.setRenderOverlay(mRenderOverlay); + mGestures.clearTouchReceivers(); + mGestures.addTouchReceiver(mMenu); + mGestures.addTouchReceiver(mBlocker); + if (mController.isVideoCaptureIntent()) { + if (mReviewCancelButton != null) { + mGestures.addTouchReceiver(mReviewCancelButton); + } + if (mReviewDoneButton != null) { + mGestures.addTouchReceiver(mReviewDoneButton); + } + if (mReviewPlayButton != null) { + mGestures.addTouchReceiver(mReviewPlayButton); + } + } + } + + public void setPrefChangedListener(OnPreferenceChangedListener listener) { + mVideoMenu.setListener(listener); + } + + private void initializeMiscControls() { + mPreviewFrameLayout = (PreviewFrameLayout) mRootView.findViewById(R.id.frame); + mPreviewFrameLayout.setOnLayoutChangeListener(mActivity); + mReviewImage = (ImageView) mRootView.findViewById(R.id.review_image); + mShutterButton = mActivity.getShutterButton(); + mShutterButton.setImageResource(R.drawable.btn_new_shutter_video); + mShutterButton.setOnShutterButtonListener(mController); + mShutterButton.setVisibility(View.VISIBLE); + mShutterButton.requestFocus(); + mShutterButton.enableTouch(true); + mRecordingTimeView = (TextView) mRootView.findViewById(R.id.recording_time); + mRecordingTimeRect = (RotateLayout) mRootView.findViewById(R.id.recording_time_rect); + mTimeLapseLabel = mRootView.findViewById(R.id.time_lapse_label); + // The R.id.labels can only be found in phone layout. + // That is, mLabelsLinearLayout should be null in tablet layout. + mLabelsLinearLayout = (LinearLayout) mRootView.findViewById(R.id.labels); + } + + public void updateOnScreenIndicators(Parameters param) { + if (param == null) return; + String value = param.getFlashMode(); + if (mFlashIndicator == null) return; + if (value == null || Parameters.FLASH_MODE_OFF.equals(value)) { + mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off); + } else { + if (Parameters.FLASH_MODE_AUTO.equals(value)) { + mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_auto); + } else if (Parameters.FLASH_MODE_ON.equals(value) + || Parameters.FLASH_MODE_TORCH.equals(value)) { + mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_on); + } else { + mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off); + } + } + } + + public void setAspectRatio(double ratio) { + mPreviewFrameLayout.setAspectRatio(ratio); + } + + public void showTimeLapseUI(boolean enable) { + if (mTimeLapseLabel != null) { + mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.GONE); + } + } + + public void showPopup(AbstractSettingPopup popup) { + mActivity.hideUI(); + mBlocker.setVisibility(View.INVISIBLE); + setShowMenu(false); + mPopup = popup; + mPopup.setVisibility(View.VISIBLE); + FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT); + lp.gravity = Gravity.CENTER; + ((FrameLayout) mRootView).addView(mPopup, lp); + } + + public void dismissPopup(boolean topLevelOnly) { + dismissPopup(topLevelOnly, true); + } + + public void dismissPopup(boolean topLevelPopupOnly, boolean fullScreen) { + if (fullScreen) { + mActivity.showUI(); + mBlocker.setVisibility(View.VISIBLE); + } + setShowMenu(fullScreen); + if (mPopup != null) { + ((FrameLayout) mRootView).removeView(mPopup); + mPopup = null; + } + mVideoMenu.popupDismissed(topLevelPopupOnly); + } + + public void onShowSwitcherPopup() { + hidePieRenderer(); + } + + public boolean hidePieRenderer() { + if (mPieRenderer != null && mPieRenderer.showsItems()) { + mPieRenderer.hide(); + return true; + } + return false; + } + + public void enableShutter(boolean enable) { + if (mShutterButton != null) { + mShutterButton.setEnabled(enable); + } + } + + // PieListener + @Override + public void onPieOpened(int centerX, int centerY) { + mActivity.cancelActivityTouchHandling(); + mActivity.setSwipingEnabled(false); + } + + @Override + public void onPieClosed() { + mActivity.setSwipingEnabled(true); + } + + public void showPreviewBorder(boolean enable) { + mPreviewFrameLayout.showBorder(enable); + } + + // SingleTapListener + // Preview area is touched. Take a picture. + @Override + public void onSingleTapUp(View view, int x, int y) { + mController.onSingleTapUp(view, x, y); + } + + // SurfaceView callback + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + Log.v(TAG, "Surface changed. width=" + width + ". height=" + height); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + Log.v(TAG, "Surface created"); + mSurfaceViewReady = true; + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + Log.v(TAG, "Surface destroyed"); + mSurfaceViewReady = false; + mController.stopPreview(); + } + + public boolean isSurfaceViewReady() { + return mSurfaceViewReady; + } + + public void showRecordingUI(boolean recording, boolean zoomSupported) { + mMenu.setVisibility(recording ? View.GONE : View.VISIBLE); + mOnScreenIndicators.setVisibility(recording ? View.GONE : View.VISIBLE); + if (recording) { + mShutterButton.setImageResource(R.drawable.btn_shutter_video_recording); + mActivity.hideSwitcher(); + mRecordingTimeView.setText(""); + mRecordingTimeView.setVisibility(View.VISIBLE); + // The camera is not allowed to be accessed in older api levels during + // recording. It is therefore necessary to hide the zoom UI on older + // platforms. + // See the documentation of android.media.MediaRecorder.start() for + // further explanation. + if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING && zoomSupported) { + // TODO: disable zoom UI here. + } + } else { + mShutterButton.setImageResource(R.drawable.btn_new_shutter_video); + mActivity.showSwitcher(); + mRecordingTimeView.setVisibility(View.GONE); + if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING && zoomSupported) { + // TODO: enable zoom UI here. + } + } + } + + public void showReviewImage(Bitmap bitmap) { + mReviewImage.setImageBitmap(bitmap); + mReviewImage.setVisibility(View.VISIBLE); + } + + public void showReviewControls() { + Util.fadeOut(mShutterButton); + Util.fadeIn(mReviewDoneButton); + Util.fadeIn(mReviewPlayButton); + mReviewImage.setVisibility(View.VISIBLE); + mMenu.setVisibility(View.GONE); + mOnScreenIndicators.setVisibility(View.GONE); + } + + public void hideReviewUI() { + mReviewImage.setVisibility(View.GONE); + mShutterButton.setEnabled(true); + mMenu.setVisibility(View.VISIBLE); + mOnScreenIndicators.setVisibility(View.VISIBLE); + Util.fadeOut(mReviewDoneButton); + Util.fadeOut(mReviewPlayButton); + Util.fadeIn(mShutterButton); + } + + private void setShowMenu(boolean show) { + if (mOnScreenIndicators != null) { + mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE); + } + if (mMenu != null) { + mMenu.setVisibility(show ? View.VISIBLE : View.GONE); + } + } + + public void onFullScreenChanged(boolean full) { + if (mGestures != null) { + mGestures.setEnabled(full); + } + if (mPopup != null) { + dismissPopup(false, full); + } + if (mRenderOverlay != null) { + // this can not happen in capture mode + mRenderOverlay.setVisibility(full ? View.VISIBLE : View.GONE); + } + setShowMenu(full); + if (mBlocker != null) { + // this can not happen in capture mode + mBlocker.setVisibility(full ? View.VISIBLE : View.GONE); + } + } + + public void initializePopup(PreferenceGroup pref) { + mVideoMenu.initialize(pref); + } + + public void initializeZoom(Parameters param) { + if (param == null || !param.isZoomSupported()) return; + mZoomMax = param.getMaxZoom(); + mZoomRatios = param.getZoomRatios(); + // Currently we use immediate zoom for fast zooming to get better UX and + // there is no plan to take advantage of the smooth zoom. + mZoomRenderer.setZoomMax(mZoomMax); + mZoomRenderer.setZoom(param.getZoom()); + mZoomRenderer.setZoomValue(mZoomRatios.get(param.getZoom())); + mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener()); + } + + public void clickShutter() { + mShutterButton.performClick(); + } + + public void pressShutter(boolean pressed) { + mShutterButton.setPressed(pressed); + } + + public boolean dispatchTouchEvent(MotionEvent m) { + if (mPopup == null && mGestures != null && mRenderOverlay != null) { + return mGestures.dispatchTouch(m); + } else if (mPopup != null) { + return mActivity.superDispatchTouchEvent(m); + } + return false; + } + + public void setRecordingTime(String text) { + mRecordingTimeView.setText(text); + } + + public void setRecordingTimeTextColor(int color) { + mRecordingTimeView.setTextColor(color); + } + + private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener { + @Override + public void onZoomValueChanged(int index) { + int newZoom = mController.onZoomChanged(index); + if (mZoomRenderer != null) { + mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom)); + } + } + + @Override + public void onZoomStart() { + } + + @Override + public void onZoomEnd() { + } + } +} -- cgit v1.2.3