VideoUI.java revision 69ef5ea6ed838cf6fba834811f01122d349a61cf
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.camera; 18 19import android.graphics.Bitmap; 20import android.hardware.Camera.Parameters; 21import android.util.Log; 22import android.view.Gravity; 23import android.view.MotionEvent; 24import android.view.SurfaceHolder; 25import android.view.View; 26import android.view.View.OnClickListener; 27import android.view.ViewGroup; 28import android.widget.FrameLayout; 29import android.widget.FrameLayout.LayoutParams; 30import android.widget.ImageView; 31import android.widget.LinearLayout; 32import android.widget.TextView; 33 34import com.android.camera.CameraPreference.OnPreferenceChangedListener; 35import com.android.camera.ui.AbstractSettingPopup; 36import com.android.camera.ui.PieRenderer; 37import com.android.camera.ui.PreviewSurfaceView; 38import com.android.camera.ui.RenderOverlay; 39import com.android.camera.ui.RotateLayout; 40import com.android.camera.ui.ZoomRenderer; 41import com.android.gallery3d.R; 42import com.android.gallery3d.common.ApiHelper; 43 44import java.util.List; 45 46public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, 47 PreviewGestures.SingleTapListener, 48 PreviewGestures.SwipeListener { 49 private final static String TAG = "CAM_VideoUI"; 50 // module fields 51 private CameraActivity mActivity; 52 private View mRootView; 53 private PreviewFrameLayout mPreviewFrameLayout; 54 private boolean mSurfaceViewReady; 55 private PreviewSurfaceView mPreviewSurfaceView; 56 // An review image having same size as preview. It is displayed when 57 // recording is stopped in capture intent. 58 private ImageView mReviewImage; 59 private View mReviewCancelButton; 60 private View mReviewDoneButton; 61 private View mReviewPlayButton; 62 private ShutterButton mShutterButton; 63 private TextView mRecordingTimeView; 64 private LinearLayout mLabelsLinearLayout; 65 private View mTimeLapseLabel; 66 private RenderOverlay mRenderOverlay; 67 private PieRenderer mPieRenderer; 68 private VideoMenu mVideoMenu; 69 private AbstractSettingPopup mPopup; 70 private ZoomRenderer mZoomRenderer; 71 private PreviewGestures mGestures; 72 private View mMenu; 73 private View mBlocker; 74 private OnScreenIndicators mOnScreenIndicators; 75 private RotateLayout mRecordingTimeRect; 76 private VideoController mController; 77 private int mZoomMax; 78 private List<Integer> mZoomRatios; 79 80 public VideoUI(CameraActivity activity, VideoController controller, View parent) { 81 mActivity = activity; 82 mController = controller; 83 mRootView = parent; 84 mActivity.getLayoutInflater().inflate(R.layout.video_module, (ViewGroup) mRootView, true); 85 mPreviewSurfaceView = (PreviewSurfaceView) mRootView 86 .findViewById(R.id.preview_surface_view); 87 initializeMiscControls(); 88 initializeControlByIntent(); 89 initializeOverlay(); 90 } 91 92 private void initializeControlByIntent() { 93 mBlocker = mActivity.findViewById(R.id.blocker); 94 mMenu = mActivity.findViewById(R.id.menu); 95 mMenu.setOnClickListener(new OnClickListener() { 96 @Override 97 public void onClick(View v) { 98 if (mPieRenderer != null) { 99 mPieRenderer.showInCenter(); 100 } 101 } 102 }); 103 mOnScreenIndicators = new OnScreenIndicators(mActivity, 104 mActivity.findViewById(R.id.on_screen_indicators)); 105 mOnScreenIndicators.resetToDefault(); 106 if (mController.isVideoCaptureIntent()) { 107 mActivity.hideSwitcher(); 108 ViewGroup cameraControls = (ViewGroup) mActivity.findViewById(R.id.camera_controls); 109 mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls); 110 // Cannot use RotateImageView for "done" and "cancel" button because 111 // the tablet layout uses RotateLayout, which cannot be cast to 112 // RotateImageView. 113 mReviewDoneButton = mActivity.findViewById(R.id.btn_done); 114 mReviewCancelButton = mActivity.findViewById(R.id.btn_cancel); 115 mReviewPlayButton = mActivity.findViewById(R.id.btn_play); 116 mReviewCancelButton.setVisibility(View.VISIBLE); 117 mReviewDoneButton.setOnClickListener(new OnClickListener() { 118 @Override 119 public void onClick(View v) { 120 mController.onReviewDoneClicked(v); 121 } 122 }); 123 mReviewCancelButton.setOnClickListener(new OnClickListener() { 124 @Override 125 public void onClick(View v) { 126 mController.onReviewCancelClicked(v); 127 } 128 }); 129 mReviewPlayButton.setOnClickListener(new OnClickListener() { 130 @Override 131 public void onClick(View v) { 132 mController.onReviewPlayClicked(v); 133 } 134 }); 135 } 136 } 137 138 public boolean collapseCameraControls() { 139 boolean ret = false; 140 if (mPopup != null) { 141 dismissPopup(false); 142 ret = true; 143 } 144 return ret; 145 } 146 147 public boolean removeTopLevelPopup() { 148 if (mPopup != null) { 149 dismissPopup(true); 150 return true; 151 } 152 return false; 153 } 154 155 public void enableCameraControls(boolean enable) { 156 if (mGestures != null) { 157 mGestures.setZoomOnly(!enable); 158 } 159 if (mPieRenderer != null && mPieRenderer.showsItems()) { 160 mPieRenderer.hide(); 161 } 162 } 163 164 public void overrideSettings(final String... keyvalues) { 165 mVideoMenu.overrideSettings(keyvalues); 166 } 167 168 public View getPreview() { 169 return mPreviewFrameLayout; 170 } 171 172 public void setOrientationIndicator(int orientation, boolean animation) { 173 if (mGestures != null) { 174 mGestures.setOrientation(orientation); 175 } 176 // We change the orientation of the linearlayout only for phone UI 177 // because when in portrait the width is not enough. 178 if (mLabelsLinearLayout != null) { 179 if (((orientation / 90) & 1) == 0) { 180 mLabelsLinearLayout.setOrientation(LinearLayout.VERTICAL); 181 } else { 182 mLabelsLinearLayout.setOrientation(LinearLayout.HORIZONTAL); 183 } 184 } 185 mRecordingTimeRect.setOrientation(0, animation); 186 } 187 188 public SurfaceHolder getSurfaceHolder() { 189 return mPreviewSurfaceView.getHolder(); 190 } 191 192 public void hideSurfaceView() { 193 mPreviewSurfaceView.setVisibility(View.GONE); 194 } 195 196 public void showSurfaceView() { 197 mPreviewSurfaceView.setVisibility(View.VISIBLE); 198 } 199 200 private void initializeOverlay() { 201 mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay); 202 if (mPieRenderer == null) { 203 mPieRenderer = new PieRenderer(mActivity); 204 mVideoMenu = new VideoMenu(mActivity, this, mPieRenderer); 205 mPieRenderer.setPieListener(this); 206 } 207 mRenderOverlay.addRenderer(mPieRenderer); 208 if (mZoomRenderer == null) { 209 mZoomRenderer = new ZoomRenderer(mActivity); 210 } 211 mRenderOverlay.addRenderer(mZoomRenderer); 212 if (mGestures == null) { 213 mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer, this); 214 } 215 mGestures.setRenderOverlay(mRenderOverlay); 216 mGestures.reset(); 217 mGestures.addTouchReceiver(mMenu); 218 mGestures.addUnclickableArea(mBlocker); 219 if (mController.isVideoCaptureIntent()) { 220 if (mReviewCancelButton != null) { 221 mGestures.addTouchReceiver(mReviewCancelButton); 222 } 223 if (mReviewDoneButton != null) { 224 mGestures.addTouchReceiver(mReviewDoneButton); 225 } 226 if (mReviewPlayButton != null) { 227 mGestures.addTouchReceiver(mReviewPlayButton); 228 } 229 } 230 } 231 232 public void setPrefChangedListener(OnPreferenceChangedListener listener) { 233 mVideoMenu.setListener(listener); 234 } 235 236 private void initializeMiscControls() { 237 mPreviewFrameLayout = (PreviewFrameLayout) mRootView.findViewById(R.id.frame); 238 mPreviewFrameLayout.setOnLayoutChangeListener(mActivity); 239 mReviewImage = (ImageView) mRootView.findViewById(R.id.review_image); 240 mShutterButton = mActivity.getShutterButton(); 241 mShutterButton.setImageResource(R.drawable.btn_new_shutter_video); 242 mShutterButton.setOnShutterButtonListener(mController); 243 mShutterButton.setVisibility(View.VISIBLE); 244 mShutterButton.requestFocus(); 245 mShutterButton.enableTouch(true); 246 mRecordingTimeView = (TextView) mRootView.findViewById(R.id.recording_time); 247 mRecordingTimeRect = (RotateLayout) mRootView.findViewById(R.id.recording_time_rect); 248 mTimeLapseLabel = mRootView.findViewById(R.id.time_lapse_label); 249 // The R.id.labels can only be found in phone layout. 250 // That is, mLabelsLinearLayout should be null in tablet layout. 251 mLabelsLinearLayout = (LinearLayout) mRootView.findViewById(R.id.labels); 252 } 253 254 public void updateOnScreenIndicators(Parameters param, ComboPreferences prefs) { 255 mOnScreenIndicators.updateFlashOnScreenIndicator(param.getFlashMode()); 256 boolean location = RecordLocationPreference.get( 257 prefs, mActivity.getContentResolver()); 258 mOnScreenIndicators.updateLocationIndicator(location); 259 260 } 261 262 public void setAspectRatio(double ratio) { 263 mPreviewFrameLayout.setAspectRatio(ratio); 264 } 265 266 public void showTimeLapseUI(boolean enable) { 267 if (mTimeLapseLabel != null) { 268 mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.GONE); 269 } 270 } 271 272 private void openMenu() { 273 if (mPieRenderer != null) { 274 mPieRenderer.showInCenter(); 275 } 276 } 277 278 public void showPopup(AbstractSettingPopup popup) { 279 mActivity.hideUI(); 280 mBlocker.setVisibility(View.INVISIBLE); 281 setShowMenu(false); 282 mPopup = popup; 283 mPopup.setVisibility(View.VISIBLE); 284 FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, 285 LayoutParams.WRAP_CONTENT); 286 lp.gravity = Gravity.CENTER; 287 ((FrameLayout) mRootView).addView(mPopup, lp); 288 mGestures.addTouchReceiver(mPopup); 289 } 290 291 public void dismissPopup(boolean topLevelOnly) { 292 dismissPopup(topLevelOnly, true); 293 } 294 295 public void dismissPopup(boolean topLevelPopupOnly, boolean fullScreen) { 296 // In review mode, we do not want to bring up the camera UI 297 if (mController.isInReviewMode()) return; 298 299 if (fullScreen) { 300 mActivity.showUI(); 301 mBlocker.setVisibility(View.VISIBLE); 302 } 303 setShowMenu(fullScreen); 304 if (mPopup != null) { 305 mGestures.removeTouchReceiver(mPopup); 306 ((FrameLayout) mRootView).removeView(mPopup); 307 mPopup = null; 308 } 309 mVideoMenu.popupDismissed(topLevelPopupOnly); 310 } 311 312 public void onShowSwitcherPopup() { 313 hidePieRenderer(); 314 } 315 316 public boolean hidePieRenderer() { 317 if (mPieRenderer != null && mPieRenderer.showsItems()) { 318 mPieRenderer.hide(); 319 return true; 320 } 321 return false; 322 } 323 324 // disable preview gestures after shutter is pressed 325 public void setShutterPressed(boolean pressed) { 326 if (mGestures == null) return; 327 mGestures.setEnabled(!pressed); 328 } 329 330 public void enableShutter(boolean enable) { 331 if (mShutterButton != null) { 332 mShutterButton.setEnabled(enable); 333 } 334 } 335 336 // PieListener 337 @Override 338 public void onPieOpened(int centerX, int centerY) { 339 mActivity.cancelActivityTouchHandling(); 340 mActivity.setSwipingEnabled(false); 341 } 342 343 @Override 344 public void onPieClosed() { 345 mActivity.setSwipingEnabled(true); 346 } 347 348 public void showPreviewBorder(boolean enable) { 349 mPreviewFrameLayout.showBorder(enable); 350 } 351 352 // SingleTapListener 353 // Preview area is touched. Take a picture. 354 @Override 355 public void onSingleTapUp(View view, int x, int y) { 356 mController.onSingleTapUp(view, x, y); 357 } 358 359 // SurfaceView callback 360 @Override 361 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 362 Log.v(TAG, "Surface changed. width=" + width + ". height=" + height); 363 } 364 365 @Override 366 public void surfaceCreated(SurfaceHolder holder) { 367 Log.v(TAG, "Surface created"); 368 mSurfaceViewReady = true; 369 } 370 371 @Override 372 public void surfaceDestroyed(SurfaceHolder holder) { 373 Log.v(TAG, "Surface destroyed"); 374 mSurfaceViewReady = false; 375 mController.stopPreview(); 376 } 377 378 public boolean isSurfaceViewReady() { 379 return mSurfaceViewReady; 380 } 381 382 public void showRecordingUI(boolean recording, boolean zoomSupported) { 383 mMenu.setVisibility(recording ? View.GONE : View.VISIBLE); 384 mOnScreenIndicators.setVisibility(recording ? View.GONE : View.VISIBLE); 385 if (recording) { 386 mShutterButton.setImageResource(R.drawable.btn_shutter_video_recording); 387 mActivity.hideSwitcher(); 388 mRecordingTimeView.setText(""); 389 mRecordingTimeView.setVisibility(View.VISIBLE); 390 // The camera is not allowed to be accessed in older api levels during 391 // recording. It is therefore necessary to hide the zoom UI on older 392 // platforms. 393 // See the documentation of android.media.MediaRecorder.start() for 394 // further explanation. 395 if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING && zoomSupported) { 396 // TODO: disable zoom UI here. 397 } 398 } else { 399 mShutterButton.setImageResource(R.drawable.btn_new_shutter_video); 400 mActivity.showSwitcher(); 401 mRecordingTimeView.setVisibility(View.GONE); 402 if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING && zoomSupported) { 403 // TODO: enable zoom UI here. 404 } 405 } 406 } 407 408 public void showReviewImage(Bitmap bitmap) { 409 mReviewImage.setImageBitmap(bitmap); 410 mReviewImage.setVisibility(View.VISIBLE); 411 } 412 413 public void showReviewControls() { 414 Util.fadeOut(mShutterButton); 415 Util.fadeIn(mReviewDoneButton); 416 Util.fadeIn(mReviewPlayButton); 417 mReviewImage.setVisibility(View.VISIBLE); 418 mMenu.setVisibility(View.GONE); 419 mOnScreenIndicators.setVisibility(View.GONE); 420 } 421 422 public void hideReviewUI() { 423 mReviewImage.setVisibility(View.GONE); 424 mShutterButton.setEnabled(true); 425 mMenu.setVisibility(View.VISIBLE); 426 mOnScreenIndicators.setVisibility(View.VISIBLE); 427 Util.fadeOut(mReviewDoneButton); 428 Util.fadeOut(mReviewPlayButton); 429 Util.fadeIn(mShutterButton); 430 } 431 432 private void setShowMenu(boolean show) { 433 if (mOnScreenIndicators != null) { 434 mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE); 435 } 436 if (mMenu != null) { 437 mMenu.setVisibility(show ? View.VISIBLE : View.GONE); 438 } 439 } 440 441 public void onFullScreenChanged(boolean full) { 442 if (mGestures != null) { 443 mGestures.setEnabled(full); 444 } 445 if (mPopup != null) { 446 dismissPopup(false, full); 447 } 448 if (mRenderOverlay != null) { 449 // this can not happen in capture mode 450 mRenderOverlay.setVisibility(full ? View.VISIBLE : View.GONE); 451 } 452 setShowMenu(full); 453 if (mBlocker != null) { 454 // this can not happen in capture mode 455 mBlocker.setVisibility(full ? View.VISIBLE : View.GONE); 456 } 457 } 458 459 public void initializePopup(PreferenceGroup pref) { 460 mVideoMenu.initialize(pref); 461 } 462 463 public void initializeZoom(Parameters param) { 464 if (param == null || !param.isZoomSupported()) return; 465 mZoomMax = param.getMaxZoom(); 466 mZoomRatios = param.getZoomRatios(); 467 // Currently we use immediate zoom for fast zooming to get better UX and 468 // there is no plan to take advantage of the smooth zoom. 469 mZoomRenderer.setZoomMax(mZoomMax); 470 mZoomRenderer.setZoom(param.getZoom()); 471 mZoomRenderer.setZoomValue(mZoomRatios.get(param.getZoom())); 472 mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener()); 473 } 474 475 public void clickShutter() { 476 mShutterButton.performClick(); 477 } 478 479 public void pressShutter(boolean pressed) { 480 mShutterButton.setPressed(pressed); 481 } 482 483 public boolean dispatchTouchEvent(MotionEvent m) { 484 if (mGestures != null && mRenderOverlay != null) { 485 return mGestures.dispatchTouch(m); 486 } 487 return false; 488 } 489 490 public void setRecordingTime(String text) { 491 mRecordingTimeView.setText(text); 492 } 493 494 public void setRecordingTimeTextColor(int color) { 495 mRecordingTimeView.setTextColor(color); 496 } 497 498 private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener { 499 @Override 500 public void onZoomValueChanged(int index) { 501 int newZoom = mController.onZoomChanged(index); 502 if (mZoomRenderer != null) { 503 mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom)); 504 } 505 } 506 507 @Override 508 public void onZoomStart() { 509 } 510 511 @Override 512 public void onZoomEnd() { 513 } 514 } 515 516 @Override 517 public void onSwipe(int direction) { 518 if (direction == PreviewGestures.DIR_UP) { 519 openMenu(); 520 } 521 } 522 523} 524