CameraAppUI.java revision 26aa92bf5942bfde16621c6833c14ba0e1ea8b5e
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.app; 18 19import android.accessibilityservice.AccessibilityServiceInfo; 20import android.content.Context; 21import android.content.res.Resources; 22import android.graphics.Bitmap; 23import android.graphics.Canvas; 24import android.graphics.Matrix; 25import android.graphics.Rect; 26import android.graphics.RectF; 27import android.graphics.SurfaceTexture; 28import android.hardware.display.DisplayManager; 29import android.util.CameraPerformanceTracker; 30import android.view.GestureDetector; 31import android.view.Gravity; 32import android.view.LayoutInflater; 33import android.view.MotionEvent; 34import android.view.TextureView; 35import android.view.View; 36import android.view.ViewConfiguration; 37import android.view.ViewGroup; 38import android.view.accessibility.AccessibilityManager; 39import android.widget.FrameLayout; 40 41import com.android.camera.AnimationManager; 42import com.android.camera.ButtonManager; 43import com.android.camera.CaptureLayoutHelper; 44import com.android.camera.ShutterButton; 45import com.android.camera.TextureViewHelper; 46import com.android.camera.debug.Log; 47import com.android.camera.filmstrip.FilmstripContentPanel; 48import com.android.camera.hardware.HardwareSpec; 49import com.android.camera.module.ModuleController; 50import com.android.camera.settings.SettingsManager; 51import com.android.camera.ui.AbstractTutorialOverlay; 52import com.android.camera.ui.BottomBar; 53import com.android.camera.ui.BottomBarModeOptionsWrapper; 54import com.android.camera.ui.CaptureAnimationOverlay; 55import com.android.camera.ui.GridLines; 56import com.android.camera.ui.MainActivityLayout; 57import com.android.camera.ui.ModeListView; 58import com.android.camera.ui.ModeTransitionView; 59import com.android.camera.ui.PreviewOverlay; 60import com.android.camera.ui.PreviewStatusListener; 61import com.android.camera.util.ApiHelper; 62import com.android.camera.util.CameraUtil; 63import com.android.camera.util.Gusterpolator; 64import com.android.camera.util.PhotoSphereHelper; 65import com.android.camera.widget.Cling; 66import com.android.camera.widget.FilmstripLayout; 67import com.android.camera.widget.IndicatorIconController; 68import com.android.camera.widget.ModeOptionsOverlay; 69import com.android.camera.widget.PeekView; 70import com.android.camera2.R; 71 72import java.util.List; 73 74/** 75 * CameraAppUI centralizes control of views shared across modules. Whereas module 76 * specific views will be handled in each Module UI. For example, we can now 77 * bring the flash animation and capture animation up from each module to app 78 * level, as these animations are largely the same for all modules. 79 * 80 * This class also serves to disambiguate touch events. It recognizes all the 81 * swipe gestures that happen on the preview by attaching a touch listener to 82 * a full-screen view on top of preview TextureView. Since CameraAppUI has knowledge 83 * of how swipe from each direction should be handled, it can then redirect these 84 * events to appropriate recipient views. 85 */ 86public class CameraAppUI implements ModeListView.ModeSwitchListener, 87 TextureView.SurfaceTextureListener, 88 ModeListView.ModeListOpenListener, 89 SettingsManager.OnSettingChangedListener, 90 ShutterButton.OnShutterButtonListener { 91 92 /** 93 * The bottom controls on the filmstrip. 94 */ 95 public static interface BottomPanel { 96 /** Values for the view state of the button. */ 97 public final int VIEWER_NONE = 0; 98 public final int VIEWER_PHOTO_SPHERE = 1; 99 public final int VIEWER_REFOCUS = 2; 100 public final int VIEWER_OTHER = 3; 101 102 /** 103 * Sets a new or replaces an existing listener for bottom control events. 104 */ 105 void setListener(Listener listener); 106 107 /** 108 * Sets cling for external viewer button. 109 */ 110 void setClingForViewer(int viewerType, Cling cling); 111 112 /** 113 * Clears cling for external viewer button. 114 */ 115 void clearClingForViewer(int viewerType); 116 117 /** 118 * Returns a cling for the specified viewer type. 119 */ 120 Cling getClingForViewer(int viewerType); 121 122 /** 123 * Set if the bottom controls are visible. 124 * @param visible {@code true} if visible. 125 */ 126 void setVisible(boolean visible); 127 128 /** 129 * @param visible Whether the button is visible. 130 */ 131 void setEditButtonVisibility(boolean visible); 132 133 /** 134 * @param enabled Whether the button is enabled. 135 */ 136 void setEditEnabled(boolean enabled); 137 138 /** 139 * Sets the visibility of the view-photosphere button. 140 * 141 * @param state one of {@link #VIEWER_NONE}, {@link #VIEWER_PHOTO_SPHERE}, 142 * {@link #VIEWER_REFOCUS}. 143 */ 144 void setViewerButtonVisibility(int state); 145 146 /** 147 * @param enabled Whether the button is enabled. 148 */ 149 void setViewEnabled(boolean enabled); 150 151 /** 152 * @param enabled Whether the button is enabled. 153 */ 154 void setTinyPlanetEnabled(boolean enabled); 155 156 /** 157 * @param visible Whether the button is visible. 158 */ 159 void setDeleteButtonVisibility(boolean visible); 160 161 /** 162 * @param enabled Whether the button is enabled. 163 */ 164 void setDeleteEnabled(boolean enabled); 165 166 /** 167 * @param visible Whether the button is visible. 168 */ 169 void setShareButtonVisibility(boolean visible); 170 171 /** 172 * @param enabled Whether the button is enabled. 173 */ 174 void setShareEnabled(boolean enabled); 175 176 /** 177 * Sets the texts for progress UI. 178 * 179 * @param text The text to show. 180 */ 181 void setProgressText(CharSequence text); 182 183 /** 184 * Sets the progress. 185 * 186 * @param progress The progress value. Should be between 0 and 100. 187 */ 188 void setProgress(int progress); 189 190 /** 191 * Replaces the progress UI with an error message. 192 */ 193 void showProgressError(CharSequence message); 194 195 /** 196 * Hide the progress error message. 197 */ 198 void hideProgressError(); 199 200 /** 201 * Shows the progress. 202 */ 203 void showProgress(); 204 205 /** 206 * Hides the progress. 207 */ 208 void hideProgress(); 209 210 /** 211 * Shows the controls. 212 */ 213 void showControls(); 214 215 /** 216 * Hides the controls. 217 */ 218 void hideControls(); 219 220 /** 221 * Classes implementing this interface can listen for events on the bottom 222 * controls. 223 */ 224 public static interface Listener { 225 /** 226 * Called when the user pressed the "view" button to e.g. view a photo 227 * sphere or RGBZ image. 228 */ 229 public void onExternalViewer(); 230 231 /** 232 * Called when the "edit" button is pressed. 233 */ 234 public void onEdit(); 235 236 /** 237 * Called when the "tiny planet" button is pressed. 238 */ 239 public void onTinyPlanet(); 240 241 /** 242 * Called when the "delete" button is pressed. 243 */ 244 public void onDelete(); 245 246 /** 247 * Called when the "share" button is pressed. 248 */ 249 public void onShare(); 250 251 /** 252 * Called when the progress error message is clicked. 253 */ 254 public void onProgressErrorClicked(); 255 } 256 } 257 258 /** 259 * BottomBarUISpec provides a structure for modules 260 * to specify their ideal bottom bar mode options layout. 261 * 262 * Once constructed by a module, this class should be 263 * treated as read only. 264 * 265 * The application then edits this spec according to 266 * hardware limitations and displays the final bottom 267 * bar ui. 268 */ 269 public static class BottomBarUISpec { 270 /** Mode options UI */ 271 272 /** 273 * Set true if the camera option should be enabled. 274 * If not set or false, and multiple cameras are supported, 275 * the camera option will be disabled. 276 * 277 * If multiple cameras are not supported, this preference 278 * is ignored and the camera option will not be visible. 279 */ 280 public boolean enableCamera; 281 282 /** 283 * Set true if the camera option should not be visible, regardless 284 * of hardware limitations. 285 */ 286 public boolean hideCamera; 287 288 /** 289 * Set true if the photo flash option should be enabled. 290 * If not set or false, the photo flash option will be 291 * disabled. 292 * 293 * If the hardware does not support multiple flash values, 294 * this preference is ignored and the flash option will 295 * be disabled. It will not be made invisible in order to 296 * preserve a consistent experience across devices and between 297 * front and back cameras. 298 */ 299 public boolean enableFlash; 300 301 /** 302 * Set true if the video flash option should be enabled. 303 * Same disable rules apply as the photo flash option. 304 */ 305 public boolean enableTorchFlash; 306 307 /** 308 * Set true if flash should not be visible, regardless of 309 * hardware limitations. 310 */ 311 public boolean hideFlash; 312 313 /** 314 * Set true if the hdr/hdr+ option should be enabled. 315 * If not set or false, the hdr/hdr+ option will be disabled. 316 * 317 * Hdr or hdr+ will be chosen based on hardware limitations, 318 * with hdr+ prefered. 319 * 320 * If hardware supports neither hdr nor hdr+, then the hdr/hdr+ 321 * will not be visible. 322 */ 323 public boolean enableHdr; 324 325 /** 326 * Set true if hdr/hdr+ should not be visible, regardless of 327 * hardware limitations. 328 */ 329 public boolean hideHdr; 330 331 /** 332 * Set true if grid lines should be visible. Not setting this 333 * causes grid lines to be disabled. This option is agnostic to 334 * the hardware. 335 */ 336 public boolean enableGridLines; 337 338 /** 339 * Set true if grid lines should not be visible. 340 */ 341 public boolean hideGridLines; 342 343 /** 344 * Set true if the panorama orientation option should be visible. 345 * 346 * This option is not constrained by hardware limitations. 347 */ 348 public boolean enablePanoOrientation; 349 350 public boolean enableExposureCompensation; 351 352 /** Intent UI */ 353 354 /** 355 * Set true if the intent ui cancel option should be visible. 356 */ 357 public boolean showCancel; 358 /** 359 * Set true if the intent ui done option should be visible. 360 */ 361 public boolean showDone; 362 /** 363 * Set true if the intent ui retake option should be visible. 364 */ 365 public boolean showRetake; 366 /** 367 * Set true if the intent ui review option should be visible. 368 */ 369 public boolean showReview; 370 371 /** Mode options callbacks */ 372 373 /** 374 * A {@link com.android.camera.ButtonManager.ButtonCallback} 375 * that will be executed when the camera option is pressed. This 376 * callback can be null. 377 */ 378 public ButtonManager.ButtonCallback cameraCallback; 379 380 /** 381 * A {@link com.android.camera.ButtonManager.ButtonCallback} 382 * that will be executed when the flash option is pressed. This 383 * callback can be null. 384 */ 385 public ButtonManager.ButtonCallback flashCallback; 386 387 /** 388 * A {@link com.android.camera.ButtonManager.ButtonCallback} 389 * that will be executed when the hdr/hdr+ option is pressed. This 390 * callback can be null. 391 */ 392 public ButtonManager.ButtonCallback hdrCallback; 393 394 /** 395 * A {@link com.android.camera.ButtonManager.ButtonCallback} 396 * that will be executed when the grid lines option is pressed. This 397 * callback can be null. 398 */ 399 public ButtonManager.ButtonCallback gridLinesCallback; 400 401 /** 402 * A {@link com.android.camera.ButtonManager.ButtonCallback} 403 * that will execute when the panorama orientation option is pressed. 404 * This callback can be null. 405 */ 406 public ButtonManager.ButtonCallback panoOrientationCallback; 407 408 /** Intent UI callbacks */ 409 410 /** 411 * A {@link android.view.View.OnClickListener} that will execute 412 * when the cancel option is pressed. This callback can be null. 413 */ 414 public View.OnClickListener cancelCallback; 415 416 /** 417 * A {@link android.view.View.OnClickListener} that will execute 418 * when the done option is pressed. This callback can be null. 419 */ 420 public View.OnClickListener doneCallback; 421 422 /** 423 * A {@link android.view.View.OnClickListener} that will execute 424 * when the retake option is pressed. This callback can be null. 425 */ 426 public View.OnClickListener retakeCallback; 427 428 /** 429 * A {@link android.view.View.OnClickListener} that will execute 430 * when the review option is pressed. This callback can be null. 431 */ 432 public View.OnClickListener reviewCallback; 433 434 /** 435 * A ExposureCompensationSetCallback that will execute 436 * when an expsosure button is pressed. This callback can be null. 437 */ 438 public interface ExposureCompensationSetCallback { 439 public void setExposure(int value); 440 } 441 public ExposureCompensationSetCallback exposureCompensationSetCallback; 442 443 /** 444 * Exposure compensation parameters. 445 */ 446 public int minExposureCompensation; 447 public int maxExposureCompensation; 448 public float exposureCompensationStep; 449 450 /** 451 * Whether self-timer is enabled. 452 */ 453 public boolean enableSelfTimer = false; 454 455 /** 456 * Whether the option for self-timer should show. If true and 457 * {@link #enableSelfTimer} is false, then the option should be shown 458 * disabled. 459 */ 460 public boolean showSelfTimer = false; 461 } 462 463 464 private final static Log.Tag TAG = new Log.Tag("CameraAppUI"); 465 466 private final AppController mController; 467 private final boolean mIsCaptureIntent; 468 private final AnimationManager mAnimationManager; 469 470 // Swipe states: 471 private final static int IDLE = 0; 472 private final static int SWIPE_UP = 1; 473 private final static int SWIPE_DOWN = 2; 474 private final static int SWIPE_LEFT = 3; 475 private final static int SWIPE_RIGHT = 4; 476 private boolean mSwipeEnabled = true; 477 478 // Shared Surface Texture properities. 479 private SurfaceTexture mSurface; 480 private int mSurfaceWidth; 481 private int mSurfaceHeight; 482 483 // Touch related measures: 484 private final int mSlop; 485 private final static int SWIPE_TIME_OUT_MS = 500; 486 487 // Mode cover states: 488 private final static int COVER_HIDDEN = 0; 489 private final static int COVER_SHOWN = 1; 490 private final static int COVER_WILL_HIDE_AT_NEXT_FRAME = 2; 491 private static final int COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE = 3; 492 493 /** 494 * Preview down-sample rate when taking a screenshot. 495 */ 496 private final static int DOWN_SAMPLE_RATE_FOR_SCREENSHOT = 2; 497 498 // App level views: 499 private final FrameLayout mCameraRootView; 500 private final ModeTransitionView mModeTransitionView; 501 private final MainActivityLayout mAppRootView; 502 private final ModeListView mModeListView; 503 private final FilmstripLayout mFilmstripLayout; 504 private TextureView mTextureView; 505 private FrameLayout mModuleUI; 506 private ShutterButton mShutterButton; 507 private View mLetterBoxer1; 508 private View mLetterBoxer2; 509 private BottomBar mBottomBar; 510 private ModeOptionsOverlay mModeOptionsOverlay; 511 private IndicatorIconController mIndicatorIconController; 512 private View mFocusOverlay; 513 private FrameLayout mTutorialsPlaceHolderWrapper; 514 private BottomBarModeOptionsWrapper mIndicatorBottomBarWrapper; 515 private TextureViewHelper mTextureViewHelper; 516 private final GestureDetector mGestureDetector; 517 private DisplayManager.DisplayListener mDisplayListener; 518 private int mLastRotation; 519 private int mSwipeState = IDLE; 520 private PreviewOverlay mPreviewOverlay; 521 private GridLines mGridLines; 522 private CaptureAnimationOverlay mCaptureOverlay; 523 private PreviewStatusListener mPreviewStatusListener; 524 private int mModeCoverState = COVER_HIDDEN; 525 private final FilmstripBottomPanel mFilmstripBottomControls; 526 private final FilmstripContentPanel mFilmstripPanel; 527 private Runnable mHideCoverRunnable; 528 private final View.OnLayoutChangeListener mPreviewLayoutChangeListener 529 = new View.OnLayoutChangeListener() { 530 @Override 531 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 532 int oldTop, int oldRight, int oldBottom) { 533 if (mPreviewStatusListener != null) { 534 mPreviewStatusListener.onPreviewLayoutChanged(v, left, top, right, bottom, oldLeft, 535 oldTop, oldRight, oldBottom); 536 } 537 } 538 }; 539 private View mModeOptionsToggle; 540 private final PeekView mPeekView; 541 private final CaptureLayoutHelper mCaptureLayoutHelper; 542 private boolean mAccessibilityEnabled; 543 private final View mAccessibilityAffordances; 544 545 /** 546 * Provides current preview frame and the controls/overlay from the module that 547 * are shown on top of the preview. 548 */ 549 public interface CameraModuleScreenShotProvider { 550 /** 551 * Returns the current preview frame down-sampled using the given down-sample 552 * factor. 553 * 554 * @param downSampleFactor the down sample factor for down sampling the 555 * preview frame. (e.g. a down sample factor of 556 * 2 means to scale down the preview frame to 1/2 557 * the width and height.) 558 * @return down-sampled preview frame 559 */ 560 public Bitmap getPreviewFrame(int downSampleFactor); 561 562 /** 563 * @return the controls and overlays that are currently showing on top of 564 * the preview drawn into a bitmap with no scaling applied. 565 */ 566 public Bitmap getPreviewOverlayAndControls(); 567 568 /** 569 * Returns a bitmap containing the current screenshot. 570 * 571 * @param previewDownSampleFactor the downsample factor applied on the 572 * preview frame when taking the screenshot 573 */ 574 public Bitmap getScreenShot(int previewDownSampleFactor); 575 } 576 577 /** 578 * This listener gets called when the size of the window (excluding the system 579 * decor such as status bar and nav bar) has changed. 580 */ 581 public interface NonDecorWindowSizeChangedListener { 582 public void onNonDecorWindowSizeChanged(int width, int height, int rotation); 583 } 584 585 private final CameraModuleScreenShotProvider mCameraModuleScreenShotProvider = 586 new CameraModuleScreenShotProvider() { 587 @Override 588 public Bitmap getPreviewFrame(int downSampleFactor) { 589 if (mCameraRootView == null || mTextureView == null) { 590 return null; 591 } 592 // Gets the bitmap from the preview TextureView. 593 Bitmap preview = mTextureViewHelper.getPreviewBitmap(downSampleFactor); 594 return preview; 595 } 596 597 @Override 598 public Bitmap getPreviewOverlayAndControls() { 599 Bitmap overlays = Bitmap.createBitmap(mCameraRootView.getWidth(), 600 mCameraRootView.getHeight(), Bitmap.Config.ARGB_8888); 601 Canvas canvas = new Canvas(overlays); 602 mCameraRootView.draw(canvas); 603 return overlays; 604 } 605 606 @Override 607 public Bitmap getScreenShot(int previewDownSampleFactor) { 608 Bitmap screenshot = Bitmap.createBitmap(mCameraRootView.getWidth(), 609 mCameraRootView.getHeight(), Bitmap.Config.ARGB_8888); 610 Canvas canvas = new Canvas(screenshot); 611 canvas.drawARGB(255, 0, 0, 0); 612 Bitmap preview = mTextureViewHelper.getPreviewBitmap(previewDownSampleFactor); 613 if (preview != null) { 614 canvas.drawBitmap(preview, null, mTextureViewHelper.getPreviewArea(), null); 615 } 616 Bitmap overlay = getPreviewOverlayAndControls(); 617 if (overlay != null) { 618 canvas.drawBitmap(overlay, 0f, 0f, null); 619 } 620 return screenshot; 621 } 622 }; 623 624 private long mCoverHiddenTime = -1; // System time when preview cover was hidden. 625 626 public long getCoverHiddenTime() { 627 return mCoverHiddenTime; 628 } 629 630 /** 631 * This resets the preview to have no applied transform matrix. 632 */ 633 public void clearPreviewTransform() { 634 mTextureViewHelper.clearTransform(); 635 } 636 637 public void updatePreviewAspectRatio(float aspectRatio) { 638 mTextureViewHelper.updateAspectRatio(aspectRatio); 639 } 640 641 642 /** 643 * Updates the preview matrix without altering it. 644 * 645 * @param matrix 646 * @param aspectRatio the desired aspect ratio for the preview. 647 */ 648 public void updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio) { 649 mTextureViewHelper.updateTransformFullScreen(matrix, aspectRatio); 650 } 651 652 /** 653 * @return the rect that will display the preview. 654 */ 655 public RectF getFullscreenRect() { 656 return mTextureViewHelper.getFullscreenRect(); 657 } 658 659 660 /** 661 * This is to support modules that calculate their own transform matrix because 662 * they need to use a transform matrix to rotate the preview. 663 * 664 * @param matrix transform matrix to be set on the TextureView 665 */ 666 public void updatePreviewTransform(Matrix matrix) { 667 mTextureViewHelper.updateTransform(matrix); 668 } 669 670 public interface AnimationFinishedListener { 671 public void onAnimationFinished(boolean success); 672 } 673 674 private class MyTouchListener implements View.OnTouchListener { 675 private boolean mScaleStarted = false; 676 @Override 677 public boolean onTouch(View v, MotionEvent event) { 678 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 679 mScaleStarted = false; 680 } else if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { 681 mScaleStarted = true; 682 } 683 return (!mScaleStarted) && mGestureDetector.onTouchEvent(event); 684 } 685 } 686 687 /** 688 * This gesture listener finds out the direction of the scroll gestures and 689 * sends them to CameraAppUI to do further handling. 690 */ 691 private class MyGestureListener extends GestureDetector.SimpleOnGestureListener { 692 private MotionEvent mDown; 693 694 @Override 695 public boolean onScroll(MotionEvent e1, MotionEvent ev, float distanceX, float distanceY) { 696 if (ev.getEventTime() - ev.getDownTime() > SWIPE_TIME_OUT_MS 697 || mSwipeState != IDLE 698 || mIsCaptureIntent 699 || !mSwipeEnabled) { 700 return false; 701 } 702 703 int deltaX = (int) (ev.getX() - mDown.getX()); 704 int deltaY = (int) (ev.getY() - mDown.getY()); 705 if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) { 706 if (Math.abs(deltaX) > mSlop || Math.abs(deltaY) > mSlop) { 707 // Calculate the direction of the swipe. 708 if (deltaX >= Math.abs(deltaY)) { 709 // Swipe right. 710 setSwipeState(SWIPE_RIGHT); 711 } else if (deltaX <= -Math.abs(deltaY)) { 712 // Swipe left. 713 setSwipeState(SWIPE_LEFT); 714 } 715 } 716 } 717 return true; 718 } 719 720 private void setSwipeState(int swipeState) { 721 mSwipeState = swipeState; 722 // Notify new swipe detected. 723 onSwipeDetected(swipeState); 724 } 725 726 @Override 727 public boolean onDown(MotionEvent ev) { 728 mDown = MotionEvent.obtain(ev); 729 mSwipeState = IDLE; 730 return false; 731 } 732 } 733 734 public CameraAppUI(AppController controller, MainActivityLayout appRootView, 735 boolean isCaptureIntent) { 736 mSlop = ViewConfiguration.get(controller.getAndroidContext()).getScaledTouchSlop(); 737 mController = controller; 738 mIsCaptureIntent = isCaptureIntent; 739 740 mAppRootView = appRootView; 741 mFilmstripLayout = (FilmstripLayout) appRootView.findViewById(R.id.filmstrip_layout); 742 mCameraRootView = (FrameLayout) appRootView.findViewById(R.id.camera_app_root); 743 mModeTransitionView = (ModeTransitionView) 744 mAppRootView.findViewById(R.id.mode_transition_view); 745 mFilmstripBottomControls = new FilmstripBottomPanel(controller, 746 (ViewGroup) mAppRootView.findViewById(R.id.filmstrip_bottom_panel)); 747 mFilmstripPanel = (FilmstripContentPanel) mAppRootView.findViewById(R.id.filmstrip_layout); 748 mGestureDetector = new GestureDetector(controller.getAndroidContext(), 749 new MyGestureListener()); 750 Resources res = controller.getAndroidContext().getResources(); 751 mCaptureLayoutHelper = new CaptureLayoutHelper( 752 res.getDimensionPixelSize(R.dimen.bottom_bar_height_min), 753 res.getDimensionPixelSize(R.dimen.bottom_bar_height_max), 754 res.getDimensionPixelSize(R.dimen.bottom_bar_height_optimal)); 755 mModeListView = (ModeListView) appRootView.findViewById(R.id.mode_list_layout); 756 if (mModeListView != null) { 757 mModeListView.setModeSwitchListener(this); 758 mModeListView.setModeListOpenListener(this); 759 mModeListView.setCameraModuleScreenShotProvider(mCameraModuleScreenShotProvider); 760 mModeListView.setCaptureLayoutHelper(mCaptureLayoutHelper); 761 boolean shouldShowSettingsCling = mController.getSettingsManager().getBoolean( 762 SettingsManager.SETTING_SHOULD_SHOW_SETTINGS_BUTTON_CLING); 763 mModeListView.setShouldShowSettingsCling(shouldShowSettingsCling); 764 } else { 765 Log.e(TAG, "Cannot find mode list in the view hierarchy"); 766 } 767 mAnimationManager = new AnimationManager(); 768 mPeekView = (PeekView) appRootView.findViewById(R.id.peek_view); 769 mAppRootView.setNonDecorWindowSizeChangedListener(mCaptureLayoutHelper); 770 initDisplayListener(); 771 mAccessibilityAffordances = mAppRootView.findViewById(R.id.accessibility_affordances); 772 View modeListToggle = mAppRootView.findViewById(R.id.accessibility_mode_toggle_button); 773 modeListToggle.setOnClickListener(new View.OnClickListener() { 774 @Override 775 public void onClick(View view) { 776 openModeList(); 777 } 778 }); 779 View filmstripToggle = mAppRootView.findViewById( 780 R.id.accessibility_filmstrip_toggle_button); 781 filmstripToggle.setOnClickListener(new View.OnClickListener() { 782 @Override 783 public void onClick(View view) { 784 showFilmstrip(); 785 } 786 }); 787 } 788 789 790 /** 791 * Freeze what is currently shown on screen until the next preview frame comes 792 * in. 793 */ 794 public void freezeScreenUntilPreviewReady() { 795 mModeTransitionView.setupModeCover(mCameraModuleScreenShotProvider 796 .getScreenShot(DOWN_SAMPLE_RATE_FOR_SCREENSHOT)); 797 mHideCoverRunnable = new Runnable() { 798 @Override 799 public void run() { 800 mModeTransitionView.hideImageCover(); 801 } 802 }; 803 mModeCoverState = COVER_SHOWN; 804 } 805 806 /** 807 * Creates a cling for the specific viewer and links the cling to the corresponding 808 * button for layout position. 809 * 810 * @param viewerType defines which viewer the cling is for. 811 */ 812 public void setupClingForViewer(int viewerType) { 813 if (viewerType == BottomPanel.VIEWER_REFOCUS) { 814 FrameLayout filmstripContent = (FrameLayout) mAppRootView 815 .findViewById(R.id.camera_filmstrip_content_layout); 816 if (filmstripContent != null) { 817 // Creates refocus cling. 818 LayoutInflater inflater = (LayoutInflater) mController.getAndroidContext() 819 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 820 Cling refocusCling = (Cling) inflater.inflate(R.layout.cling_widget, null, false); 821 // Sets instruction text in the cling. 822 refocusCling.setText(mController.getAndroidContext().getResources() 823 .getString(R.string.cling_text_for_refocus_editor_button)); 824 825 // Adds cling into view hierarchy. 826 int clingWidth = mController.getAndroidContext() 827 .getResources().getDimensionPixelSize(R.dimen.default_cling_width); 828 filmstripContent.addView(refocusCling, clingWidth, 829 ViewGroup.LayoutParams.WRAP_CONTENT); 830 mFilmstripBottomControls.setClingForViewer(viewerType, refocusCling); 831 } 832 } 833 } 834 835 /** 836 * Clears the listeners for the cling and remove it from the view hierarchy. 837 * 838 * @param viewerType defines which viewer the cling is for. 839 */ 840 public void clearClingForViewer(int viewerType) { 841 Cling clingToBeRemoved = mFilmstripBottomControls.getClingForViewer(viewerType); 842 if (clingToBeRemoved == null) { 843 // No cling is created for the specific viewer type. 844 return; 845 } 846 mFilmstripBottomControls.clearClingForViewer(viewerType); 847 clingToBeRemoved.setVisibility(View.GONE); 848 mAppRootView.removeView(clingToBeRemoved); 849 } 850 851 /** 852 * Enable or disable swipe gestures. We want to disable them e.g. while we 853 * record a video. 854 */ 855 public void setSwipeEnabled(boolean enabled) { 856 mSwipeEnabled = enabled; 857 // TODO: This can be removed once we come up with a new design for handling swipe 858 // on shutter button and mode options. (More details: b/13751653) 859 mAppRootView.setSwipeEnabled(enabled); 860 } 861 862 public void onDestroy() { 863 ((DisplayManager) mController.getAndroidContext() 864 .getSystemService(Context.DISPLAY_SERVICE)) 865 .unregisterDisplayListener(mDisplayListener); 866 } 867 868 /** 869 * Initializes the display listener to listen to display changes such as 870 * 180-degree rotation change, which will not have an onConfigurationChanged 871 * callback. 872 */ 873 private void initDisplayListener() { 874 if (ApiHelper.HAS_DISPLAY_LISTENER) { 875 mLastRotation = CameraUtil.getDisplayRotation(mController.getAndroidContext()); 876 877 mDisplayListener = new DisplayManager.DisplayListener() { 878 @Override 879 public void onDisplayAdded(int arg0) { 880 // Do nothing. 881 } 882 883 @Override 884 public void onDisplayChanged(int displayId) { 885 int rotation = CameraUtil.getDisplayRotation( 886 mController.getAndroidContext()); 887 if ((rotation - mLastRotation + 360) % 360 == 180 888 && mPreviewStatusListener != null) { 889 mPreviewStatusListener.onPreviewFlipped(); 890 mIndicatorBottomBarWrapper.requestLayout(); 891 mModeListView.requestLayout(); 892 mTextureView.requestLayout(); 893 } 894 mLastRotation = rotation; 895 } 896 897 @Override 898 public void onDisplayRemoved(int arg0) { 899 // Do nothing. 900 } 901 }; 902 903 ((DisplayManager) mController.getAndroidContext() 904 .getSystemService(Context.DISPLAY_SERVICE)) 905 .registerDisplayListener(mDisplayListener, null); 906 } 907 } 908 909 /** 910 * Redirects touch events to appropriate recipient views based on swipe direction. 911 * More specifically, swipe up and swipe down will be handled by the view that handles 912 * mode transition; swipe left will be send to filmstrip; swipe right will be redirected 913 * to mode list in order to bring up mode list. 914 */ 915 private void onSwipeDetected(int swipeState) { 916 if (swipeState == SWIPE_UP || swipeState == SWIPE_DOWN) { 917 // TODO: Polish quick switch after this release. 918 // Quick switch between modes. 919 int currentModuleIndex = mController.getCurrentModuleIndex(); 920 final int moduleToTransitionTo = 921 mController.getQuickSwitchToModuleId(currentModuleIndex); 922 if (currentModuleIndex != moduleToTransitionTo) { 923 mAppRootView.redirectTouchEventsTo(mModeTransitionView); 924 925 int shadeColorId = R.color.mode_cover_default_color; 926 int iconRes = CameraUtil.getCameraModeCoverIconResId(moduleToTransitionTo, 927 mController.getAndroidContext()); 928 929 AnimationFinishedListener listener = new AnimationFinishedListener() { 930 @Override 931 public void onAnimationFinished(boolean success) { 932 if (success) { 933 mHideCoverRunnable = new Runnable() { 934 @Override 935 public void run() { 936 mModeTransitionView.startPeepHoleAnimation(); 937 } 938 }; 939 mModeCoverState = COVER_SHOWN; 940 // Go to new module when the previous operation is successful. 941 mController.onModeSelected(moduleToTransitionTo); 942 } 943 } 944 }; 945 if (mSwipeState == SWIPE_UP) { 946 mModeTransitionView.prepareToPullUpShade(shadeColorId, iconRes, listener); 947 } else { 948 mModeTransitionView.prepareToPullDownShade(shadeColorId, iconRes, listener); 949 } 950 } 951 } else if (swipeState == SWIPE_LEFT) { 952 // Pass the touch sequence to filmstrip layout. 953 mAppRootView.redirectTouchEventsTo(mFilmstripLayout); 954 } else if (swipeState == SWIPE_RIGHT) { 955 // Pass the touch to mode switcher 956 mAppRootView.redirectTouchEventsTo(mModeListView); 957 } 958 } 959 960 /** 961 * Gets called when activity resumes in preview. 962 */ 963 public void resume() { 964 // Show mode theme cover until preview is ready 965 showModeCoverUntilPreviewReady(); 966 967 // Hide action bar first since we are in full screen mode first, and 968 // switch the system UI to lights-out mode. 969 mFilmstripPanel.hide(); 970 971 // Show UI that is meant to only be used when spoken feedback is 972 // enabled. 973 mAccessibilityEnabled = isSpokenFeedbackAccessibilityEnabled(); 974 mAccessibilityAffordances.setVisibility(mAccessibilityEnabled ? View.VISIBLE : View.GONE); 975 } 976 977 /** 978 * @return Whether any spoken feedback accessibility feature is currently 979 * enabled. 980 */ 981 private boolean isSpokenFeedbackAccessibilityEnabled() { 982 AccessibilityManager accessibilityManager = (AccessibilityManager) mController 983 .getAndroidContext().getSystemService(Context.ACCESSIBILITY_SERVICE); 984 List<AccessibilityServiceInfo> infos = accessibilityManager 985 .getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN); 986 return infos != null && !infos.isEmpty(); 987 } 988 989 /** 990 * Opens the mode list (e.g. because of the menu button being pressed) and 991 * adapts the rest of the UI. 992 */ 993 public void openModeList() { 994 mModeOptionsOverlay.closeModeOptions(); 995 mModeListView.onMenuPressed(); 996 } 997 998 /** 999 * A cover view showing the mode theme color and mode icon will be visible on 1000 * top of preview until preview is ready (i.e. camera preview is started and 1001 * the first frame has been received). 1002 */ 1003 private void showModeCoverUntilPreviewReady() { 1004 int modeId = mController.getCurrentModuleIndex(); 1005 int colorId = R.color.mode_cover_default_color;; 1006 int iconId = CameraUtil.getCameraModeCoverIconResId(modeId, mController.getAndroidContext()); 1007 mModeTransitionView.setupModeCover(colorId, iconId); 1008 mHideCoverRunnable = new Runnable() { 1009 @Override 1010 public void run() { 1011 mModeTransitionView.hideModeCover(null); 1012 showShimmyDelayed(); 1013 } 1014 }; 1015 mModeCoverState = COVER_SHOWN; 1016 } 1017 1018 private void showShimmyDelayed() { 1019 if (!mIsCaptureIntent) { 1020 // Show shimmy in SHIMMY_DELAY_MS 1021 mModeListView.showModeSwitcherHint(); 1022 } 1023 } 1024 1025 private void hideModeCover() { 1026 if (mHideCoverRunnable != null) { 1027 mAppRootView.post(mHideCoverRunnable); 1028 mHideCoverRunnable = null; 1029 } 1030 mModeCoverState = COVER_HIDDEN; 1031 if (mCoverHiddenTime < 0) { 1032 mCoverHiddenTime = System.currentTimeMillis(); 1033 } 1034 } 1035 1036 1037 public void onPreviewVisiblityChanged(int visibility) { 1038 if (visibility == ModuleController.VISIBILITY_HIDDEN) { 1039 setIndicatorBottomBarWrapperVisible(false); 1040 mAccessibilityAffordances.setVisibility(View.GONE); 1041 } else { 1042 setIndicatorBottomBarWrapperVisible(true); 1043 if (mAccessibilityEnabled) { 1044 mAccessibilityAffordances.setVisibility(View.VISIBLE); 1045 } else { 1046 mAccessibilityAffordances.setVisibility(View.GONE); 1047 } 1048 } 1049 } 1050 1051 /** 1052 * Call to stop the preview from being rendered. 1053 */ 1054 public void pausePreviewRendering() { 1055 mTextureView.setVisibility(View.INVISIBLE); 1056 } 1057 1058 /** 1059 * Call to begin rendering the preview again. 1060 */ 1061 public void resumePreviewRendering() { 1062 mTextureView.setVisibility(View.VISIBLE); 1063 } 1064 1065 @Override 1066 public void onOpenFullScreen() { 1067 // Do nothing. 1068 } 1069 1070 @Override 1071 public void onModeListOpenProgress(float progress) { 1072 progress = 1 - progress; 1073 float interpolatedProgress = Gusterpolator.INSTANCE.getInterpolation(progress); 1074 mModeOptionsToggle.setAlpha(interpolatedProgress); 1075 // Change shutter button alpha linearly based on the mode list open progress: 1076 // set the alpha to disabled alpha when list is fully open, to enabled alpha 1077 // when the list is fully closed. 1078 mShutterButton.setAlpha(progress * ShutterButton.ALPHA_WHEN_ENABLED 1079 + (1 - progress) * ShutterButton.ALPHA_WHEN_DISABLED); 1080 } 1081 1082 @Override 1083 public void onModeListClosed() { 1084 // Make sure the alpha on mode options ellipse is reset when mode drawer 1085 // is closed. 1086 mModeOptionsToggle.setAlpha(1f); 1087 mShutterButton.setAlpha(ShutterButton.ALPHA_WHEN_ENABLED); 1088 } 1089 1090 /** 1091 * Called when the back key is pressed. 1092 * 1093 * @return Whether the UI responded to the key event. 1094 */ 1095 public boolean onBackPressed() { 1096 if (mFilmstripLayout.getVisibility() == View.VISIBLE) { 1097 return mFilmstripLayout.onBackPressed(); 1098 } else { 1099 return mModeListView.onBackPressed(); 1100 } 1101 } 1102 1103 /** 1104 * Sets a {@link com.android.camera.ui.PreviewStatusListener} that 1105 * listens to SurfaceTexture changes. In addition, listeners are set on 1106 * dependent app ui elements. 1107 * 1108 * @param previewStatusListener the listener that gets notified when SurfaceTexture 1109 * changes 1110 */ 1111 public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) { 1112 mPreviewStatusListener = previewStatusListener; 1113 if (mPreviewStatusListener != null) { 1114 onPreviewListenerChanged(); 1115 } 1116 } 1117 1118 /** 1119 * When the PreviewStatusListener changes, listeners need to be 1120 * set on the following app ui elements: 1121 * {@link com.android.camera.ui.PreviewOverlay}, 1122 * {@link com.android.camera.ui.BottomBar}, 1123 * {@link com.android.camera.ui.IndicatorIconController}. 1124 */ 1125 private void onPreviewListenerChanged() { 1126 // Set a listener for recognizing preview gestures. 1127 GestureDetector.OnGestureListener gestureListener 1128 = mPreviewStatusListener.getGestureListener(); 1129 if (gestureListener != null) { 1130 mPreviewOverlay.setGestureListener(gestureListener); 1131 } 1132 View.OnTouchListener touchListener = mPreviewStatusListener.getTouchListener(); 1133 if (touchListener != null) { 1134 mPreviewOverlay.setTouchListener(touchListener); 1135 } 1136 1137 mTextureViewHelper.setAutoAdjustTransform( 1138 mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout()); 1139 } 1140 1141 /** 1142 * This method should be called in onCameraOpened. It defines CameraAppUI 1143 * specific changes that depend on the camera or camera settings. 1144 */ 1145 public void onChangeCamera() { 1146 ModuleController moduleController = mController.getCurrentModuleController(); 1147 applyModuleSpecs(moduleController.getHardwareSpec(), moduleController.getBottomBarSpec()); 1148 1149 if (mIndicatorIconController != null) { 1150 // Sync the settings state with the indicator state. 1151 mIndicatorIconController.syncIndicators(); 1152 } 1153 } 1154 1155 /** 1156 * Adds a listener to receive callbacks when preview area changes. 1157 */ 1158 public void addPreviewAreaChangedListener( 1159 PreviewStatusListener.PreviewAreaChangedListener listener) { 1160 mTextureViewHelper.addPreviewAreaSizeChangedListener(listener); 1161 } 1162 1163 /** 1164 * Removes a listener that receives callbacks when preview area changes. 1165 */ 1166 public void removePreviewAreaChangedListener( 1167 PreviewStatusListener.PreviewAreaChangedListener listener) { 1168 mTextureViewHelper.removePreviewAreaSizeChangedListener(listener); 1169 } 1170 1171 /** 1172 * This inflates generic_module layout, which contains all the shared views across 1173 * modules. Then each module inflates their own views in the given view group. For 1174 * now, this is called every time switching from a not-yet-refactored module to a 1175 * refactored module. In the future, this should only need to be done once per app 1176 * start. 1177 */ 1178 public void prepareModuleUI() { 1179 mController.getSettingsManager().addListener(this); 1180 mModuleUI = (FrameLayout) mCameraRootView.findViewById(R.id.module_layout); 1181 mTextureView = (TextureView) mCameraRootView.findViewById(R.id.preview_content); 1182 mTextureViewHelper = new TextureViewHelper(mTextureView, mCaptureLayoutHelper); 1183 mTextureViewHelper.setSurfaceTextureListener(this); 1184 mTextureViewHelper.setOnLayoutChangeListener(mPreviewLayoutChangeListener); 1185 1186 mBottomBar = (BottomBar) mCameraRootView.findViewById(R.id.bottom_bar); 1187 int unpressedColor = mController.getAndroidContext().getResources() 1188 .getColor(R.color.bottombar_unpressed); 1189 setBottomBarColor(unpressedColor); 1190 int pressedColor = mController.getAndroidContext().getResources() 1191 .getColor(R.color.bottombar_pressed); 1192 setBottomBarPressedColor(pressedColor); 1193 mBottomBar.setCaptureLayoutHelper(mCaptureLayoutHelper); 1194 1195 mModeOptionsOverlay 1196 = (ModeOptionsOverlay) mCameraRootView.findViewById(R.id.mode_options_overlay); 1197 1198 // Sets the visibility of the bottom bar and the mode options. 1199 resetBottomControls(mController.getCurrentModuleController(), 1200 mController.getCurrentModuleIndex()); 1201 mModeOptionsOverlay.setCaptureLayoutHelper(mCaptureLayoutHelper); 1202 1203 mShutterButton = (ShutterButton) mCameraRootView.findViewById(R.id.shutter_button); 1204 addShutterListener(mController.getCurrentModuleController()); 1205 addShutterListener(mModeOptionsOverlay); 1206 addShutterListener(this); 1207 1208 mLetterBoxer1 = mCameraRootView.findViewById(R.id.leftLetterBoxer1); 1209 mLetterBoxer2 = mCameraRootView.findViewById(R.id.leftLetterBoxer2); 1210 1211 mGridLines = (GridLines) mCameraRootView.findViewById(R.id.grid_lines); 1212 mTextureViewHelper.addPreviewAreaSizeChangedListener(mGridLines); 1213 1214 mPreviewOverlay = (PreviewOverlay) mCameraRootView.findViewById(R.id.preview_overlay); 1215 mPreviewOverlay.setOnTouchListener(new MyTouchListener()); 1216 mPreviewOverlay.setOnPreviewTouchedListener(mModeOptionsOverlay); 1217 1218 mCaptureOverlay = (CaptureAnimationOverlay) 1219 mCameraRootView.findViewById(R.id.capture_overlay); 1220 mTextureViewHelper.addPreviewAreaSizeChangedListener(mPreviewOverlay); 1221 mTextureViewHelper.addPreviewAreaSizeChangedListener(mCaptureOverlay); 1222 1223 if (mIndicatorIconController == null) { 1224 mIndicatorIconController = 1225 new IndicatorIconController(mController, mAppRootView); 1226 } 1227 1228 mController.getButtonManager().load(mCameraRootView); 1229 mController.getButtonManager().setListener(mIndicatorIconController); 1230 mController.getSettingsManager().addListener(mIndicatorIconController); 1231 1232 mModeOptionsToggle = mCameraRootView.findViewById(R.id.mode_options_toggle); 1233 mFocusOverlay = mCameraRootView.findViewById(R.id.focus_overlay); 1234 mTutorialsPlaceHolderWrapper = (FrameLayout) mCameraRootView 1235 .findViewById(R.id.tutorials_placeholder_wrapper); 1236 mIndicatorBottomBarWrapper = (BottomBarModeOptionsWrapper) mAppRootView 1237 .findViewById(R.id.indicator_bottombar_wrapper); 1238 mIndicatorBottomBarWrapper.setCaptureLayoutHelper(mCaptureLayoutHelper); 1239 mTextureViewHelper.addPreviewAreaSizeChangedListener( 1240 new PreviewStatusListener.PreviewAreaChangedListener() { 1241 @Override 1242 public void onPreviewAreaChanged(RectF previewArea) { 1243 mPeekView.setTranslationX(previewArea.right - mAppRootView.getRight()); 1244 } 1245 }); 1246 1247 mTextureViewHelper.addPreviewAreaSizeChangedListener(mModeListView); 1248 mTextureViewHelper.addAspectRatioChangedListener( 1249 new PreviewStatusListener.PreviewAspectRatioChangedListener() { 1250 @Override 1251 public void onPreviewAspectRatioChanged(float aspectRatio) { 1252 mModeOptionsOverlay.requestLayout(); 1253 mBottomBar.requestLayout(); 1254 } 1255 } 1256 ); 1257 } 1258 1259 /** 1260 * Called indirectly from each module in their initialization to get a view group 1261 * to inflate the module specific views in. 1262 * 1263 * @return a view group for modules to attach views to 1264 */ 1265 public FrameLayout getModuleRootView() { 1266 // TODO: Change it to mModuleUI when refactor is done 1267 return mCameraRootView; 1268 } 1269 1270 /** 1271 * Remove all the module specific views. 1272 */ 1273 public void clearModuleUI() { 1274 if (mModuleUI != null) { 1275 mModuleUI.removeAllViews(); 1276 } 1277 removeShutterListener(mController.getCurrentModuleController()); 1278 mTutorialsPlaceHolderWrapper.removeAllViews(); 1279 mTutorialsPlaceHolderWrapper.setVisibility(View.GONE); 1280 1281 setShutterButtonEnabled(true); 1282 mPreviewStatusListener = null; 1283 mPreviewOverlay.reset(); 1284 mFocusOverlay.setVisibility(View.INVISIBLE); 1285 } 1286 1287 /** 1288 * Gets called when preview is ready to start. It sets up one shot preview callback 1289 * in order to receive a callback when the preview frame is available, so that 1290 * the preview cover can be hidden to reveal preview. 1291 * 1292 * An alternative for getting the timing to hide preview cover is through 1293 * {@link CameraAppUI#onSurfaceTextureUpdated(android.graphics.SurfaceTexture)}, 1294 * which is less accurate but therefore is the fallback for modules that manage 1295 * their own preview callbacks (as setting one preview callback will override 1296 * any other installed preview callbacks), or use camera2 API. 1297 */ 1298 public void onPreviewReadyToStart() { 1299 if (mModeCoverState == COVER_SHOWN) { 1300 mModeCoverState = COVER_WILL_HIDE_AT_NEXT_FRAME; 1301 mController.setupOneShotPreviewListener(); 1302 } 1303 } 1304 1305 /** 1306 * Gets called when preview is started. 1307 */ 1308 public void onPreviewStarted() { 1309 if (mModeCoverState == COVER_SHOWN) { 1310 mModeCoverState = COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE; 1311 } 1312 enableModeOptions(); 1313 } 1314 1315 /** 1316 * Gets notified when next preview frame comes in. 1317 */ 1318 public void onNewPreviewFrame() { 1319 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME); 1320 hideModeCover(); 1321 mModeCoverState = COVER_HIDDEN; 1322 } 1323 1324 /** 1325 * Set the mode options toggle clickable. 1326 */ 1327 public void enableModeOptions() { 1328 /* 1329 * For modules using camera 1 api, this gets called in 1330 * onSurfaceTextureUpdated whenever the preview gets stopped and 1331 * started after each capture. This also takes care of the 1332 * case where the mode options might be unclickable when we 1333 * switch modes 1334 * 1335 * For modules using camera 2 api, they're required to call this 1336 * method when a capture is "completed". Unfortunately this differs 1337 * per module implementation. 1338 */ 1339 mModeOptionsOverlay.setToggleClickable(true); 1340 } 1341 1342 @Override 1343 public void onShutterButtonClick() { 1344 /* 1345 * Set the mode options toggle unclickable, generally 1346 * throughout the app, whenever the shutter button is clicked. 1347 * 1348 * This could be done in the OnShutterButtonListener of the 1349 * ModeOptionsOverlay, but since it is very important that we 1350 * can clearly see when the toggle becomes clickable again, 1351 * keep all of that logic at this level. 1352 */ 1353 mModeOptionsOverlay.setToggleClickable(false); 1354 } 1355 1356 @Override 1357 public void onShutterButtonFocus(boolean pressed) { 1358 // noop 1359 } 1360 1361 /** 1362 * Gets called when a mode is selected from {@link com.android.camera.ui.ModeListView} 1363 * 1364 * @param modeIndex mode index of the selected mode 1365 */ 1366 @Override 1367 public void onModeSelected(int modeIndex) { 1368 mHideCoverRunnable = new Runnable() { 1369 @Override 1370 public void run() { 1371 mModeListView.startModeSelectionAnimation(); 1372 } 1373 }; 1374 mModeCoverState = COVER_SHOWN; 1375 1376 int lastIndex = mController.getCurrentModuleIndex(); 1377 mController.onModeSelected(modeIndex); 1378 int currentIndex = mController.getCurrentModuleIndex(); 1379 1380 if (lastIndex == currentIndex) { 1381 hideModeCover(); 1382 } 1383 } 1384 1385 @Override 1386 public void onSettingsSelected() { 1387 mController.getSettingsManager() 1388 .setBoolean(SettingsManager.SETTING_SHOULD_SHOW_SETTINGS_BUTTON_CLING, false); 1389 mModeListView.setShouldShowSettingsCling(false); 1390 mController.onSettingsSelected(); 1391 } 1392 1393 @Override 1394 public int getCurrentModeIndex() { 1395 return mController.getCurrentModuleIndex(); 1396 } 1397 1398 /********************** Capture animation **********************/ 1399 /* TODO: This session is subject to UX changes. In addition to the generic 1400 flash animation and post capture animation, consider designating a parameter 1401 for specifying the type of animation, as well as an animation finished listener 1402 so that modules can have more knowledge of the status of the animation. */ 1403 1404 /** 1405 * Starts the filmstrip peek animation. 1406 * 1407 * @param bitmap The bitmap to show. 1408 * @param strong Whether the animation shows more portion of the bitmap or 1409 * not. 1410 */ 1411 public void startPeekAnimation(Bitmap bitmap, boolean strong) { 1412 if (mFilmstripLayout.getVisibility() == View.VISIBLE) { 1413 return; 1414 } 1415 mPeekView.startPeekAnimation(bitmap, strong); 1416 } 1417 1418 /** 1419 * Starts the pre-capture animation. 1420 */ 1421 public void startPreCaptureAnimation() { 1422 mCaptureOverlay.startFlashAnimation(); 1423 } 1424 1425 /** 1426 * Cancels the pre-capture animation. 1427 */ 1428 public void cancelPreCaptureAnimation() { 1429 mAnimationManager.cancelAnimations(); 1430 } 1431 1432 /** 1433 * Cancels the post-capture animation. 1434 */ 1435 public void cancelPostCaptureAnimation() { 1436 mAnimationManager.cancelAnimations(); 1437 } 1438 1439 public FilmstripContentPanel getFilmstripContentPanel() { 1440 return mFilmstripPanel; 1441 } 1442 1443 /** 1444 * @return The {@link com.android.camera.app.CameraAppUI.BottomPanel} on the 1445 * bottom of the filmstrip. 1446 */ 1447 public BottomPanel getFilmstripBottomControls() { 1448 return mFilmstripBottomControls; 1449 } 1450 1451 /** 1452 * @param listener The listener for bottom controls. 1453 */ 1454 public void setFilmstripBottomControlsListener(BottomPanel.Listener listener) { 1455 mFilmstripBottomControls.setListener(listener); 1456 } 1457 1458 /***************************SurfaceTexture Api and Listener*********************************/ 1459 1460 /** 1461 * Return the shared surface texture. 1462 */ 1463 public SurfaceTexture getSurfaceTexture() { 1464 return mSurface; 1465 } 1466 1467 /** 1468 * Return the shared {@link android.graphics.SurfaceTexture}'s width. 1469 */ 1470 public int getSurfaceWidth() { 1471 return mSurfaceWidth; 1472 } 1473 1474 /** 1475 * Return the shared {@link android.graphics.SurfaceTexture}'s height. 1476 */ 1477 public int getSurfaceHeight() { 1478 return mSurfaceHeight; 1479 } 1480 1481 @Override 1482 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 1483 mSurface = surface; 1484 mSurfaceWidth = width; 1485 mSurfaceHeight = height; 1486 Log.v(TAG, "SurfaceTexture is available"); 1487 if (mPreviewStatusListener != null) { 1488 mPreviewStatusListener.onSurfaceTextureAvailable(surface, width, height); 1489 } 1490 enableModeOptions(); 1491 } 1492 1493 @Override 1494 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 1495 mSurface = surface; 1496 mSurfaceWidth = width; 1497 mSurfaceHeight = height; 1498 if (mPreviewStatusListener != null) { 1499 mPreviewStatusListener.onSurfaceTextureSizeChanged(surface, width, height); 1500 } 1501 } 1502 1503 @Override 1504 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 1505 mSurface = null; 1506 Log.v(TAG, "SurfaceTexture is destroyed"); 1507 if (mPreviewStatusListener != null) { 1508 return mPreviewStatusListener.onSurfaceTextureDestroyed(surface); 1509 } 1510 return false; 1511 } 1512 1513 @Override 1514 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 1515 mSurface = surface; 1516 if (mModeCoverState == COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE) { 1517 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME); 1518 hideModeCover(); 1519 mModeCoverState = COVER_HIDDEN; 1520 } 1521 if (mPreviewStatusListener != null) { 1522 mPreviewStatusListener.onSurfaceTextureUpdated(surface); 1523 } 1524 } 1525 1526 /****************************Grid lines api ******************************/ 1527 1528 /** 1529 * Show a set of evenly spaced lines over the preview. The number 1530 * of lines horizontally and vertically is determined by 1531 * {@link com.android.camera.ui.GridLines}. 1532 */ 1533 public void showGridLines() { 1534 if (mGridLines != null) { 1535 mGridLines.setVisibility(View.VISIBLE); 1536 } 1537 } 1538 1539 /** 1540 * Hide the set of evenly spaced grid lines overlaying the preview. 1541 */ 1542 public void hideGridLines() { 1543 if (mGridLines != null) { 1544 mGridLines.setVisibility(View.INVISIBLE); 1545 } 1546 } 1547 1548 /** 1549 * Return a callback which shows or hide the preview grid lines 1550 * depending on whether the grid lines setting is set on. 1551 */ 1552 public ButtonManager.ButtonCallback getGridLinesCallback() { 1553 return new ButtonManager.ButtonCallback() { 1554 @Override 1555 public void onStateChanged(int state) { 1556 if (mController.getSettingsManager().areGridLinesOn()) { 1557 showGridLines(); 1558 } else { 1559 hideGridLines(); 1560 } 1561 } 1562 }; 1563 } 1564 1565 /***************************Mode options api *****************************/ 1566 1567 /** 1568 * Set the mode options visible. 1569 */ 1570 public void showModeOptions() { 1571 /* Make mode options clickable. */ 1572 enableModeOptions(); 1573 mModeOptionsOverlay.setVisibility(View.VISIBLE); 1574 } 1575 1576 /** 1577 * Set the mode options invisible. This is necessary for modes 1578 * that don't show a bottom bar for the capture UI. 1579 */ 1580 public void hideModeOptions() { 1581 mModeOptionsOverlay.setVisibility(View.INVISIBLE); 1582 } 1583 1584 /****************************Bottom bar api ******************************/ 1585 1586 /** 1587 * Sets up the bottom bar and mode options with the correct 1588 * shutter button and visibility based on the current module. 1589 */ 1590 public void resetBottomControls(ModuleController module, int moduleIndex) { 1591 if (areBottomControlsUsed(module)) { 1592 setBottomBarShutterIcon(moduleIndex); 1593 mCaptureLayoutHelper.setShowBottomBar(true); 1594 } else { 1595 mCaptureLayoutHelper.setShowBottomBar(false); 1596 } 1597 } 1598 1599 /** 1600 * Show or hide the mode options and bottom bar, based on 1601 * whether the current module is using the bottom bar. Returns 1602 * whether the mode options and bottom bar are used. 1603 */ 1604 private boolean areBottomControlsUsed(ModuleController module) { 1605 if (module.isUsingBottomBar()) { 1606 showBottomBar(); 1607 showModeOptions(); 1608 return true; 1609 } else { 1610 hideBottomBar(); 1611 hideModeOptions(); 1612 return false; 1613 } 1614 } 1615 1616 /** 1617 * Set the bottom bar visible. 1618 */ 1619 public void showBottomBar() { 1620 mBottomBar.setVisibility(View.VISIBLE); 1621 } 1622 1623 /** 1624 * Set the bottom bar invisible. 1625 */ 1626 public void hideBottomBar() { 1627 mBottomBar.setVisibility(View.INVISIBLE); 1628 } 1629 1630 /** 1631 * Sets the color of the bottom bar. 1632 */ 1633 public void setBottomBarColor(int colorId) { 1634 mBottomBar.setBackgroundColor(colorId); 1635 } 1636 1637 /** 1638 * Sets the pressed color of the bottom bar. 1639 */ 1640 public void setBottomBarPressedColor(int colorId) { 1641 mBottomBar.setBackgroundPressedColor(colorId); 1642 } 1643 1644 /** 1645 * Sets the shutter button icon on the bottom bar, based on 1646 * the mode index. 1647 */ 1648 public void setBottomBarShutterIcon(int modeIndex) { 1649 int shutterIconId = CameraUtil.getCameraShutterIconId(modeIndex, 1650 mController.getAndroidContext()); 1651 mBottomBar.setShutterButtonIcon(shutterIconId); 1652 } 1653 1654 public void animateBottomBarToVideoStop(int shutterIconId) { 1655 mBottomBar.animateToVideoStop(shutterIconId); 1656 } 1657 1658 public void animateBottomBarToFullSize(int shutterIconId) { 1659 mBottomBar.animateToFullSize(shutterIconId); 1660 } 1661 1662 public void setShutterButtonEnabled(boolean enabled) { 1663 mBottomBar.setShutterButtonEnabled(enabled); 1664 } 1665 1666 public boolean isShutterButtonEnabled() { 1667 return mBottomBar.isShutterButtonEnabled(); 1668 } 1669 1670 public void setIndicatorBottomBarWrapperVisible(boolean visible) { 1671 mIndicatorBottomBarWrapper.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); 1672 } 1673 1674 /** 1675 * Set the visibility of the bottom bar. 1676 */ 1677 // TODO: needed for when panorama is managed by the generic module ui. 1678 public void setBottomBarVisible(boolean visible) { 1679 mBottomBar.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); 1680 } 1681 1682 /** 1683 * Add a {@link #ShutterButton.OnShutterButtonListener} to the shutter button. 1684 */ 1685 public void addShutterListener(ShutterButton.OnShutterButtonListener listener) { 1686 mShutterButton.addOnShutterButtonListener(listener); 1687 } 1688 1689 1690 /** 1691 * This adds letterboxing around the preview, one on each side 1692 * 1693 * @param width the width in pixels of each letterboxing cover 1694 */ 1695 public void addLetterboxing(int width) { 1696 FrameLayout.LayoutParams params1 = (FrameLayout.LayoutParams) mLetterBoxer1 1697 .getLayoutParams(); 1698 FrameLayout.LayoutParams params2 = (FrameLayout.LayoutParams) mLetterBoxer2 1699 .getLayoutParams(); 1700 1701 if (mCameraRootView.getWidth() < mCameraRootView.getHeight()) { 1702 params1.width = width; 1703 params1.height = mCameraRootView.getHeight(); 1704 params1.gravity = Gravity.LEFT; 1705 mLetterBoxer1.setVisibility(View.VISIBLE); 1706 1707 params2.width = width; 1708 params2.height = mCameraRootView.getHeight(); 1709 params2.gravity = Gravity.RIGHT; 1710 mLetterBoxer2.setVisibility(View.VISIBLE); 1711 } else { 1712 params1.height = width; 1713 params1.width = mCameraRootView.getWidth(); 1714 params1.gravity = Gravity.TOP; 1715 mLetterBoxer1.setVisibility(View.VISIBLE); 1716 1717 params2.height = width; 1718 params2.width = mCameraRootView.getWidth(); 1719 params2.gravity = Gravity.BOTTOM; 1720 mLetterBoxer2.setVisibility(View.VISIBLE); 1721 } 1722 } 1723 1724 /** 1725 * Remove the letter boxing strips if they happen to be present. 1726 */ 1727 public void hideLetterboxing() { 1728 mLetterBoxer1.setVisibility(View.GONE); 1729 mLetterBoxer2.setVisibility(View.GONE); 1730 } 1731 1732 /** 1733 * Remove a {@link #ShutterButton.OnShutterButtonListener} from the shutter button. 1734 */ 1735 public void removeShutterListener(ShutterButton.OnShutterButtonListener listener) { 1736 mShutterButton.removeOnShutterButtonListener(listener); 1737 } 1738 1739 /** 1740 * Performs a transition to the capture layout of the bottom bar. 1741 */ 1742 public void transitionToCapture() { 1743 ModuleController moduleController = mController.getCurrentModuleController(); 1744 applyModuleSpecs(moduleController.getHardwareSpec(), 1745 moduleController.getBottomBarSpec()); 1746 mBottomBar.transitionToCapture(); 1747 } 1748 1749 /** 1750 * Displays the Cancel button instead of the capture button. 1751 */ 1752 public void transitionToCancel() { 1753 ModuleController moduleController = mController.getCurrentModuleController(); 1754 applyModuleSpecs(moduleController.getHardwareSpec(), 1755 moduleController.getBottomBarSpec()); 1756 mBottomBar.transitionToCancel(); 1757 } 1758 1759 /** 1760 * Performs a transition to the global intent layout. 1761 */ 1762 public void transitionToIntentCaptureLayout() { 1763 ModuleController moduleController = mController.getCurrentModuleController(); 1764 applyModuleSpecs(moduleController.getHardwareSpec(), 1765 moduleController.getBottomBarSpec()); 1766 mBottomBar.transitionToIntentCaptureLayout(); 1767 } 1768 1769 /** 1770 * Performs a transition to the global intent review layout. 1771 */ 1772 public void transitionToIntentReviewLayout() { 1773 ModuleController moduleController = mController.getCurrentModuleController(); 1774 applyModuleSpecs(moduleController.getHardwareSpec(), 1775 moduleController.getBottomBarSpec()); 1776 mBottomBar.transitionToIntentReviewLayout(); 1777 } 1778 1779 @Override 1780 public void onSettingChanged(SettingsManager settingsManager, int id) { 1781 // Update the mode options based on the hardware spec, 1782 // when hdr changes to prevent flash from getting out of sync. 1783 if (id == SettingsManager.SETTING_CAMERA_HDR) { 1784 ModuleController moduleController = mController.getCurrentModuleController(); 1785 applyModuleSpecs(moduleController.getHardwareSpec(), 1786 moduleController.getBottomBarSpec()); 1787 } 1788 } 1789 1790 /** 1791 * Applies a {@link com.android.camera.CameraAppUI.BottomBarUISpec} 1792 * to the bottom bar mode options based on limitations from a 1793 * {@link com.android.camera.hardware.HardwareSpec}. 1794 * 1795 * Options not supported by the hardware are either hidden 1796 * or disabled, depending on the option. 1797 * 1798 * Otherwise, the option is fully enabled and clickable. 1799 */ 1800 public void applyModuleSpecs(final HardwareSpec hardwareSpec, 1801 final BottomBarUISpec bottomBarSpec) { 1802 if (hardwareSpec == null || bottomBarSpec == null) { 1803 return; 1804 } 1805 1806 ButtonManager buttonManager = mController.getButtonManager(); 1807 SettingsManager settingsManager = mController.getSettingsManager(); 1808 1809 buttonManager.setToInitialState(); 1810 1811 /** Standard mode options */ 1812 if (hardwareSpec.isFrontCameraSupported()) { 1813 if (bottomBarSpec.enableCamera) { 1814 buttonManager.initializeButton(ButtonManager.BUTTON_CAMERA, 1815 bottomBarSpec.cameraCallback); 1816 } else { 1817 buttonManager.disableButton(ButtonManager.BUTTON_CAMERA); 1818 } 1819 } else { 1820 // Hide camera icon if front camera not available. 1821 buttonManager.hideButton(ButtonManager.BUTTON_CAMERA); 1822 } 1823 1824 boolean flashBackCamera = mController.getSettingsManager().getBoolean( 1825 SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA); 1826 if (bottomBarSpec.hideFlash || !flashBackCamera) { 1827 buttonManager.hideButton(ButtonManager.BUTTON_FLASH); 1828 } else { 1829 if (hardwareSpec.isFlashSupported()) { 1830 if (bottomBarSpec.enableFlash) { 1831 buttonManager.initializeButton(ButtonManager.BUTTON_FLASH, bottomBarSpec.flashCallback); 1832 } else if (bottomBarSpec.enableTorchFlash) { 1833 buttonManager.initializeButton(ButtonManager.BUTTON_TORCH, bottomBarSpec.flashCallback); 1834 } else { 1835 buttonManager.disableButton(ButtonManager.BUTTON_FLASH); 1836 } 1837 } else { 1838 // Disable flash icon if not supported by the hardware. 1839 buttonManager.disableButton(ButtonManager.BUTTON_FLASH); 1840 } 1841 } 1842 1843 if (bottomBarSpec.hideHdr || mIsCaptureIntent) { 1844 // Force hide hdr or hdr plus icon. 1845 buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS); 1846 } else { 1847 if (hardwareSpec.isHdrPlusSupported()) { 1848 if (bottomBarSpec.enableHdr && settingsManager.isCameraBackFacing()) { 1849 buttonManager.initializeButton(ButtonManager.BUTTON_HDRPLUS, 1850 bottomBarSpec.hdrCallback); 1851 } else { 1852 buttonManager.disableButton(ButtonManager.BUTTON_HDRPLUS); 1853 } 1854 } else if (hardwareSpec.isHdrSupported()) { 1855 if (bottomBarSpec.enableHdr && settingsManager.isCameraBackFacing()) { 1856 buttonManager.initializeButton(ButtonManager.BUTTON_HDR, 1857 bottomBarSpec.hdrCallback); 1858 } else { 1859 buttonManager.disableButton(ButtonManager.BUTTON_HDR); 1860 } 1861 } else { 1862 // Hide hdr plus or hdr icon if neither are supported. 1863 buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS); 1864 } 1865 } 1866 1867 if (bottomBarSpec.hideGridLines) { 1868 // Force hide grid lines icon. 1869 buttonManager.hideButton(ButtonManager.BUTTON_GRID_LINES); 1870 hideGridLines(); 1871 } else { 1872 if (bottomBarSpec.enableGridLines) { 1873 buttonManager.initializeButton(ButtonManager.BUTTON_GRID_LINES, 1874 bottomBarSpec.gridLinesCallback != null ? 1875 bottomBarSpec.gridLinesCallback : getGridLinesCallback() 1876 ); 1877 } else { 1878 buttonManager.disableButton(ButtonManager.BUTTON_GRID_LINES); 1879 hideGridLines(); 1880 } 1881 } 1882 1883 if (bottomBarSpec.enableSelfTimer) { 1884 buttonManager.initializeButton(ButtonManager.BUTTON_COUNTDOWN, null); 1885 } else { 1886 if (bottomBarSpec.showSelfTimer) { 1887 buttonManager.disableButton(ButtonManager.BUTTON_COUNTDOWN); 1888 } else { 1889 buttonManager.hideButton(ButtonManager.BUTTON_COUNTDOWN); 1890 } 1891 } 1892 1893 if (bottomBarSpec.enablePanoOrientation 1894 && PhotoSphereHelper.getPanoramaOrientationOptionArrayId() > 0) { 1895 buttonManager.initializePanoOrientationButtons(bottomBarSpec.panoOrientationCallback); 1896 } 1897 1898 boolean enableExposureCompensation = bottomBarSpec.enableExposureCompensation && 1899 !(bottomBarSpec.minExposureCompensation == 0 && bottomBarSpec.maxExposureCompensation == 0) && 1900 mController.getSettingsManager() 1901 .getBoolean(SettingsManager.SETTING_EXPOSURE_COMPENSATION_ENABLED); 1902 if (enableExposureCompensation) { 1903 buttonManager.initializePushButton(ButtonManager.BUTTON_EXPOSURE_COMPENSATION, null); 1904 buttonManager.setExposureCompensationParameters( 1905 bottomBarSpec.minExposureCompensation, 1906 bottomBarSpec.maxExposureCompensation, 1907 bottomBarSpec.exposureCompensationStep); 1908 1909 buttonManager.setExposureCompensationCallback( 1910 bottomBarSpec.exposureCompensationSetCallback); 1911 buttonManager.updateExposureButtons(); 1912 } else { 1913 buttonManager.hideButton(ButtonManager.BUTTON_EXPOSURE_COMPENSATION); 1914 buttonManager.setExposureCompensationCallback(null); 1915 } 1916 1917 /** Intent UI */ 1918 if (bottomBarSpec.showCancel) { 1919 buttonManager.initializePushButton(ButtonManager.BUTTON_CANCEL, 1920 bottomBarSpec.cancelCallback); 1921 } 1922 if (bottomBarSpec.showDone) { 1923 buttonManager.initializePushButton(ButtonManager.BUTTON_DONE, 1924 bottomBarSpec.doneCallback); 1925 } 1926 if (bottomBarSpec.showRetake) { 1927 buttonManager.initializePushButton(ButtonManager.BUTTON_RETAKE, 1928 bottomBarSpec.retakeCallback); 1929 } 1930 if (bottomBarSpec.showReview) { 1931 buttonManager.initializePushButton(ButtonManager.BUTTON_REVIEW, 1932 bottomBarSpec.reviewCallback, 1933 R.drawable.ic_play); 1934 } 1935 } 1936 1937 /** 1938 * Shows the given tutorial on the screen. 1939 */ 1940 public void showTutorial(AbstractTutorialOverlay tutorial, LayoutInflater inflater) { 1941 tutorial.show(mTutorialsPlaceHolderWrapper, inflater); 1942 } 1943 1944 /***************************Filmstrip api *****************************/ 1945 1946 public void showFilmstrip() { 1947 mModeListView.onBackPressed(); 1948 mFilmstripLayout.showFilmstrip(); 1949 } 1950 1951 public void hideFilmstrip() { 1952 mFilmstripLayout.hideFilmstrip(); 1953 } 1954} 1955