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