VideoUI.java revision 773e1c9285bd2d0f03ba87db13e77b4f5197ac4f
1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.camera; 18 19import android.graphics.Bitmap; 20import android.graphics.Color; 21import android.graphics.Matrix; 22import android.graphics.SurfaceTexture; 23import android.graphics.drawable.ColorDrawable; 24import android.hardware.Camera.Parameters; 25import android.os.Handler; 26import android.os.Message; 27import android.util.Log; 28import android.view.Gravity; 29import android.view.SurfaceHolder; 30import android.view.SurfaceView; 31import android.view.TextureView; 32import android.view.TextureView.SurfaceTextureListener; 33import android.view.View; 34import android.view.View.OnClickListener; 35import android.view.View.OnLayoutChangeListener; 36import android.view.ViewGroup; 37import android.widget.FrameLayout.LayoutParams; 38import android.widget.ImageView; 39import android.widget.LinearLayout; 40import android.widget.PopupWindow; 41import android.widget.TextView; 42 43import com.android.camera.CameraPreference.OnPreferenceChangedListener; 44import com.android.camera.ui.AbstractSettingPopup; 45import com.android.camera.ui.PieRenderer; 46import com.android.camera.ui.RenderOverlay; 47import com.android.camera.ui.RotateLayout; 48import com.android.camera.ui.ZoomRenderer; 49import com.android.camera.util.CameraUtil; 50import com.android.camera2.R; 51 52import java.util.List; 53 54public class VideoUI implements PieRenderer.PieListener, 55 PreviewGestures.SingleTapListener, 56 SurfaceTextureListener, SurfaceHolder.Callback { 57 private static final String TAG = "CAM_VideoUI"; 58 private static final int UPDATE_TRANSFORM_MATRIX = 1; 59 // module fields 60 private CameraActivity mActivity; 61 private View mRootView; 62 private TextureView mTextureView; 63 // An review image having same size as preview. It is displayed when 64 // recording is stopped in capture intent. 65 private ImageView mReviewImage; 66 private View mReviewCancelButton; 67 private View mReviewDoneButton; 68 private View mReviewPlayButton; 69 private TextView mRecordingTimeView; 70 private LinearLayout mLabelsLinearLayout; 71 private View mTimeLapseLabel; 72 private RenderOverlay mRenderOverlay; 73 private PieRenderer mPieRenderer; 74 private VideoMenu mVideoMenu; 75 private SettingsPopup mPopup; 76 private ZoomRenderer mZoomRenderer; 77 private RotateLayout mRecordingTimeRect; 78 private boolean mRecordingStarted = false; 79 private SurfaceTexture mSurfaceTexture; 80 private VideoController mController; 81 private int mZoomMax; 82 private List<Integer> mZoomRatios; 83 private View mFlashOverlay; 84 85 private View mBottomBar; 86 private View mPreviewCover; 87 private SurfaceView mSurfaceView = null; 88 private int mPreviewWidth = 0; 89 private int mPreviewHeight = 0; 90 private float mSurfaceTextureUncroppedWidth; 91 private float mSurfaceTextureUncroppedHeight; 92 private float mAspectRatio = 4f / 3f; 93 private Matrix mMatrix = null; 94 private final AnimationManager mAnimationManager; 95 private final Handler mHandler = new Handler() { 96 @Override 97 public void handleMessage(Message msg) { 98 switch (msg.what) { 99 case UPDATE_TRANSFORM_MATRIX: 100 setTransformMatrix(mPreviewWidth, mPreviewHeight); 101 break; 102 default: 103 break; 104 } 105 } 106 }; 107 private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() { 108 @Override 109 public void onLayoutChange(View v, int left, int top, int right, 110 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 111 int width = right - left; 112 int height = bottom - top; 113 // Full-screen screennail 114 int w = width; 115 int h = height; 116 if (CameraUtil.getDisplayRotation(mActivity) % 180 != 0) { 117 w = height; 118 h = width; 119 } 120 if (mPreviewWidth != width || mPreviewHeight != height) { 121 mPreviewWidth = width; 122 mPreviewHeight = height; 123 onScreenSizeChanged(width, height, w, h); 124 } 125 } 126 }; 127 128 public void showPreviewCover() { 129 mPreviewCover.setVisibility(View.VISIBLE); 130 } 131 132 private class SettingsPopup extends PopupWindow { 133 public SettingsPopup(View popup) { 134 super(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 135 setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 136 setOutsideTouchable(true); 137 setFocusable(true); 138 popup.setVisibility(View.VISIBLE); 139 setContentView(popup); 140 showAtLocation(mRootView, Gravity.CENTER, 0, 0); 141 } 142 143 public void dismiss(boolean topLevelOnly) { 144 super.dismiss(); 145 popupDismissed(); 146 showUI(); 147 mVideoMenu.popupDismissed(topLevelOnly); 148 149 // Switch back into fullscreen/lights-out mode after popup 150 // is dimissed. 151 mActivity.setSystemBarsVisibility(false); 152 } 153 154 @Override 155 public void dismiss() { 156 // Called by Framework when touch outside the popup or hit back key 157 dismiss(true); 158 } 159 } 160 161 public VideoUI(CameraActivity activity, VideoController controller, View parent) { 162 mActivity = activity; 163 mController = controller; 164 mRootView = parent; 165 ViewGroup moduleRoot = (ViewGroup) mRootView.findViewById(R.id.module_layout); 166 mActivity.getLayoutInflater().inflate(R.layout.video_module, 167 (ViewGroup) moduleRoot, true); 168 169 mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay); 170 mRenderOverlay.setTapListener(this); 171 mPreviewCover = mRootView.findViewById(R.id.preview_cover); 172 mTextureView = (TextureView) mRootView.findViewById(R.id.preview_content); 173 mTextureView.setSurfaceTextureListener(this); 174 mTextureView.addOnLayoutChangeListener(mLayoutListener); 175 176 mSurfaceTexture = mTextureView.getSurfaceTexture(); 177 if (mSurfaceTexture != null) { 178 setTransformMatrix(mTextureView.getWidth(), mTextureView.getHeight()); 179 mPreviewCover.setVisibility(View.GONE); 180 } 181 182 mSurfaceTexture = mTextureView.getSurfaceTexture(); 183 if (mSurfaceTexture != null) { 184 mPreviewWidth = mTextureView.getWidth(); 185 mPreviewHeight = mTextureView.getHeight(); 186 setTransformMatrix(mPreviewWidth, mPreviewHeight); 187 mPreviewCover.setVisibility(View.GONE); 188 } 189 mFlashOverlay = mRootView.findViewById(R.id.flash_overlay); 190 initializeMiscControls(); 191 initializeControlByIntent(); 192 initializeOverlay(); 193 mAnimationManager = new AnimationManager(); 194 195 mBottomBar = mRootView.findViewById(R.id.bottom_bar); 196 mBottomBar.setBackgroundColor(activity.getResources().getColor(R.color.video_mode_color)); 197 } 198 199 200 public void initializeSurfaceView() { 201 mSurfaceView = new SurfaceView(mActivity); 202 ((ViewGroup) mRootView).addView(mSurfaceView, 0); 203 mSurfaceView.getHolder().addCallback(this); 204 } 205 206 private void initializeControlByIntent() { 207 if (mController.isVideoCaptureIntent()) { 208 // Cannot use RotateImageView for "done" and "cancel" button because 209 // the tablet layout uses RotateLayout, which cannot be cast to 210 // RotateImageView. 211 mReviewDoneButton = mRootView.findViewById(R.id.btn_done); 212 mReviewCancelButton = mRootView.findViewById(R.id.btn_cancel); 213 mReviewPlayButton = mRootView.findViewById(R.id.btn_play); 214 mReviewCancelButton.setVisibility(View.VISIBLE); 215 mReviewDoneButton.setOnClickListener(new OnClickListener() { 216 @Override 217 public void onClick(View v) { 218 mController.onReviewDoneClicked(v); 219 } 220 }); 221 mReviewCancelButton.setOnClickListener(new OnClickListener() { 222 @Override 223 public void onClick(View v) { 224 mController.onReviewCancelClicked(v); 225 } 226 }); 227 mReviewPlayButton.setOnClickListener(new OnClickListener() { 228 @Override 229 public void onClick(View v) { 230 mController.onReviewPlayClicked(v); 231 } 232 }); 233 } 234 } 235 236 public void setPreviewSize(int width, int height) { 237 if (width == 0 || height == 0) { 238 Log.w(TAG, "Preview size should not be 0."); 239 return; 240 } 241 if (width > height) { 242 mAspectRatio = (float) width / height; 243 } else { 244 mAspectRatio = (float) height / width; 245 } 246 mHandler.sendEmptyMessage(UPDATE_TRANSFORM_MATRIX); 247 } 248 249 public int getPreviewWidth() { 250 return mPreviewWidth; 251 } 252 253 public int getPreviewHeight() { 254 return mPreviewHeight; 255 } 256 257 public void onScreenSizeChanged(int width, int height, int previewWidth, int previewHeight) { 258 setTransformMatrix(width, height); 259 } 260 261 private void setTransformMatrix(int width, int height) { 262 mMatrix = mTextureView.getTransform(mMatrix); 263 int orientation = CameraUtil.getDisplayRotation(mActivity); 264 float scaleX = 1f, scaleY = 1f; 265 float scaledTextureWidth, scaledTextureHeight; 266 if (width > height) { 267 scaledTextureWidth = Math.max(width, 268 (int) (height * mAspectRatio)); 269 scaledTextureHeight = Math.max(height, 270 (int)(width / mAspectRatio)); 271 } else { 272 scaledTextureWidth = Math.max(width, 273 (int) (height / mAspectRatio)); 274 scaledTextureHeight = Math.max(height, 275 (int) (width * mAspectRatio)); 276 } 277 278 if (mSurfaceTextureUncroppedWidth != scaledTextureWidth || 279 mSurfaceTextureUncroppedHeight != scaledTextureHeight) { 280 mSurfaceTextureUncroppedWidth = scaledTextureWidth; 281 mSurfaceTextureUncroppedHeight = scaledTextureHeight; 282 } 283 scaleX = scaledTextureWidth / width; 284 scaleY = scaledTextureHeight / height; 285 mMatrix.setScale(scaleX, scaleY, (float) width / 2, (float) height / 2); 286 mTextureView.setTransform(mMatrix); 287 288 if (mSurfaceView != null && mSurfaceView.getVisibility() == View.VISIBLE) { 289 LayoutParams lp = (LayoutParams) mSurfaceView.getLayoutParams(); 290 lp.width = (int) mSurfaceTextureUncroppedWidth; 291 lp.height = (int) mSurfaceTextureUncroppedHeight; 292 lp.gravity = Gravity.CENTER; 293 mSurfaceView.requestLayout(); 294 } 295 } 296 297 /** 298 * Starts a flash animation 299 */ 300 public void animateFlash() { 301 mAnimationManager.startFlashAnimation(mFlashOverlay); 302 } 303 304 /** 305 * Starts a capture animation 306 */ 307 public void animateCapture() { 308 Bitmap bitmap = null; 309 if (mTextureView != null) { 310 bitmap = mTextureView.getBitmap((int) mSurfaceTextureUncroppedWidth / 2, 311 (int) mSurfaceTextureUncroppedHeight / 2); 312 } 313 animateCapture(bitmap); 314 } 315 316 /** 317 * Starts a capture animation 318 * @param bitmap the captured image that we shrink and slide in the animation 319 */ 320 public void animateCapture(Bitmap bitmap) { 321 if (bitmap == null) { 322 Log.e(TAG, "No valid bitmap for capture animation."); 323 return; 324 } 325 } 326 327 /** 328 * Cancels on-going animations 329 */ 330 public void cancelAnimations() { 331 mAnimationManager.cancelAnimations(); 332 } 333 334 public void hideUI() { 335 336 } 337 338 public void showUI() { 339 340 } 341 342 public boolean arePreviewControlsVisible() { 343 return false; 344 } 345 346 public boolean collapseCameraControls() { 347 boolean ret = false; 348 if (mPopup != null) { 349 dismissPopup(false); 350 ret = true; 351 } 352 return ret; 353 } 354 355 public boolean removeTopLevelPopup() { 356 if (mPopup != null) { 357 dismissPopup(true); 358 return true; 359 } 360 return false; 361 } 362 363 public void enableCameraControls(boolean enable) { 364 if (mPieRenderer != null && mPieRenderer.showsItems()) { 365 mPieRenderer.hide(); 366 } 367 } 368 369 public void overrideSettings(final String... keyvalues) { 370 mVideoMenu.overrideSettings(keyvalues); 371 } 372 373 public void setOrientationIndicator(int orientation, boolean animation) { 374 // We change the orientation of the linearlayout only for phone UI 375 // because when in portrait the width is not enough. 376 if (mLabelsLinearLayout != null) { 377 if (((orientation / 90) & 1) == 0) { 378 mLabelsLinearLayout.setOrientation(LinearLayout.VERTICAL); 379 } else { 380 mLabelsLinearLayout.setOrientation(LinearLayout.HORIZONTAL); 381 } 382 } 383 mRecordingTimeRect.setOrientation(0, animation); 384 } 385 386 public SurfaceHolder getSurfaceHolder() { 387 return mSurfaceView.getHolder(); 388 } 389 390 public void hideSurfaceView() { 391 mSurfaceView.setVisibility(View.GONE); 392 mTextureView.setVisibility(View.VISIBLE); 393 setTransformMatrix(mPreviewWidth, mPreviewHeight); 394 } 395 396 public void showSurfaceView() { 397 mSurfaceView.setVisibility(View.VISIBLE); 398 mTextureView.setVisibility(View.GONE); 399 setTransformMatrix(mPreviewWidth, mPreviewHeight); 400 } 401 402 private void initializeOverlay() { 403 mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay); 404 if (mPieRenderer == null) { 405 mPieRenderer = new PieRenderer(mActivity); 406 mVideoMenu = new VideoMenu(mActivity, this, mPieRenderer); 407 mPieRenderer.setPieListener(this); 408 } 409 mRenderOverlay.addRenderer(mPieRenderer); 410 if (mZoomRenderer == null) { 411 mZoomRenderer = new ZoomRenderer(mActivity); 412 } 413 mRenderOverlay.addRenderer(mZoomRenderer); 414 mRenderOverlay.setGestures(null); 415 } 416 417 public void setPrefChangedListener(OnPreferenceChangedListener listener) { 418 mVideoMenu.setListener(listener); 419 } 420 421 private void initializeMiscControls() { 422 mReviewImage = (ImageView) mRootView.findViewById(R.id.review_image); 423 mRecordingTimeView = (TextView) mRootView.findViewById(R.id.recording_time); 424 mRecordingTimeRect = (RotateLayout) mRootView.findViewById(R.id.recording_time_rect); 425 mTimeLapseLabel = mRootView.findViewById(R.id.time_lapse_label); 426 // The R.id.labels can only be found in phone layout. 427 // That is, mLabelsLinearLayout should be null in tablet layout. 428 mLabelsLinearLayout = (LinearLayout) mRootView.findViewById(R.id.labels); 429 } 430 431 public void updateOnScreenIndicators(Parameters param, ComboPreferences prefs) { 432 433 } 434 435 public void setAspectRatio(double ratio) { 436 // mPreviewFrameLayout.setAspectRatio(ratio); 437 } 438 439 public void showTimeLapseUI(boolean enable) { 440 if (mTimeLapseLabel != null) { 441 mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.GONE); 442 } 443 } 444 445 private void openMenu() { 446 if (mPieRenderer != null) { 447 mPieRenderer.showInCenter(); 448 } 449 } 450 451 public void dismissPopup(boolean topLevelOnly) { 452 // In review mode, we do not want to bring up the camera UI 453 if (mController.isInReviewMode()) return; 454 if (mPopup != null) { 455 mPopup.dismiss(topLevelOnly); 456 } 457 } 458 459 private void popupDismissed() { 460 mPopup = null; 461 } 462 463 public void showPopup(AbstractSettingPopup popup) { 464 hideUI(); 465 466 if (mPopup != null) { 467 mPopup.dismiss(false); 468 } 469 mPopup = new SettingsPopup(popup); 470 } 471 472 public void onShowSwitcherPopup() { 473 hidePieRenderer(); 474 } 475 476 public boolean hidePieRenderer() { 477 if (mPieRenderer != null && mPieRenderer.showsItems()) { 478 mPieRenderer.hide(); 479 return true; 480 } 481 return false; 482 } 483 484 public void enableShutter(boolean enable) { 485 486 } 487 488 // PieListener 489 @Override 490 public void onPieOpened(int centerX, int centerY) { 491 setSwipingEnabled(false); 492 } 493 494 @Override 495 public void onPieClosed() { 496 setSwipingEnabled(true); 497 } 498 499 public void setSwipingEnabled(boolean enable) { 500 mActivity.setSwipingEnabled(enable); 501 } 502 503 public void showPreviewBorder(boolean enable) { 504 // TODO: mPreviewFrameLayout.showBorder(enable); 505 } 506 507 // SingleTapListener 508 // Preview area is touched. Take a picture. 509 @Override 510 public void onSingleTapUp(View view, int x, int y) { 511 mController.onSingleTapUp(view, x, y); 512 } 513 514 public void showRecordingUI(boolean recording) { 515 mRecordingStarted = recording; 516 if (recording) { 517 mRecordingTimeView.setText(""); 518 mRecordingTimeView.setVisibility(View.VISIBLE); 519 } else { 520 mRecordingTimeView.setVisibility(View.GONE); 521 } 522 } 523 524 public void showReviewImage(Bitmap bitmap) { 525 mReviewImage.setImageBitmap(bitmap); 526 mReviewImage.setVisibility(View.VISIBLE); 527 } 528 529 public void showReviewControls() { 530 CameraUtil.fadeIn(mReviewDoneButton); 531 CameraUtil.fadeIn(mReviewPlayButton); 532 mReviewImage.setVisibility(View.VISIBLE); 533 } 534 535 public void hideReviewUI() { 536 mReviewImage.setVisibility(View.GONE); 537 CameraUtil.fadeOut(mReviewDoneButton); 538 CameraUtil.fadeOut(mReviewPlayButton); 539 } 540 541 private void setShowMenu(boolean show) { 542 543 } 544 545 public void onPreviewFocusChanged(boolean previewFocused) { 546 if (previewFocused) { 547 showUI(); 548 } else { 549 hideUI(); 550 } 551 552 if (mRenderOverlay != null) { 553 // this can not happen in capture mode 554 mRenderOverlay.setVisibility(previewFocused ? View.VISIBLE : View.GONE); 555 } 556 setShowMenu(previewFocused); 557 } 558 559 public void initializePopup(PreferenceGroup pref) { 560 mVideoMenu.initialize(pref); 561 } 562 563 public void initializeZoom(Parameters param) { 564 mZoomMax = param.getMaxZoom(); 565 mZoomRatios = param.getZoomRatios(); 566 // Currently we use immediate zoom for fast zooming to get better UX and 567 // there is no plan to take advantage of the smooth zoom. 568 mZoomRenderer.setZoomMax(mZoomMax); 569 mZoomRenderer.setZoom(param.getZoom()); 570 mZoomRenderer.setZoomValue(mZoomRatios.get(param.getZoom())); 571 mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener()); 572 } 573 574 public void clickShutter() { 575 576 } 577 578 public void pressShutter(boolean pressed) { 579 580 } 581 582 public View getShutterButton() { 583 return null; 584 } 585 586 public void setRecordingTime(String text) { 587 mRecordingTimeView.setText(text); 588 } 589 590 public void setRecordingTimeTextColor(int color) { 591 mRecordingTimeView.setTextColor(color); 592 } 593 594 public boolean isVisible() { 595 return false; 596 } 597 598 private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener { 599 @Override 600 public void onZoomValueChanged(int index) { 601 int newZoom = mController.onZoomChanged(index); 602 if (mZoomRenderer != null) { 603 mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom)); 604 } 605 } 606 607 @Override 608 public void onZoomStart() { 609 } 610 611 @Override 612 public void onZoomEnd() { 613 } 614 } 615 616 public SurfaceTexture getSurfaceTexture() { 617 return mSurfaceTexture; 618 } 619 620 // SurfaceTexture callbacks 621 @Override 622 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 623 mSurfaceTexture = surface; 624 mController.onPreviewUIReady(); 625 } 626 627 @Override 628 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 629 mSurfaceTexture = null; 630 mController.onPreviewUIDestroyed(); 631 Log.d(TAG, "surfaceTexture is destroyed"); 632 return true; 633 } 634 635 @Override 636 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 637 } 638 639 @Override 640 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 641 // Make sure preview cover is hidden if preview data is available. 642 if (mPreviewCover.getVisibility() != View.GONE) { 643 mPreviewCover.setVisibility(View.GONE); 644 } 645 } 646 647 // SurfaceHolder callbacks 648 @Override 649 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 650 Log.v(TAG, "Surface changed. width=" + width + ". height=" + height); 651 } 652 653 @Override 654 public void surfaceCreated(SurfaceHolder holder) { 655 Log.v(TAG, "Surface created"); 656 } 657 658 @Override 659 public void surfaceDestroyed(SurfaceHolder holder) { 660 Log.v(TAG, "Surface destroyed"); 661 mController.stopPreview(); 662 } 663} 664