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