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 17 package com.android.camera.app; 18 19 import android.app.Activity; 20 import android.content.res.Resources; 21 import android.graphics.Bitmap; 22 import android.graphics.Canvas; 23 import android.graphics.Matrix; 24 import android.graphics.RectF; 25 import android.graphics.SurfaceTexture; 26 import android.hardware.display.DisplayManager; 27 import android.util.CameraPerformanceTracker; 28 import android.view.GestureDetector; 29 import android.view.LayoutInflater; 30 import android.view.MotionEvent; 31 import android.view.TextureView; 32 import android.view.View; 33 import android.view.ViewConfiguration; 34 import android.view.ViewGroup; 35 import android.widget.FrameLayout; 36 import android.widget.ImageButton; 37 38 import com.android.camera.AccessibilityUtil; 39 import com.android.camera.AnimationManager; 40 import com.android.camera.ButtonManager; 41 import com.android.camera.CaptureLayoutHelper; 42 import com.android.camera.ShutterButton; 43 import com.android.camera.TextureViewHelper; 44 import com.android.camera.debug.Log; 45 import com.android.camera.filmstrip.FilmstripContentPanel; 46 import com.android.camera.hardware.HardwareSpec; 47 import com.android.camera.module.ModuleController; 48 import com.android.camera.settings.Keys; 49 import com.android.camera.settings.SettingsManager; 50 import com.android.camera.ui.AbstractTutorialOverlay; 51 import com.android.camera.ui.BottomBar; 52 import com.android.camera.ui.CaptureAnimationOverlay; 53 import com.android.camera.ui.GridLines; 54 import com.android.camera.ui.MainActivityLayout; 55 import com.android.camera.ui.ModeListView; 56 import com.android.camera.ui.ModeTransitionView; 57 import com.android.camera.ui.PreviewOverlay; 58 import com.android.camera.ui.PreviewStatusListener; 59 import com.android.camera.ui.StickyBottomCaptureLayout; 60 import com.android.camera.ui.TouchCoordinate; 61 import com.android.camera.ui.focus.FocusRing; 62 import com.android.camera.util.AndroidServices; 63 import com.android.camera.util.ApiHelper; 64 import com.android.camera.util.CameraUtil; 65 import com.android.camera.util.Gusterpolator; 66 import com.android.camera.util.PhotoSphereHelper; 67 import com.android.camera.widget.Cling; 68 import com.android.camera.widget.FilmstripLayout; 69 import com.android.camera.widget.IndicatorIconController; 70 import com.android.camera.widget.ModeOptionsOverlay; 71 import com.android.camera.widget.RoundedThumbnailView; 72 import com.android.camera2.R; 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 */ 86 public 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 */ setListener(Listener listener)105 void setListener(Listener listener); 106 107 /** 108 * Sets cling for external viewer button. 109 */ setClingForViewer(int viewerType, Cling cling)110 void setClingForViewer(int viewerType, Cling cling); 111 112 /** 113 * Clears cling for external viewer button. 114 */ clearClingForViewer(int viewerType)115 void clearClingForViewer(int viewerType); 116 117 /** 118 * Returns a cling for the specified viewer type. 119 */ getClingForViewer(int viewerType)120 Cling getClingForViewer(int viewerType); 121 122 /** 123 * Set if the bottom controls are visible. 124 * @param visible {@code true} if visible. 125 */ setVisible(boolean visible)126 void setVisible(boolean visible); 127 128 /** 129 * @param visible Whether the button is visible. 130 */ setEditButtonVisibility(boolean visible)131 void setEditButtonVisibility(boolean visible); 132 133 /** 134 * @param enabled Whether the button is enabled. 135 */ setEditEnabled(boolean enabled)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 */ setViewerButtonVisibility(int state)144 void setViewerButtonVisibility(int state); 145 146 /** 147 * @param enabled Whether the button is enabled. 148 */ setViewEnabled(boolean enabled)149 void setViewEnabled(boolean enabled); 150 151 /** 152 * @param enabled Whether the button is enabled. 153 */ setTinyPlanetEnabled(boolean enabled)154 void setTinyPlanetEnabled(boolean enabled); 155 156 /** 157 * @param visible Whether the button is visible. 158 */ setDeleteButtonVisibility(boolean visible)159 void setDeleteButtonVisibility(boolean visible); 160 161 /** 162 * @param enabled Whether the button is enabled. 163 */ setDeleteEnabled(boolean enabled)164 void setDeleteEnabled(boolean enabled); 165 166 /** 167 * @param visible Whether the button is visible. 168 */ setShareButtonVisibility(boolean visible)169 void setShareButtonVisibility(boolean visible); 170 171 /** 172 * @param enabled Whether the button is enabled. 173 */ setShareEnabled(boolean enabled)174 void setShareEnabled(boolean enabled); 175 176 /** 177 * Sets the texts for progress UI. 178 * 179 * @param text The text to show. 180 */ setProgressText(CharSequence text)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 */ setProgress(int progress)188 void setProgress(int progress); 189 190 /** 191 * Replaces the progress UI with an error message. 192 */ showProgressError(CharSequence message)193 void showProgressError(CharSequence message); 194 195 /** 196 * Hide the progress error message. 197 */ hideProgressError()198 void hideProgressError(); 199 200 /** 201 * Shows the progress. 202 */ showProgress()203 void showProgress(); 204 205 /** 206 * Hides the progress. 207 */ hideProgress()208 void hideProgress(); 209 210 /** 211 * Shows the controls. 212 */ showControls()213 void showControls(); 214 215 /** 216 * Hides the controls. 217 */ hideControls()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 */ onExternalViewer()229 public void onExternalViewer(); 230 231 /** 232 * Called when the "edit" button is pressed. 233 */ onEdit()234 public void onEdit(); 235 236 /** 237 * Called when the "tiny planet" button is pressed. 238 */ onTinyPlanet()239 public void onTinyPlanet(); 240 241 /** 242 * Called when the "delete" button is pressed. 243 */ onDelete()244 public void onDelete(); 245 246 /** 247 * Called when the "share" button is pressed. 248 */ onShare()249 public void onShare(); 250 251 /** 252 * Called when the progress error message is clicked. 253 */ onProgressErrorClicked()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 /** 357 * Set true if manual exposure compensation should be visible. 358 * 359 * This option is not constrained by hardware limitations. 360 * For example, this is false in HDR+ mode. 361 */ 362 public boolean enableExposureCompensation; 363 364 /** 365 * Set true if the device and module support exposure compensation. 366 * Used only to show exposure button in disabled (greyed out) state. 367 */ 368 public boolean isExposureCompensationSupported; 369 370 /** Intent UI */ 371 372 /** 373 * Set true if the intent ui cancel option should be visible. 374 */ 375 public boolean showCancel; 376 /** 377 * Set true if the intent ui done option should be visible. 378 */ 379 public boolean showDone; 380 /** 381 * Set true if the intent ui retake option should be visible. 382 */ 383 public boolean showRetake; 384 /** 385 * Set true if the intent ui review option should be visible. 386 */ 387 public boolean showReview; 388 389 /** Mode options callbacks */ 390 391 /** 392 * A {@link com.android.camera.ButtonManager.ButtonCallback} 393 * that will be executed when the camera option is pressed. This 394 * callback can be null. 395 */ 396 public ButtonManager.ButtonCallback cameraCallback; 397 398 /** 399 * A {@link com.android.camera.ButtonManager.ButtonCallback} 400 * that will be executed when the flash option is pressed. This 401 * callback can be null. 402 */ 403 public ButtonManager.ButtonCallback flashCallback; 404 405 /** 406 * A {@link com.android.camera.ButtonManager.ButtonCallback} 407 * that will be executed when the hdr/hdr+ option is pressed. This 408 * callback can be null. 409 */ 410 public ButtonManager.ButtonCallback hdrCallback; 411 412 /** 413 * A {@link com.android.camera.ButtonManager.ButtonCallback} 414 * that will be executed when the grid lines option is pressed. This 415 * callback can be null. 416 */ 417 public ButtonManager.ButtonCallback gridLinesCallback; 418 419 /** 420 * A {@link com.android.camera.ButtonManager.ButtonCallback} 421 * that will execute when the panorama orientation option is pressed. 422 * This callback can be null. 423 */ 424 public ButtonManager.ButtonCallback panoOrientationCallback; 425 426 /** Intent UI callbacks */ 427 428 /** 429 * A {@link android.view.View.OnClickListener} that will execute 430 * when the cancel option is pressed. This callback can be null. 431 */ 432 public View.OnClickListener cancelCallback; 433 434 /** 435 * A {@link android.view.View.OnClickListener} that will execute 436 * when the done option is pressed. This callback can be null. 437 */ 438 public View.OnClickListener doneCallback; 439 440 /** 441 * A {@link android.view.View.OnClickListener} that will execute 442 * when the retake option is pressed. This callback can be null. 443 */ 444 public View.OnClickListener retakeCallback; 445 446 /** 447 * A {@link android.view.View.OnClickListener} that will execute 448 * when the review option is pressed. This callback can be null. 449 */ 450 public View.OnClickListener reviewCallback; 451 452 /** 453 * A ExposureCompensationSetCallback that will execute 454 * when an expsosure button is pressed. This callback can be null. 455 */ 456 public interface ExposureCompensationSetCallback { setExposure(int value)457 public void setExposure(int value); 458 } 459 public ExposureCompensationSetCallback exposureCompensationSetCallback; 460 461 /** 462 * Exposure compensation parameters. 463 */ 464 public int minExposureCompensation; 465 public int maxExposureCompensation; 466 public float exposureCompensationStep; 467 468 /** 469 * Whether self-timer is enabled. 470 */ 471 public boolean enableSelfTimer = false; 472 473 /** 474 * Whether the option for self-timer should show. If true and 475 * {@link #enableSelfTimer} is false, then the option should be shown 476 * disabled. 477 */ 478 public boolean showSelfTimer = false; 479 } 480 481 482 private final static Log.Tag TAG = new Log.Tag("CameraAppUI"); 483 484 private final AppController mController; 485 private final boolean mIsCaptureIntent; 486 private final AnimationManager mAnimationManager; 487 488 // Swipe states: 489 private final static int IDLE = 0; 490 private final static int SWIPE_UP = 1; 491 private final static int SWIPE_DOWN = 2; 492 private final static int SWIPE_LEFT = 3; 493 private final static int SWIPE_RIGHT = 4; 494 private boolean mSwipeEnabled = true; 495 496 // Shared Surface Texture properities. 497 private SurfaceTexture mSurface; 498 private int mSurfaceWidth; 499 private int mSurfaceHeight; 500 501 // Touch related measures: 502 private final int mSlop; 503 private final static int SWIPE_TIME_OUT_MS = 500; 504 505 // Mode cover states: 506 private final static int COVER_HIDDEN = 0; 507 private final static int COVER_SHOWN = 1; 508 private final static int COVER_WILL_HIDE_AT_NEXT_FRAME = 2; 509 private final static int COVER_WILL_HIDE_AFTER_NEXT_TEXTURE_UPDATE = 3; 510 private final static int COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE = 4; 511 512 /** 513 * Preview down-sample rate when taking a screenshot. 514 */ 515 private final static int DOWN_SAMPLE_RATE_FOR_SCREENSHOT = 2; 516 517 // App level views: 518 private final FrameLayout mCameraRootView; 519 private final ModeTransitionView mModeTransitionView; 520 private final MainActivityLayout mAppRootView; 521 private final ModeListView mModeListView; 522 private final FilmstripLayout mFilmstripLayout; 523 private TextureView mTextureView; 524 private FrameLayout mModuleUI; 525 private ShutterButton mShutterButton; 526 private ImageButton mCountdownCancelButton; 527 private BottomBar mBottomBar; 528 private ModeOptionsOverlay mModeOptionsOverlay; 529 private IndicatorIconController mIndicatorIconController; 530 private FocusRing mFocusRing; 531 private FrameLayout mTutorialsPlaceHolderWrapper; 532 private StickyBottomCaptureLayout mStickyBottomCaptureLayout; 533 private TextureViewHelper mTextureViewHelper; 534 private final GestureDetector mGestureDetector; 535 private DisplayManager.DisplayListener mDisplayListener; 536 private int mLastRotation; 537 private int mSwipeState = IDLE; 538 private PreviewOverlay mPreviewOverlay; 539 private GridLines mGridLines; 540 private CaptureAnimationOverlay mCaptureOverlay; 541 private PreviewStatusListener mPreviewStatusListener; 542 private int mModeCoverState = COVER_HIDDEN; 543 private final FilmstripBottomPanel mFilmstripBottomControls; 544 private final FilmstripContentPanel mFilmstripPanel; 545 private Runnable mHideCoverRunnable; 546 private final View.OnLayoutChangeListener mPreviewLayoutChangeListener 547 = new View.OnLayoutChangeListener() { 548 @Override 549 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 550 int oldTop, int oldRight, int oldBottom) { 551 if (mPreviewStatusListener != null) { 552 mPreviewStatusListener.onPreviewLayoutChanged(v, left, top, right, bottom, oldLeft, 553 oldTop, oldRight, oldBottom); 554 } 555 } 556 }; 557 private View mModeOptionsToggle; 558 private final RoundedThumbnailView mRoundedThumbnailView; 559 private final CaptureLayoutHelper mCaptureLayoutHelper; 560 private final View mAccessibilityAffordances; 561 private AccessibilityUtil mAccessibilityUtil; 562 563 private boolean mDisableAllUserInteractions; 564 /** Whether to prevent capture indicator from being triggered. */ 565 private boolean mSuppressCaptureIndicator; 566 567 /** Supported HDR mode (none, hdr, hdr+). */ 568 private String mHdrSupportMode; 569 570 /** Used to track the last scope used to update the bottom bar UI. */ 571 private String mCurrentCameraScope; 572 private String mCurrentModuleScope; 573 574 /** 575 * Provides current preview frame and the controls/overlay from the module that 576 * are shown on top of the preview. 577 */ 578 public interface CameraModuleScreenShotProvider { 579 /** 580 * Returns the current preview frame down-sampled using the given down-sample 581 * factor. 582 * 583 * @param downSampleFactor the down sample factor for down sampling the 584 * preview frame. (e.g. a down sample factor of 585 * 2 means to scale down the preview frame to 1/2 586 * the width and height.) 587 * @return down-sampled preview frame 588 */ getPreviewFrame(int downSampleFactor)589 public Bitmap getPreviewFrame(int downSampleFactor); 590 591 /** 592 * @return the controls and overlays that are currently showing on top of 593 * the preview drawn into a bitmap with no scaling applied. 594 */ getPreviewOverlayAndControls()595 public Bitmap getPreviewOverlayAndControls(); 596 597 /** 598 * Returns a bitmap containing the current screenshot. 599 * 600 * @param previewDownSampleFactor the downsample factor applied on the 601 * preview frame when taking the screenshot 602 */ getScreenShot(int previewDownSampleFactor)603 public Bitmap getScreenShot(int previewDownSampleFactor); 604 } 605 606 /** 607 * This listener gets called when the size of the window (excluding the system 608 * decor such as status bar and nav bar) has changed. 609 */ 610 public interface NonDecorWindowSizeChangedListener { onNonDecorWindowSizeChanged(int width, int height, int rotation)611 public void onNonDecorWindowSizeChanged(int width, int height, int rotation); 612 } 613 614 private final CameraModuleScreenShotProvider mCameraModuleScreenShotProvider = 615 new CameraModuleScreenShotProvider() { 616 @Override 617 public Bitmap getPreviewFrame(int downSampleFactor) { 618 if (mCameraRootView == null || mTextureView == null) { 619 return null; 620 } 621 // Gets the bitmap from the preview TextureView. 622 Bitmap preview = mTextureViewHelper.getPreviewBitmap(downSampleFactor); 623 return preview; 624 } 625 626 @Override 627 public Bitmap getPreviewOverlayAndControls() { 628 Bitmap overlays = Bitmap.createBitmap(mCameraRootView.getWidth(), 629 mCameraRootView.getHeight(), Bitmap.Config.ARGB_8888); 630 Canvas canvas = new Canvas(overlays); 631 mCameraRootView.draw(canvas); 632 return overlays; 633 } 634 635 @Override 636 public Bitmap getScreenShot(int previewDownSampleFactor) { 637 Bitmap screenshot = Bitmap.createBitmap(mCameraRootView.getWidth(), 638 mCameraRootView.getHeight(), Bitmap.Config.ARGB_8888); 639 Canvas canvas = new Canvas(screenshot); 640 canvas.drawARGB(255, 0, 0, 0); 641 Bitmap preview = mTextureViewHelper.getPreviewBitmap(previewDownSampleFactor); 642 if (preview != null) { 643 canvas.drawBitmap(preview, null, mTextureViewHelper.getPreviewArea(), null); 644 } 645 Bitmap overlay = getPreviewOverlayAndControls(); 646 if (overlay != null) { 647 canvas.drawBitmap(overlay, 0f, 0f, null); 648 } 649 return screenshot; 650 } 651 }; 652 653 private long mCoverHiddenTime = -1; // System time when preview cover was hidden. 654 getCoverHiddenTime()655 public long getCoverHiddenTime() { 656 return mCoverHiddenTime; 657 } 658 659 /** 660 * This resets the preview to have no applied transform matrix. 661 */ clearPreviewTransform()662 public void clearPreviewTransform() { 663 mTextureViewHelper.clearTransform(); 664 } 665 updatePreviewAspectRatio(float aspectRatio)666 public void updatePreviewAspectRatio(float aspectRatio) { 667 mTextureViewHelper.updateAspectRatio(aspectRatio); 668 } 669 670 /** 671 * WAR: Reset the SurfaceTexture's default buffer size to the current view dimensions of 672 * its TextureView. This is necessary to get the expected behavior for the TextureView's 673 * HardwareLayer transform matrix (set by TextureView#setTransform) after configuring the 674 * SurfaceTexture as an output for the Camera2 API (which involves changing the default buffer 675 * size). 676 * 677 * b/17286155 - Tracking a fix for this in HardwareLayer. 678 */ setDefaultBufferSizeToViewDimens()679 public void setDefaultBufferSizeToViewDimens() { 680 if (mSurface == null || mTextureView == null) { 681 Log.w(TAG, "Could not set SurfaceTexture default buffer dimensions, not yet setup"); 682 return; 683 } 684 mSurface.setDefaultBufferSize(mTextureView.getWidth(), mTextureView.getHeight()); 685 } 686 687 /** 688 * Updates the preview matrix without altering it. 689 * 690 * @param matrix 691 * @param aspectRatio the desired aspect ratio for the preview. 692 */ updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio)693 public void updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio) { 694 mTextureViewHelper.updateTransformFullScreen(matrix, aspectRatio); 695 } 696 697 /** 698 * @return the rect that will display the preview. 699 */ getFullscreenRect()700 public RectF getFullscreenRect() { 701 return mTextureViewHelper.getFullscreenRect(); 702 } 703 704 /** 705 * This is to support modules that calculate their own transform matrix because 706 * they need to use a transform matrix to rotate the preview. 707 * 708 * @param matrix transform matrix to be set on the TextureView 709 */ updatePreviewTransform(Matrix matrix)710 public void updatePreviewTransform(Matrix matrix) { 711 mTextureViewHelper.updateTransform(matrix); 712 } 713 714 public interface AnimationFinishedListener { onAnimationFinished(boolean success)715 public void onAnimationFinished(boolean success); 716 } 717 718 private class MyTouchListener implements View.OnTouchListener { 719 private boolean mScaleStarted = false; 720 @Override onTouch(View v, MotionEvent event)721 public boolean onTouch(View v, MotionEvent event) { 722 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 723 mScaleStarted = false; 724 } else if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { 725 mScaleStarted = true; 726 } 727 return (!mScaleStarted) && mGestureDetector.onTouchEvent(event); 728 } 729 } 730 731 /** 732 * This gesture listener finds out the direction of the scroll gestures and 733 * sends them to CameraAppUI to do further handling. 734 */ 735 private class MyGestureListener extends GestureDetector.SimpleOnGestureListener { 736 private MotionEvent mDown; 737 738 @Override onScroll(MotionEvent e1, MotionEvent ev, float distanceX, float distanceY)739 public boolean onScroll(MotionEvent e1, MotionEvent ev, float distanceX, float distanceY) { 740 if (ev.getEventTime() - ev.getDownTime() > SWIPE_TIME_OUT_MS 741 || mSwipeState != IDLE 742 || mIsCaptureIntent 743 || !mSwipeEnabled) { 744 return false; 745 } 746 747 int deltaX = (int) (ev.getX() - mDown.getX()); 748 int deltaY = (int) (ev.getY() - mDown.getY()); 749 if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) { 750 if (Math.abs(deltaX) > mSlop || Math.abs(deltaY) > mSlop) { 751 // Calculate the direction of the swipe. 752 if (deltaX >= Math.abs(deltaY)) { 753 // Swipe right. 754 setSwipeState(SWIPE_RIGHT); 755 } else if (deltaX <= -Math.abs(deltaY)) { 756 // Swipe left. 757 setSwipeState(SWIPE_LEFT); 758 } 759 } 760 } 761 return true; 762 } 763 setSwipeState(int swipeState)764 private void setSwipeState(int swipeState) { 765 mSwipeState = swipeState; 766 // Notify new swipe detected. 767 onSwipeDetected(swipeState); 768 } 769 770 @Override onDown(MotionEvent ev)771 public boolean onDown(MotionEvent ev) { 772 mDown = MotionEvent.obtain(ev); 773 mSwipeState = IDLE; 774 return false; 775 } 776 } 777 CameraAppUI(AppController controller, MainActivityLayout appRootView, boolean isCaptureIntent)778 public CameraAppUI(AppController controller, MainActivityLayout appRootView, 779 boolean isCaptureIntent) { 780 mSlop = ViewConfiguration.get(controller.getAndroidContext()).getScaledTouchSlop(); 781 mController = controller; 782 mIsCaptureIntent = isCaptureIntent; 783 784 mAppRootView = appRootView; 785 mFilmstripLayout = (FilmstripLayout) appRootView.findViewById(R.id.filmstrip_layout); 786 mCameraRootView = (FrameLayout) appRootView.findViewById(R.id.camera_app_root); 787 mModeTransitionView = (ModeTransitionView) 788 mAppRootView.findViewById(R.id.mode_transition_view); 789 mFilmstripBottomControls = new FilmstripBottomPanel(controller, 790 (ViewGroup) mAppRootView.findViewById(R.id.filmstrip_bottom_panel)); 791 mFilmstripPanel = (FilmstripContentPanel) mAppRootView.findViewById(R.id.filmstrip_layout); 792 mGestureDetector = new GestureDetector(controller.getAndroidContext(), 793 new MyGestureListener()); 794 Resources res = controller.getAndroidContext().getResources(); 795 mCaptureLayoutHelper = new CaptureLayoutHelper( 796 res.getDimensionPixelSize(R.dimen.bottom_bar_height_min), 797 res.getDimensionPixelSize(R.dimen.bottom_bar_height_max), 798 res.getDimensionPixelSize(R.dimen.bottom_bar_height_optimal)); 799 mModeListView = (ModeListView) appRootView.findViewById(R.id.mode_list_layout); 800 if (mModeListView != null) { 801 mModeListView.setModeSwitchListener(this); 802 mModeListView.setModeListOpenListener(this); 803 mModeListView.setCameraModuleScreenShotProvider(mCameraModuleScreenShotProvider); 804 mModeListView.setCaptureLayoutHelper(mCaptureLayoutHelper); 805 boolean shouldShowSettingsCling = mController.getSettingsManager().getBoolean( 806 SettingsManager.SCOPE_GLOBAL, 807 Keys.KEY_SHOULD_SHOW_SETTINGS_BUTTON_CLING); 808 mModeListView.setShouldShowSettingsCling(shouldShowSettingsCling); 809 } else { 810 Log.e(TAG, "Cannot find mode list in the view hierarchy"); 811 } 812 mAnimationManager = new AnimationManager(); 813 mRoundedThumbnailView = (RoundedThumbnailView) appRootView.findViewById(R.id.rounded_thumbnail_view); 814 mRoundedThumbnailView.setCallback(new RoundedThumbnailView.Callback() { 815 @Override 816 public void onHitStateFinished() { 817 mFilmstripLayout.showFilmstrip(); 818 } 819 }); 820 821 mAppRootView.setNonDecorWindowSizeChangedListener(mCaptureLayoutHelper); 822 initDisplayListener(); 823 mAccessibilityAffordances = mAppRootView.findViewById(R.id.accessibility_affordances); 824 View modeListToggle = mAppRootView.findViewById(R.id.accessibility_mode_toggle_button); 825 modeListToggle.setOnClickListener(new View.OnClickListener() { 826 @Override 827 public void onClick(View view) { 828 openModeList(); 829 } 830 }); 831 View filmstripToggle = mAppRootView.findViewById( 832 R.id.accessibility_filmstrip_toggle_button); 833 filmstripToggle.setOnClickListener(new View.OnClickListener() { 834 @Override 835 public void onClick(View view) { 836 showFilmstrip(); 837 } 838 }); 839 840 mSuppressCaptureIndicator = false; 841 } 842 843 844 /** 845 * Freeze what is currently shown on screen until the next preview frame comes 846 * in. 847 */ freezeScreenUntilPreviewReady()848 public void freezeScreenUntilPreviewReady() { 849 Log.v(TAG, "freezeScreenUntilPreviewReady"); 850 mModeTransitionView.setupModeCover(mCameraModuleScreenShotProvider 851 .getScreenShot(DOWN_SAMPLE_RATE_FOR_SCREENSHOT)); 852 mHideCoverRunnable = new Runnable() { 853 @Override 854 public void run() { 855 mModeTransitionView.hideImageCover(); 856 } 857 }; 858 mModeCoverState = COVER_SHOWN; 859 } 860 861 /** 862 * Creates a cling for the specific viewer and links the cling to the corresponding 863 * button for layout position. 864 * 865 * @param viewerType defines which viewer the cling is for. 866 */ setupClingForViewer(int viewerType)867 public void setupClingForViewer(int viewerType) { 868 if (viewerType == BottomPanel.VIEWER_REFOCUS) { 869 FrameLayout filmstripContent = (FrameLayout) mAppRootView 870 .findViewById(R.id.camera_filmstrip_content_layout); 871 if (filmstripContent != null) { 872 // Creates refocus cling. 873 LayoutInflater inflater = AndroidServices.instance().provideLayoutInflater(); 874 Cling refocusCling = (Cling) inflater.inflate(R.layout.cling_widget, null, false); 875 // Sets instruction text in the cling. 876 refocusCling.setText(mController.getAndroidContext().getResources() 877 .getString(R.string.cling_text_for_refocus_editor_button)); 878 879 // Adds cling into view hierarchy. 880 int clingWidth = mController.getAndroidContext() 881 .getResources().getDimensionPixelSize(R.dimen.default_cling_width); 882 filmstripContent.addView(refocusCling, clingWidth, 883 ViewGroup.LayoutParams.WRAP_CONTENT); 884 mFilmstripBottomControls.setClingForViewer(viewerType, refocusCling); 885 } 886 } 887 } 888 889 /** 890 * Clears the listeners for the cling and remove it from the view hierarchy. 891 * 892 * @param viewerType defines which viewer the cling is for. 893 */ clearClingForViewer(int viewerType)894 public void clearClingForViewer(int viewerType) { 895 Cling clingToBeRemoved = mFilmstripBottomControls.getClingForViewer(viewerType); 896 if (clingToBeRemoved == null) { 897 // No cling is created for the specific viewer type. 898 return; 899 } 900 mFilmstripBottomControls.clearClingForViewer(viewerType); 901 clingToBeRemoved.setVisibility(View.GONE); 902 mAppRootView.removeView(clingToBeRemoved); 903 } 904 905 /** 906 * Enable or disable swipe gestures. We want to disable them e.g. while we 907 * record a video. 908 */ setSwipeEnabled(boolean enabled)909 public void setSwipeEnabled(boolean enabled) { 910 mSwipeEnabled = enabled; 911 // TODO: This can be removed once we come up with a new design for handling swipe 912 // on shutter button and mode options. (More details: b/13751653) 913 mAppRootView.setSwipeEnabled(enabled); 914 } 915 onDestroy()916 public void onDestroy() { 917 AndroidServices.instance().provideDisplayManager() 918 .unregisterDisplayListener(mDisplayListener); 919 } 920 921 /** 922 * Initializes the display listener to listen to display changes such as 923 * 180-degree rotation change, which will not have an onConfigurationChanged 924 * callback. 925 */ initDisplayListener()926 private void initDisplayListener() { 927 if (ApiHelper.HAS_DISPLAY_LISTENER) { 928 mLastRotation = CameraUtil.getDisplayRotation((Activity) mAppRootView.getContext()); 929 930 mDisplayListener = new DisplayManager.DisplayListener() { 931 @Override 932 public void onDisplayAdded(int arg0) { 933 // Do nothing. 934 } 935 936 @Override 937 public void onDisplayChanged(int displayId) { 938 int rotation = CameraUtil.getDisplayRotation( 939 (Activity) mAppRootView.getContext()); 940 if ((rotation - mLastRotation + 360) % 360 == 180 941 && mPreviewStatusListener != null) { 942 mPreviewStatusListener.onPreviewFlipped(); 943 mStickyBottomCaptureLayout.requestLayout(); 944 mModeListView.requestLayout(); 945 mTextureView.requestLayout(); 946 } 947 mLastRotation = rotation; 948 } 949 950 @Override 951 public void onDisplayRemoved(int arg0) { 952 // Do nothing. 953 } 954 }; 955 956 AndroidServices.instance().provideDisplayManager() 957 .registerDisplayListener(mDisplayListener, null); 958 } 959 } 960 961 /** 962 * Redirects touch events to appropriate recipient views based on swipe direction. 963 * More specifically, swipe up and swipe down will be handled by the view that handles 964 * mode transition; swipe left will be send to filmstrip; swipe right will be redirected 965 * to mode list in order to bring up mode list. 966 */ onSwipeDetected(int swipeState)967 private void onSwipeDetected(int swipeState) { 968 if (swipeState == SWIPE_UP || swipeState == SWIPE_DOWN) { 969 // TODO: Polish quick switch after this release. 970 // Quick switch between modes. 971 int currentModuleIndex = mController.getCurrentModuleIndex(); 972 final int moduleToTransitionTo = 973 mController.getQuickSwitchToModuleId(currentModuleIndex); 974 if (currentModuleIndex != moduleToTransitionTo) { 975 mAppRootView.redirectTouchEventsTo(mModeTransitionView); 976 int shadeColorId = R.color.camera_gray_background; 977 int iconRes = CameraUtil.getCameraModeCoverIconResId(moduleToTransitionTo, 978 mController.getAndroidContext()); 979 980 AnimationFinishedListener listener = new AnimationFinishedListener() { 981 @Override 982 public void onAnimationFinished(boolean success) { 983 if (success) { 984 mHideCoverRunnable = new Runnable() { 985 @Override 986 public void run() { 987 mModeTransitionView.startPeepHoleAnimation(); 988 } 989 }; 990 mModeCoverState = COVER_SHOWN; 991 // Go to new module when the previous operation is successful. 992 mController.onModeSelected(moduleToTransitionTo); 993 } 994 } 995 }; 996 } 997 } else if (swipeState == SWIPE_LEFT) { 998 // Pass the touch sequence to filmstrip layout. 999 mAppRootView.redirectTouchEventsTo(mFilmstripLayout); 1000 } else if (swipeState == SWIPE_RIGHT) { 1001 // Pass the touch to mode switcher 1002 mAppRootView.redirectTouchEventsTo(mModeListView); 1003 } 1004 } 1005 1006 /** 1007 * Gets called when activity resumes in preview. 1008 */ resume()1009 public void resume() { 1010 // Show mode theme cover until preview is ready 1011 showModeCoverUntilPreviewReady(); 1012 1013 // Hide action bar first since we are in full screen mode first, and 1014 // switch the system UI to lights-out mode. 1015 mFilmstripPanel.hide(); 1016 1017 // Show UI that is meant to only be used when spoken feedback is 1018 // enabled. 1019 mAccessibilityAffordances.setVisibility( 1020 (!mIsCaptureIntent && mAccessibilityUtil.isAccessibilityEnabled()) ? View.VISIBLE 1021 : View.GONE); 1022 } 1023 1024 /** 1025 * Opens the mode list (e.g. because of the menu button being pressed) and 1026 * adapts the rest of the UI. 1027 */ openModeList()1028 public void openModeList() { 1029 mModeOptionsOverlay.closeModeOptions(); 1030 mModeListView.onMenuPressed(); 1031 } 1032 showAccessibilityZoomUI(float maxZoom)1033 public void showAccessibilityZoomUI(float maxZoom) { 1034 mAccessibilityUtil.showZoomUI(maxZoom); 1035 } 1036 hideAccessibilityZoomUI()1037 public void hideAccessibilityZoomUI() { 1038 mAccessibilityUtil.hideZoomUI(); 1039 } 1040 1041 /** 1042 * A cover view showing the mode theme color and mode icon will be visible on 1043 * top of preview until preview is ready (i.e. camera preview is started and 1044 * the first frame has been received). 1045 */ showModeCoverUntilPreviewReady()1046 private void showModeCoverUntilPreviewReady() { 1047 int modeId = mController.getCurrentModuleIndex(); 1048 int colorId = R.color.camera_gray_background;; 1049 int iconId = CameraUtil.getCameraModeCoverIconResId(modeId, mController.getAndroidContext()); 1050 mModeTransitionView.setupModeCover(colorId, iconId); 1051 mHideCoverRunnable = new Runnable() { 1052 @Override 1053 public void run() { 1054 mModeTransitionView.hideModeCover(null); 1055 if (!mDisableAllUserInteractions) { 1056 showShimmyDelayed(); 1057 } 1058 } 1059 }; 1060 mModeCoverState = COVER_SHOWN; 1061 } 1062 showShimmyDelayed()1063 private void showShimmyDelayed() { 1064 if (!mIsCaptureIntent) { 1065 // Show shimmy in SHIMMY_DELAY_MS 1066 mModeListView.showModeSwitcherHint(); 1067 } 1068 } 1069 hideModeCover()1070 private void hideModeCover() { 1071 if (mHideCoverRunnable != null) { 1072 mAppRootView.post(mHideCoverRunnable); 1073 mHideCoverRunnable = null; 1074 } 1075 mModeCoverState = COVER_HIDDEN; 1076 if (mCoverHiddenTime < 0) { 1077 mCoverHiddenTime = System.currentTimeMillis(); 1078 } 1079 } 1080 1081 onPreviewVisiblityChanged(int visibility)1082 public void onPreviewVisiblityChanged(int visibility) { 1083 if (visibility == ModuleController.VISIBILITY_HIDDEN) { 1084 setIndicatorBottomBarWrapperVisible(false); 1085 mAccessibilityAffordances.setVisibility(View.GONE); 1086 } else { 1087 setIndicatorBottomBarWrapperVisible(true); 1088 if (!mIsCaptureIntent && mAccessibilityUtil.isAccessibilityEnabled()) { 1089 mAccessibilityAffordances.setVisibility(View.VISIBLE); 1090 } else { 1091 mAccessibilityAffordances.setVisibility(View.GONE); 1092 } 1093 } 1094 } 1095 1096 /** 1097 * Call to stop the preview from being rendered. Sets the entire capture 1098 * root view to invisible which includes the preview plus focus indicator 1099 * and any other auxiliary views for capture modes. 1100 */ pausePreviewRendering()1101 public void pausePreviewRendering() { 1102 mCameraRootView.setVisibility(View.INVISIBLE); 1103 } 1104 1105 /** 1106 * Call to begin rendering the preview and auxiliary views again. 1107 */ resumePreviewRendering()1108 public void resumePreviewRendering() { 1109 mCameraRootView.setVisibility(View.VISIBLE); 1110 } 1111 1112 /** 1113 * Returns the transform associated with the preview view. 1114 * 1115 * @param m the Matrix in which to copy the current transform. 1116 * @return The specified matrix if not null or a new Matrix instance 1117 * otherwise. 1118 */ getPreviewTransform(Matrix m)1119 public Matrix getPreviewTransform(Matrix m) { 1120 return mTextureView.getTransform(m); 1121 } 1122 1123 @Override onOpenFullScreen()1124 public void onOpenFullScreen() { 1125 // Do nothing. 1126 } 1127 1128 @Override onModeListOpenProgress(float progress)1129 public void onModeListOpenProgress(float progress) { 1130 // When the mode list is in transition, ensure the large layers are 1131 // hardware accelerated. 1132 if (progress >= 1.0f || progress <= 0.0f) { 1133 // Convert hardware layers back to default layer types when animation stops 1134 // to prevent accidental artifacting. 1135 if(mModeOptionsToggle.getLayerType() == View.LAYER_TYPE_HARDWARE || 1136 mShutterButton.getLayerType() == View.LAYER_TYPE_HARDWARE) { 1137 Log.v(TAG, "Disabling hardware layer for the Mode Options Toggle Button."); 1138 mModeOptionsToggle.setLayerType(View.LAYER_TYPE_NONE, null); 1139 Log.v(TAG, "Disabling hardware layer for the Shutter Button."); 1140 mShutterButton.setLayerType(View.LAYER_TYPE_NONE, null); 1141 } 1142 } else { 1143 if(mModeOptionsToggle.getLayerType() != View.LAYER_TYPE_HARDWARE || 1144 mShutterButton.getLayerType() != View.LAYER_TYPE_HARDWARE) { 1145 Log.v(TAG, "Enabling hardware layer for the Mode Options Toggle Button."); 1146 mModeOptionsToggle.setLayerType(View.LAYER_TYPE_HARDWARE, null); 1147 Log.v(TAG, "Enabling hardware layer for the Shutter Button."); 1148 mShutterButton.setLayerType(View.LAYER_TYPE_HARDWARE, null); 1149 } 1150 } 1151 1152 progress = 1 - progress; 1153 float interpolatedProgress = Gusterpolator.INSTANCE.getInterpolation(progress); 1154 mModeOptionsToggle.setAlpha(interpolatedProgress); 1155 // Change shutter button alpha linearly based on the mode list open progress: 1156 // set the alpha to disabled alpha when list is fully open, to enabled alpha 1157 // when the list is fully closed. 1158 mShutterButton.setAlpha(progress * ShutterButton.ALPHA_WHEN_ENABLED 1159 + (1 - progress) * ShutterButton.ALPHA_WHEN_DISABLED); 1160 } 1161 1162 @Override onModeListClosed()1163 public void onModeListClosed() { 1164 // Convert hardware layers back to default layer types when animation stops 1165 // to prevent accidental artifacting. 1166 if(mModeOptionsToggle.getLayerType() == View.LAYER_TYPE_HARDWARE || 1167 mShutterButton.getLayerType() == View.LAYER_TYPE_HARDWARE) { 1168 Log.v(TAG, "Disabling hardware layer for the Mode Options Toggle Button."); 1169 mModeOptionsToggle.setLayerType(View.LAYER_TYPE_NONE, null); 1170 Log.v(TAG, "Disabling hardware layer for the Shutter Button."); 1171 mShutterButton.setLayerType(View.LAYER_TYPE_NONE, null); 1172 } 1173 1174 // Make sure the alpha on mode options ellipse is reset when mode drawer 1175 // is closed. 1176 mModeOptionsToggle.setAlpha(1f); 1177 mShutterButton.setAlpha(ShutterButton.ALPHA_WHEN_ENABLED); 1178 } 1179 1180 /** 1181 * Called when the back key is pressed. 1182 * 1183 * @return Whether the UI responded to the key event. 1184 */ onBackPressed()1185 public boolean onBackPressed() { 1186 if (mFilmstripLayout.getVisibility() == View.VISIBLE) { 1187 return mFilmstripLayout.onBackPressed(); 1188 } else { 1189 return mModeListView.onBackPressed(); 1190 } 1191 } 1192 1193 /** 1194 * Sets a {@link com.android.camera.ui.PreviewStatusListener} that 1195 * listens to SurfaceTexture changes. In addition, listeners are set on 1196 * dependent app ui elements. 1197 * 1198 * @param previewStatusListener the listener that gets notified when SurfaceTexture 1199 * changes 1200 */ setPreviewStatusListener(PreviewStatusListener previewStatusListener)1201 public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) { 1202 mPreviewStatusListener = previewStatusListener; 1203 if (mPreviewStatusListener != null) { 1204 onPreviewListenerChanged(); 1205 } 1206 } 1207 1208 /** 1209 * When the PreviewStatusListener changes, listeners need to be 1210 * set on the following app ui elements: 1211 * {@link com.android.camera.ui.PreviewOverlay}, 1212 * {@link com.android.camera.ui.BottomBar}, 1213 * {@link com.android.camera.ui.IndicatorIconController}. 1214 */ onPreviewListenerChanged()1215 private void onPreviewListenerChanged() { 1216 // Set a listener for recognizing preview gestures. 1217 GestureDetector.OnGestureListener gestureListener 1218 = mPreviewStatusListener.getGestureListener(); 1219 if (gestureListener != null) { 1220 mPreviewOverlay.setGestureListener(gestureListener); 1221 } 1222 View.OnTouchListener touchListener = mPreviewStatusListener.getTouchListener(); 1223 if (touchListener != null) { 1224 mPreviewOverlay.setTouchListener(touchListener); 1225 } 1226 1227 mTextureViewHelper.setAutoAdjustTransform( 1228 mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout()); 1229 } 1230 1231 /** 1232 * This method should be called in onCameraOpened. It defines CameraAppUI 1233 * specific changes that depend on the camera or camera settings. 1234 */ onChangeCamera()1235 public void onChangeCamera() { 1236 ModuleController moduleController = mController.getCurrentModuleController(); 1237 HardwareSpec hardwareSpec = moduleController.getHardwareSpec(); 1238 1239 /** 1240 * The current UI requires that the flash option visibility in front- 1241 * facing camera be 1242 * * disabled if back facing camera supports flash 1243 * * hidden if back facing camera does not support flash 1244 * We save whether back facing camera supports flash because we cannot 1245 * get this in front facing camera without a camera switch. 1246 * 1247 * If this preference is cleared, we also need to clear the camera 1248 * facing setting so we default to opening the camera in back facing 1249 * camera, and can save this flash support value again. 1250 */ 1251 if (hardwareSpec != null) { 1252 if (!mController.getSettingsManager().isSet(SettingsManager.SCOPE_GLOBAL, 1253 Keys.KEY_FLASH_SUPPORTED_BACK_CAMERA)) { 1254 mController.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL, 1255 Keys.KEY_FLASH_SUPPORTED_BACK_CAMERA, 1256 hardwareSpec.isFlashSupported()); 1257 } 1258 /** Similar logic applies to the HDR option. */ 1259 if (!mController.getSettingsManager().isSet(SettingsManager.SCOPE_GLOBAL, 1260 Keys.KEY_HDR_SUPPORT_MODE_BACK_CAMERA)) { 1261 String hdrSupportMode; 1262 if (hardwareSpec.isHdrPlusSupported()) { 1263 hdrSupportMode = getResourceString( 1264 R.string.pref_camera_hdr_supportmode_hdr_plus); 1265 } else if (hardwareSpec.isHdrSupported()) { 1266 hdrSupportMode = getResourceString(R.string.pref_camera_hdr_supportmode_hdr); 1267 } else { 1268 hdrSupportMode = getResourceString(R.string.pref_camera_hdr_supportmode_none); 1269 } 1270 mController.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL, 1271 Keys.KEY_HDR_SUPPORT_MODE_BACK_CAMERA, hdrSupportMode); 1272 } 1273 } 1274 1275 applyModuleSpecs(hardwareSpec, moduleController.getBottomBarSpec(), 1276 true /*skipScopeCheck*/); 1277 syncModeOptionIndicators(); 1278 } 1279 1280 /** 1281 * Updates the mode option indicators according to the current settings. 1282 */ syncModeOptionIndicators()1283 public void syncModeOptionIndicators() { 1284 if (mIndicatorIconController != null) { 1285 // Sync the settings state with the indicator state. 1286 mIndicatorIconController.syncIndicators(); 1287 } 1288 } 1289 1290 /** 1291 * Adds a listener to receive callbacks when preview area changes. 1292 */ addPreviewAreaChangedListener( PreviewStatusListener.PreviewAreaChangedListener listener)1293 public void addPreviewAreaChangedListener( 1294 PreviewStatusListener.PreviewAreaChangedListener listener) { 1295 mTextureViewHelper.addPreviewAreaSizeChangedListener(listener); 1296 } 1297 1298 /** 1299 * Removes a listener that receives callbacks when preview area changes. 1300 */ removePreviewAreaChangedListener( PreviewStatusListener.PreviewAreaChangedListener listener)1301 public void removePreviewAreaChangedListener( 1302 PreviewStatusListener.PreviewAreaChangedListener listener) { 1303 mTextureViewHelper.removePreviewAreaSizeChangedListener(listener); 1304 } 1305 1306 /** 1307 * This inflates generic_module layout, which contains all the shared views across 1308 * modules. Then each module inflates their own views in the given view group. For 1309 * now, this is called every time switching from a not-yet-refactored module to a 1310 * refactored module. In the future, this should only need to be done once per app 1311 * start. 1312 */ prepareModuleUI()1313 public void prepareModuleUI() { 1314 mController.getSettingsManager().addListener(this); 1315 mModuleUI = (FrameLayout) mCameraRootView.findViewById(R.id.module_layout); 1316 mTextureView = (TextureView) mCameraRootView.findViewById(R.id.preview_content); 1317 mTextureViewHelper = new TextureViewHelper(mTextureView, mCaptureLayoutHelper, 1318 mController.getCameraProvider(), mController); 1319 mTextureViewHelper.setSurfaceTextureListener(this); 1320 mTextureViewHelper.setOnLayoutChangeListener(mPreviewLayoutChangeListener); 1321 1322 mBottomBar = (BottomBar) mCameraRootView.findViewById(R.id.bottom_bar); 1323 int unpressedColor = mController.getAndroidContext().getResources() 1324 .getColor(R.color.camera_gray_background); 1325 setBottomBarColor(unpressedColor); 1326 updateModeSpecificUIColors(); 1327 1328 mBottomBar.setCaptureLayoutHelper(mCaptureLayoutHelper); 1329 1330 mModeOptionsOverlay 1331 = (ModeOptionsOverlay) mCameraRootView.findViewById(R.id.mode_options_overlay); 1332 1333 // Sets the visibility of the bottom bar and the mode options. 1334 resetBottomControls(mController.getCurrentModuleController(), 1335 mController.getCurrentModuleIndex()); 1336 mModeOptionsOverlay.setCaptureLayoutHelper(mCaptureLayoutHelper); 1337 1338 mShutterButton = (ShutterButton) mCameraRootView.findViewById(R.id.shutter_button); 1339 addShutterListener(mController.getCurrentModuleController()); 1340 addShutterListener(mModeOptionsOverlay); 1341 addShutterListener(this); 1342 1343 mGridLines = (GridLines) mCameraRootView.findViewById(R.id.grid_lines); 1344 mTextureViewHelper.addPreviewAreaSizeChangedListener(mGridLines); 1345 1346 mPreviewOverlay = (PreviewOverlay) mCameraRootView.findViewById(R.id.preview_overlay); 1347 mPreviewOverlay.setOnTouchListener(new MyTouchListener()); 1348 mPreviewOverlay.setOnPreviewTouchedListener(mModeOptionsOverlay); 1349 mAccessibilityUtil = new AccessibilityUtil(mPreviewOverlay, mAccessibilityAffordances); 1350 1351 mCaptureOverlay = (CaptureAnimationOverlay) 1352 mCameraRootView.findViewById(R.id.capture_overlay); 1353 mTextureViewHelper.addPreviewAreaSizeChangedListener(mPreviewOverlay); 1354 mTextureViewHelper.addPreviewAreaSizeChangedListener(mCaptureOverlay); 1355 1356 if (mIndicatorIconController == null) { 1357 mIndicatorIconController = 1358 new IndicatorIconController(mController, mAppRootView); 1359 } 1360 1361 mController.getButtonManager().load(mCameraRootView); 1362 mController.getButtonManager().setListener(mIndicatorIconController); 1363 mController.getSettingsManager().addListener(mIndicatorIconController); 1364 1365 mModeOptionsToggle = mCameraRootView.findViewById(R.id.mode_options_toggle); 1366 mFocusRing = (FocusRing) mCameraRootView.findViewById(R.id.focus_ring); 1367 mTutorialsPlaceHolderWrapper = (FrameLayout) mCameraRootView 1368 .findViewById(R.id.tutorials_placeholder_wrapper); 1369 mStickyBottomCaptureLayout = (StickyBottomCaptureLayout) mAppRootView 1370 .findViewById(R.id.sticky_bottom_capture_layout); 1371 mStickyBottomCaptureLayout.setCaptureLayoutHelper(mCaptureLayoutHelper); 1372 mCountdownCancelButton = (ImageButton) mStickyBottomCaptureLayout 1373 .findViewById(R.id.shutter_cancel_button); 1374 1375 mTextureViewHelper.addPreviewAreaSizeChangedListener(mModeListView); 1376 mTextureViewHelper.addAspectRatioChangedListener( 1377 new PreviewStatusListener.PreviewAspectRatioChangedListener() { 1378 @Override 1379 public void onPreviewAspectRatioChanged(float aspectRatio) { 1380 mModeOptionsOverlay.requestLayout(); 1381 mBottomBar.requestLayout(); 1382 } 1383 } 1384 ); 1385 } 1386 1387 /** 1388 * Called indirectly from each module in their initialization to get a view group 1389 * to inflate the module specific views in. 1390 * 1391 * @return a view group for modules to attach views to 1392 */ getModuleRootView()1393 public FrameLayout getModuleRootView() { 1394 // TODO: Change it to mModuleUI when refactor is done 1395 return mCameraRootView; 1396 } 1397 1398 /** 1399 * Remove all the module specific views. 1400 */ clearModuleUI()1401 public void clearModuleUI() { 1402 if (mModuleUI != null) { 1403 mModuleUI.removeAllViews(); 1404 } 1405 removeShutterListener(mController.getCurrentModuleController()); 1406 mTutorialsPlaceHolderWrapper.removeAllViews(); 1407 mTutorialsPlaceHolderWrapper.setVisibility(View.GONE); 1408 1409 setShutterButtonEnabled(true); 1410 mPreviewStatusListener = null; 1411 mPreviewOverlay.reset(); 1412 1413 Log.v(TAG, "mFocusRing.stopFocusAnimations()"); 1414 mFocusRing.stopFocusAnimations(); 1415 } 1416 1417 /** 1418 * Gets called when preview is ready to start. It sets up one shot preview callback 1419 * in order to receive a callback when the preview frame is available, so that 1420 * the preview cover can be hidden to reveal preview. 1421 * 1422 * An alternative for getting the timing to hide preview cover is through 1423 * {@link CameraAppUI#onSurfaceTextureUpdated(android.graphics.SurfaceTexture)}, 1424 * which is less accurate but therefore is the fallback for modules that manage 1425 * their own preview callbacks (as setting one preview callback will override 1426 * any other installed preview callbacks), or use camera2 API. 1427 */ onPreviewReadyToStart()1428 public void onPreviewReadyToStart() { 1429 if (mModeCoverState == COVER_SHOWN) { 1430 mModeCoverState = COVER_WILL_HIDE_AT_NEXT_FRAME; 1431 mController.setupOneShotPreviewListener(); 1432 } 1433 } 1434 1435 /** 1436 * Gets called when preview is started. 1437 */ onPreviewStarted()1438 public void onPreviewStarted() { 1439 Log.v(TAG, "onPreviewStarted"); 1440 if (mModeCoverState == COVER_SHOWN) { 1441 // This is a work around of the face detection failure in b/20724126. 1442 // In particular, we need to drop the first preview frame in order to 1443 // make face detection work and also need to hide this preview frame to 1444 // avoid potential janks. We do this only for L, Nexus 6 and Haleakala. 1445 if (ApiHelper.isLorLMr1() && ApiHelper.IS_NEXUS_6) { 1446 mModeCoverState = COVER_WILL_HIDE_AFTER_NEXT_TEXTURE_UPDATE; 1447 } else { 1448 mModeCoverState = COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE; 1449 } 1450 } 1451 enableModeOptions(); 1452 } 1453 1454 /** 1455 * Gets notified when next preview frame comes in. 1456 */ onNewPreviewFrame()1457 public void onNewPreviewFrame() { 1458 Log.v(TAG, "onNewPreviewFrame"); 1459 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME); 1460 hideModeCover(); 1461 } 1462 1463 @Override onShutterButtonClick()1464 public void onShutterButtonClick() { 1465 /* 1466 * Set the mode options toggle unclickable, generally 1467 * throughout the app, whenever the shutter button is clicked. 1468 * 1469 * This could be done in the OnShutterButtonListener of the 1470 * ModeOptionsOverlay, but since it is very important that we 1471 * can clearly see when the toggle becomes clickable again, 1472 * keep all of that logic at this level. 1473 */ 1474 // disableModeOptions(); 1475 } 1476 1477 @Override onShutterCoordinate(TouchCoordinate coord)1478 public void onShutterCoordinate(TouchCoordinate coord) { 1479 // Do nothing. 1480 } 1481 1482 @Override onShutterButtonFocus(boolean pressed)1483 public void onShutterButtonFocus(boolean pressed) { 1484 // noop 1485 } 1486 1487 @Override onShutterButtonLongPressed()1488 public void onShutterButtonLongPressed() { 1489 // noop 1490 } 1491 1492 /** 1493 * Set the mode options toggle clickable. 1494 */ enableModeOptions()1495 public void enableModeOptions() { 1496 /* 1497 * For modules using camera 1 api, this gets called in 1498 * onSurfaceTextureUpdated whenever the preview gets stopped and 1499 * started after each capture. This also takes care of the 1500 * case where the mode options might be unclickable when we 1501 * switch modes 1502 * 1503 * For modules using camera 2 api, they're required to call this 1504 * method when a capture is "completed". Unfortunately this differs 1505 * per module implementation. 1506 */ 1507 if (!mDisableAllUserInteractions) { 1508 mModeOptionsOverlay.setToggleClickable(true); 1509 } 1510 } 1511 1512 /** 1513 * Set the mode options toggle not clickable. 1514 */ disableModeOptions()1515 public void disableModeOptions() { 1516 mModeOptionsOverlay.setToggleClickable(false); 1517 } 1518 setDisableAllUserInteractions(boolean disable)1519 public void setDisableAllUserInteractions(boolean disable) { 1520 if (disable) { 1521 disableModeOptions(); 1522 setShutterButtonEnabled(false); 1523 setSwipeEnabled(false); 1524 mModeListView.hideAnimated(); 1525 } else { 1526 enableModeOptions(); 1527 setShutterButtonEnabled(true); 1528 setSwipeEnabled(true); 1529 } 1530 mDisableAllUserInteractions = disable; 1531 } 1532 1533 @Override onModeButtonPressed(int modeIndex)1534 public void onModeButtonPressed(int modeIndex) { 1535 // TODO: Make CameraActivity listen to ModeListView's events. 1536 int pressedModuleId = mController.getModuleId(modeIndex); 1537 int currentModuleId = mController.getCurrentModuleIndex(); 1538 if (pressedModuleId != currentModuleId) { 1539 hideCaptureIndicator(); 1540 } 1541 } 1542 1543 /** 1544 * Gets called when a mode is selected from {@link com.android.camera.ui.ModeListView} 1545 * 1546 * @param modeIndex mode index of the selected mode 1547 */ 1548 @Override onModeSelected(int modeIndex)1549 public void onModeSelected(int modeIndex) { 1550 mHideCoverRunnable = new Runnable() { 1551 @Override 1552 public void run() { 1553 mModeListView.startModeSelectionAnimation(); 1554 } 1555 }; 1556 mShutterButton.setAlpha(ShutterButton.ALPHA_WHEN_ENABLED); 1557 mModeCoverState = COVER_SHOWN; 1558 1559 int lastIndex = mController.getCurrentModuleIndex(); 1560 // Actual mode teardown / new mode initialization happens here 1561 mController.onModeSelected(modeIndex); 1562 int currentIndex = mController.getCurrentModuleIndex(); 1563 1564 if (lastIndex == currentIndex) { 1565 hideModeCover(); 1566 } 1567 1568 updateModeSpecificUIColors(); 1569 } 1570 updateModeSpecificUIColors()1571 private void updateModeSpecificUIColors() { 1572 setBottomBarColorsForModeIndex(mController.getCurrentModuleIndex()); 1573 } 1574 1575 @Override onSettingsSelected()1576 public void onSettingsSelected() { 1577 mController.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL, 1578 Keys.KEY_SHOULD_SHOW_SETTINGS_BUTTON_CLING, false); 1579 mModeListView.setShouldShowSettingsCling(false); 1580 mController.onSettingsSelected(); 1581 } 1582 1583 @Override getCurrentModeIndex()1584 public int getCurrentModeIndex() { 1585 return mController.getCurrentModuleIndex(); 1586 } 1587 1588 /********************** Capture animation **********************/ 1589 /* TODO: This session is subject to UX changes. In addition to the generic 1590 flash animation and post capture animation, consider designating a parameter 1591 for specifying the type of animation, as well as an animation finished listener 1592 so that modules can have more knowledge of the status of the animation. */ 1593 1594 /** 1595 * Turns on or off the capture indicator suppression. 1596 */ setShouldSuppressCaptureIndicator(boolean suppress)1597 public void setShouldSuppressCaptureIndicator(boolean suppress) { 1598 mSuppressCaptureIndicator = suppress; 1599 } 1600 1601 /** 1602 * Starts the capture indicator pop-out animation. 1603 * 1604 * @param accessibilityString An accessibility String to be announced during the peek animation. 1605 */ startCaptureIndicatorRevealAnimation(String accessibilityString)1606 public void startCaptureIndicatorRevealAnimation(String accessibilityString) { 1607 if (mSuppressCaptureIndicator || mFilmstripLayout.getVisibility() == View.VISIBLE) { 1608 return; 1609 } 1610 mRoundedThumbnailView.startRevealThumbnailAnimation(accessibilityString); 1611 } 1612 1613 /** 1614 * Updates the thumbnail image in the capture indicator. 1615 * 1616 * @param thumbnailBitmap The thumbnail image to be shown. 1617 */ updateCaptureIndicatorThumbnail(Bitmap thumbnailBitmap, int rotation)1618 public void updateCaptureIndicatorThumbnail(Bitmap thumbnailBitmap, int rotation) { 1619 if (mSuppressCaptureIndicator || mFilmstripLayout.getVisibility() == View.VISIBLE) { 1620 return; 1621 } 1622 mRoundedThumbnailView.setThumbnail(thumbnailBitmap, rotation); 1623 } 1624 1625 /** 1626 * Hides the capture indicator. 1627 */ hideCaptureIndicator()1628 public void hideCaptureIndicator() { 1629 mRoundedThumbnailView.hideThumbnail(); 1630 } 1631 1632 /** 1633 * Starts the flash animation. 1634 */ startFlashAnimation(boolean shortFlash)1635 public void startFlashAnimation(boolean shortFlash) { 1636 mCaptureOverlay.startFlashAnimation(shortFlash); 1637 } 1638 1639 /** 1640 * Cancels the pre-capture animation. 1641 */ cancelPreCaptureAnimation()1642 public void cancelPreCaptureAnimation() { 1643 mAnimationManager.cancelAnimations(); 1644 } 1645 1646 /** 1647 * Cancels the post-capture animation. 1648 */ cancelPostCaptureAnimation()1649 public void cancelPostCaptureAnimation() { 1650 mAnimationManager.cancelAnimations(); 1651 } 1652 getFilmstripContentPanel()1653 public FilmstripContentPanel getFilmstripContentPanel() { 1654 return mFilmstripPanel; 1655 } 1656 1657 /** 1658 * @return The {@link com.android.camera.app.CameraAppUI.BottomPanel} on the 1659 * bottom of the filmstrip. 1660 */ getFilmstripBottomControls()1661 public BottomPanel getFilmstripBottomControls() { 1662 return mFilmstripBottomControls; 1663 } 1664 showBottomControls()1665 public void showBottomControls() { 1666 mFilmstripBottomControls.show(); 1667 } 1668 hideBottomControls()1669 public void hideBottomControls() { 1670 mFilmstripBottomControls.hide(); 1671 } 1672 1673 /** 1674 * @param listener The listener for bottom controls. 1675 */ setFilmstripBottomControlsListener(BottomPanel.Listener listener)1676 public void setFilmstripBottomControlsListener(BottomPanel.Listener listener) { 1677 mFilmstripBottomControls.setListener(listener); 1678 } 1679 1680 /***************************SurfaceTexture Api and Listener*********************************/ 1681 1682 /** 1683 * Return the shared surface texture. 1684 */ getSurfaceTexture()1685 public SurfaceTexture getSurfaceTexture() { 1686 return mSurface; 1687 } 1688 1689 /** 1690 * Return the shared {@link android.graphics.SurfaceTexture}'s width. 1691 */ getSurfaceWidth()1692 public int getSurfaceWidth() { 1693 return mSurfaceWidth; 1694 } 1695 1696 /** 1697 * Return the shared {@link android.graphics.SurfaceTexture}'s height. 1698 */ getSurfaceHeight()1699 public int getSurfaceHeight() { 1700 return mSurfaceHeight; 1701 } 1702 1703 @Override onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)1704 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 1705 mSurface = surface; 1706 mSurfaceWidth = width; 1707 mSurfaceHeight = height; 1708 Log.v(TAG, "SurfaceTexture is available"); 1709 if (mPreviewStatusListener != null) { 1710 mPreviewStatusListener.onSurfaceTextureAvailable(surface, width, height); 1711 } 1712 enableModeOptions(); 1713 } 1714 1715 @Override onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)1716 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 1717 mSurface = surface; 1718 mSurfaceWidth = width; 1719 mSurfaceHeight = height; 1720 if (mPreviewStatusListener != null) { 1721 mPreviewStatusListener.onSurfaceTextureSizeChanged(surface, width, height); 1722 } 1723 } 1724 1725 @Override onSurfaceTextureDestroyed(SurfaceTexture surface)1726 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 1727 mSurface = null; 1728 Log.v(TAG, "SurfaceTexture is destroyed"); 1729 if (mPreviewStatusListener != null) { 1730 return mPreviewStatusListener.onSurfaceTextureDestroyed(surface); 1731 } 1732 return false; 1733 } 1734 1735 @Override onSurfaceTextureUpdated(SurfaceTexture surface)1736 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 1737 mSurface = surface; 1738 if (mPreviewStatusListener != null) { 1739 mPreviewStatusListener.onSurfaceTextureUpdated(surface); 1740 } 1741 // Do not show the first preview frame. Due to the bug b/20724126, we need to have 1742 // a WAR to request a preview frame followed by 5-frame ZSL burst before the repeating 1743 // preview and ZSL streams. Need to hide the first preview frame since it is janky. 1744 // We do this only for L, Nexus 6 and Haleakala. 1745 if (mModeCoverState == COVER_WILL_HIDE_AFTER_NEXT_TEXTURE_UPDATE) { 1746 mModeCoverState = COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE; 1747 } else if (mModeCoverState == COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE){ 1748 Log.v(TAG, "hiding cover via onSurfaceTextureUpdated"); 1749 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME); 1750 hideModeCover(); 1751 } 1752 } 1753 1754 /****************************Grid lines api ******************************/ 1755 1756 /** 1757 * Show a set of evenly spaced lines over the preview. The number 1758 * of lines horizontally and vertically is determined by 1759 * {@link com.android.camera.ui.GridLines}. 1760 */ showGridLines()1761 public void showGridLines() { 1762 if (mGridLines != null) { 1763 mGridLines.setVisibility(View.VISIBLE); 1764 } 1765 } 1766 1767 /** 1768 * Hide the set of evenly spaced grid lines overlaying the preview. 1769 */ hideGridLines()1770 public void hideGridLines() { 1771 if (mGridLines != null) { 1772 mGridLines.setVisibility(View.INVISIBLE); 1773 } 1774 } 1775 1776 /** 1777 * Return a callback which shows or hide the preview grid lines 1778 * depending on whether the grid lines setting is set on. 1779 */ getGridLinesCallback()1780 public ButtonManager.ButtonCallback getGridLinesCallback() { 1781 return new ButtonManager.ButtonCallback() { 1782 @Override 1783 public void onStateChanged(int state) { 1784 if (!mController.isPaused()) { 1785 if (Keys.areGridLinesOn(mController.getSettingsManager())) { 1786 showGridLines(); 1787 } else { 1788 hideGridLines(); 1789 } 1790 } 1791 } 1792 }; 1793 } 1794 1795 /***************************Mode options api *****************************/ 1796 1797 /** 1798 * Set the mode options visible. 1799 */ 1800 public void showModeOptions() { 1801 /* Make mode options clickable. */ 1802 enableModeOptions(); 1803 mModeOptionsOverlay.setVisibility(View.VISIBLE); 1804 } 1805 1806 /** 1807 * Set the mode options invisible. This is necessary for modes 1808 * that don't show a bottom bar for the capture UI. 1809 */ 1810 public void hideModeOptions() { 1811 mModeOptionsOverlay.setVisibility(View.INVISIBLE); 1812 } 1813 1814 /****************************Bottom bar api ******************************/ 1815 1816 /** 1817 * Sets up the bottom bar and mode options with the correct 1818 * shutter button and visibility based on the current module. 1819 */ 1820 public void resetBottomControls(ModuleController module, int moduleIndex) { 1821 if (areBottomControlsUsed(module)) { 1822 setBottomBarShutterIcon(moduleIndex); 1823 mCaptureLayoutHelper.setShowBottomBar(true); 1824 } else { 1825 mCaptureLayoutHelper.setShowBottomBar(false); 1826 } 1827 } 1828 1829 /** 1830 * Show or hide the mode options and bottom bar, based on 1831 * whether the current module is using the bottom bar. Returns 1832 * whether the mode options and bottom bar are used. 1833 */ 1834 private boolean areBottomControlsUsed(ModuleController module) { 1835 if (module.isUsingBottomBar()) { 1836 showBottomBar(); 1837 showModeOptions(); 1838 return true; 1839 } else { 1840 hideBottomBar(); 1841 hideModeOptions(); 1842 return false; 1843 } 1844 } 1845 1846 /** 1847 * Set the bottom bar visible. 1848 */ 1849 public void showBottomBar() { 1850 mBottomBar.setVisibility(View.VISIBLE); 1851 } 1852 1853 /** 1854 * Set the bottom bar invisible. 1855 */ 1856 public void hideBottomBar() { 1857 mBottomBar.setVisibility(View.INVISIBLE); 1858 } 1859 1860 /** 1861 * Sets the color of the bottom bar. 1862 */ 1863 public void setBottomBarColor(int colorId) { 1864 mBottomBar.setBackgroundColor(colorId); 1865 } 1866 1867 /** 1868 * Sets the pressed color of the bottom bar for a camera mode index. 1869 */ 1870 public void setBottomBarColorsForModeIndex(int index) { 1871 mBottomBar.setColorsForModeIndex(index); 1872 } 1873 1874 /** 1875 * Sets the shutter button icon on the bottom bar, based on 1876 * the mode index. 1877 */ 1878 public void setBottomBarShutterIcon(int modeIndex) { 1879 int shutterIconId = CameraUtil.getCameraShutterIconId(modeIndex, 1880 mController.getAndroidContext()); 1881 mBottomBar.setShutterButtonIcon(shutterIconId); 1882 } 1883 1884 public void animateBottomBarToVideoStop(int shutterIconId) { 1885 mBottomBar.animateToVideoStop(shutterIconId); 1886 } 1887 1888 public void animateBottomBarToFullSize(int shutterIconId) { 1889 mBottomBar.animateToFullSize(shutterIconId); 1890 } 1891 1892 public void setShutterButtonEnabled(final boolean enabled) { 1893 if (!mDisableAllUserInteractions) { 1894 mBottomBar.post(new Runnable() { 1895 @Override 1896 public void run() { 1897 mBottomBar.setShutterButtonEnabled(enabled); 1898 } 1899 }); 1900 } 1901 } 1902 1903 public void setShutterButtonImportantToA11y(boolean important) { 1904 mBottomBar.setShutterButtonImportantToA11y(important); 1905 } 1906 1907 public boolean isShutterButtonEnabled() { 1908 return mBottomBar.isShutterButtonEnabled(); 1909 } 1910 1911 public void setIndicatorBottomBarWrapperVisible(boolean visible) { 1912 mStickyBottomCaptureLayout.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); 1913 } 1914 1915 /** 1916 * Set the visibility of the bottom bar. 1917 */ 1918 // TODO: needed for when panorama is managed by the generic module ui. 1919 public void setBottomBarVisible(boolean visible) { 1920 mBottomBar.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); 1921 } 1922 1923 /** 1924 * Add a {@link #ShutterButton.OnShutterButtonListener} to the shutter button. 1925 */ 1926 public void addShutterListener(ShutterButton.OnShutterButtonListener listener) { 1927 mShutterButton.addOnShutterButtonListener(listener); 1928 } 1929 1930 /** 1931 * Remove a {@link #ShutterButton.OnShutterButtonListener} from the shutter button. 1932 */ 1933 public void removeShutterListener(ShutterButton.OnShutterButtonListener listener) { 1934 mShutterButton.removeOnShutterButtonListener(listener); 1935 } 1936 1937 /** 1938 * Sets or replaces the "cancel shutter" button listener. 1939 * <p> 1940 * TODO: Make this part of the interface the same way shutter button 1941 * listeners are. 1942 */ 1943 public void setCancelShutterButtonListener(View.OnClickListener listener) { 1944 mCountdownCancelButton.setOnClickListener(listener); 1945 } 1946 1947 /** 1948 * Performs a transition to the capture layout of the bottom bar. 1949 */ 1950 public void transitionToCapture() { 1951 ModuleController moduleController = mController.getCurrentModuleController(); 1952 applyModuleSpecs(moduleController.getHardwareSpec(), 1953 moduleController.getBottomBarSpec()); 1954 mBottomBar.transitionToCapture(); 1955 } 1956 1957 /** 1958 * Displays the Cancel button instead of the capture button. 1959 */ 1960 public void transitionToCancel() { 1961 ModuleController moduleController = mController.getCurrentModuleController(); 1962 applyModuleSpecs(moduleController.getHardwareSpec(), 1963 moduleController.getBottomBarSpec()); 1964 mBottomBar.transitionToCancel(); 1965 } 1966 1967 /** 1968 * Performs a transition to the global intent layout. 1969 */ 1970 public void transitionToIntentCaptureLayout() { 1971 ModuleController moduleController = mController.getCurrentModuleController(); 1972 applyModuleSpecs(moduleController.getHardwareSpec(), 1973 moduleController.getBottomBarSpec()); 1974 mBottomBar.transitionToIntentCaptureLayout(); 1975 showModeOptions(); 1976 } 1977 1978 /** 1979 * Performs a transition to the global intent review layout. 1980 */ 1981 public void transitionToIntentReviewLayout() { 1982 ModuleController moduleController = mController.getCurrentModuleController(); 1983 applyModuleSpecs(moduleController.getHardwareSpec(), 1984 moduleController.getBottomBarSpec()); 1985 mBottomBar.transitionToIntentReviewLayout(); 1986 hideModeOptions(); 1987 1988 // Hide the preview snapshot since the screen is frozen when users tap 1989 // shutter button in capture intent. 1990 hideModeCover(); 1991 } 1992 1993 /** 1994 * @return whether UI is in intent review mode 1995 */ 1996 public boolean isInIntentReview() { 1997 return mBottomBar.isInIntentReview(); 1998 } 1999 2000 @Override 2001 public void onSettingChanged(SettingsManager settingsManager, String key) { 2002 // Update the mode options based on the hardware spec, 2003 // when hdr changes to prevent flash from getting out of sync. 2004 if (key.equals(Keys.KEY_CAMERA_HDR)) { 2005 ModuleController moduleController = mController.getCurrentModuleController(); 2006 applyModuleSpecs(moduleController.getHardwareSpec(), 2007 moduleController.getBottomBarSpec(), 2008 true /*skipScopeCheck*/); 2009 } 2010 } 2011 2012 /** 2013 * Applies a {@link com.android.camera.CameraAppUI.BottomBarUISpec} 2014 * to the bottom bar mode options based on limitations from a 2015 * {@link com.android.camera.hardware.HardwareSpec}. 2016 * 2017 * Options not supported by the hardware are either hidden 2018 * or disabled, depending on the option. 2019 * 2020 * Otherwise, the option is fully enabled and clickable. 2021 */ 2022 public void applyModuleSpecs(HardwareSpec hardwareSpec, 2023 BottomBarUISpec bottomBarSpec) { 2024 applyModuleSpecs(hardwareSpec, bottomBarSpec, false /*skipScopeCheck*/); 2025 } 2026 2027 private void applyModuleSpecs(final HardwareSpec hardwareSpec, 2028 final BottomBarUISpec bottomBarSpec, boolean skipScopeCheck) { 2029 if (hardwareSpec == null || bottomBarSpec == null) { 2030 return; 2031 } 2032 2033 ButtonManager buttonManager = mController.getButtonManager(); 2034 SettingsManager settingsManager = mController.getSettingsManager(); 2035 2036 buttonManager.setToInitialState(); 2037 2038 if (skipScopeCheck 2039 || !mController.getModuleScope().equals(mCurrentModuleScope) 2040 || !mController.getCameraScope().equals(mCurrentCameraScope)) { 2041 2042 // Scope dependent options, update only if the module or the 2043 // camera scope changed or scope-check skip was requested. 2044 mCurrentModuleScope = mController.getModuleScope(); 2045 mCurrentCameraScope = mController.getCameraScope(); 2046 2047 mHdrSupportMode = settingsManager.getString(SettingsManager.SCOPE_GLOBAL, 2048 Keys.KEY_HDR_SUPPORT_MODE_BACK_CAMERA); 2049 2050 /** Standard mode options */ 2051 if (mController.getCameraProvider().getNumberOfCameras() > 1 && 2052 hardwareSpec.isFrontCameraSupported()) { 2053 if (bottomBarSpec.enableCamera) { 2054 int hdrButtonId = ButtonManager.BUTTON_HDR; 2055 if (mHdrSupportMode.equals(getResourceString( 2056 R.string.pref_camera_hdr_supportmode_hdr_plus))) { 2057 hdrButtonId = ButtonManager.BUTTON_HDR_PLUS; 2058 } 2059 buttonManager.initializeButton(ButtonManager.BUTTON_CAMERA, 2060 bottomBarSpec.cameraCallback, 2061 getDisableButtonCallback(hdrButtonId)); 2062 } else { 2063 buttonManager.disableButton(ButtonManager.BUTTON_CAMERA); 2064 } 2065 } else { 2066 // Hide camera icon if front camera not available. 2067 buttonManager.hideButton(ButtonManager.BUTTON_CAMERA); 2068 } 2069 2070 boolean flashBackCamera = settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 2071 Keys.KEY_FLASH_SUPPORTED_BACK_CAMERA); 2072 if (bottomBarSpec.hideFlash 2073 || !flashBackCamera) { 2074 // Hide both flash and torch button in flash disable logic 2075 buttonManager.hideButton(ButtonManager.BUTTON_FLASH); 2076 buttonManager.hideButton(ButtonManager.BUTTON_TORCH); 2077 } else { 2078 if (hardwareSpec.isFlashSupported()) { 2079 if (bottomBarSpec.enableFlash) { 2080 buttonManager.initializeButton(ButtonManager.BUTTON_FLASH, 2081 bottomBarSpec.flashCallback); 2082 } else if (bottomBarSpec.enableTorchFlash) { 2083 buttonManager.initializeButton(ButtonManager.BUTTON_TORCH, 2084 bottomBarSpec.flashCallback); 2085 } else if (bottomBarSpec.enableHdrPlusFlash) { 2086 buttonManager.initializeButton(ButtonManager.BUTTON_HDR_PLUS_FLASH, 2087 bottomBarSpec.flashCallback); 2088 } else { 2089 // Disable both flash and torch button in flash disable 2090 // logic. Need to ensure it's visible, it may be hidden 2091 // from previous non-flash mode. 2092 buttonManager.showButton(ButtonManager.BUTTON_FLASH); 2093 buttonManager.disableButton(ButtonManager.BUTTON_FLASH); 2094 buttonManager.disableButton(ButtonManager.BUTTON_TORCH); 2095 } 2096 } else { 2097 // Flash not supported but another module does. 2098 // Disable flash button. Need to ensure it's visible, 2099 // it may be hidden from previous non-flash mode. 2100 buttonManager.showButton(ButtonManager.BUTTON_FLASH); 2101 buttonManager.disableButton(ButtonManager.BUTTON_FLASH); 2102 buttonManager.disableButton(ButtonManager.BUTTON_TORCH); 2103 } 2104 } 2105 2106 if (bottomBarSpec.hideHdr || mIsCaptureIntent) { 2107 // Force hide hdr or hdr plus icon. 2108 buttonManager.hideButton(ButtonManager.BUTTON_HDR_PLUS); 2109 } else { 2110 if (hardwareSpec.isHdrPlusSupported()) { 2111 mHdrSupportMode = getResourceString( 2112 R.string.pref_camera_hdr_supportmode_hdr_plus); 2113 if (bottomBarSpec.enableHdr) { 2114 buttonManager.initializeButton(ButtonManager.BUTTON_HDR_PLUS, 2115 bottomBarSpec.hdrCallback, 2116 getDisableButtonCallback(ButtonManager.BUTTON_CAMERA)); 2117 } else { 2118 buttonManager.disableButton(ButtonManager.BUTTON_HDR_PLUS); 2119 } 2120 } else if (hardwareSpec.isHdrSupported()) { 2121 mHdrSupportMode = getResourceString(R.string.pref_camera_hdr_supportmode_hdr); 2122 if (bottomBarSpec.enableHdr) { 2123 buttonManager.initializeButton(ButtonManager.BUTTON_HDR, 2124 bottomBarSpec.hdrCallback, 2125 getDisableButtonCallback(ButtonManager.BUTTON_CAMERA)); 2126 } else { 2127 buttonManager.disableButton(ButtonManager.BUTTON_HDR); 2128 } 2129 } else { 2130 // Hide hdr plus or hdr icon if neither are supported overall. 2131 if (mHdrSupportMode.isEmpty() || mHdrSupportMode 2132 .equals(getResourceString(R.string.pref_camera_hdr_supportmode_none))) { 2133 buttonManager.hideButton(ButtonManager.BUTTON_HDR_PLUS); 2134 } else { 2135 // Disable HDR button. Need to ensure it's visible, 2136 // it may be hidden from previous non HDR mode (eg. Video). 2137 int buttonId = ButtonManager.BUTTON_HDR; 2138 if (mHdrSupportMode.equals( 2139 getResourceString(R.string.pref_camera_hdr_supportmode_hdr_plus))) { 2140 buttonId = ButtonManager.BUTTON_HDR_PLUS; 2141 } 2142 buttonManager.showButton(buttonId); 2143 buttonManager.disableButton(buttonId); 2144 } 2145 } 2146 } 2147 2148 } 2149 if (bottomBarSpec.hideGridLines) { 2150 // Force hide grid lines icon. 2151 buttonManager.hideButton(ButtonManager.BUTTON_GRID_LINES); 2152 hideGridLines(); 2153 } else { 2154 if (bottomBarSpec.enableGridLines) { 2155 buttonManager.initializeButton(ButtonManager.BUTTON_GRID_LINES, 2156 bottomBarSpec.gridLinesCallback != null ? 2157 bottomBarSpec.gridLinesCallback : getGridLinesCallback() 2158 ); 2159 } else { 2160 buttonManager.disableButton(ButtonManager.BUTTON_GRID_LINES); 2161 hideGridLines(); 2162 } 2163 } 2164 2165 if (bottomBarSpec.enableSelfTimer) { 2166 buttonManager.initializeButton(ButtonManager.BUTTON_COUNTDOWN, null); 2167 } else { 2168 if (bottomBarSpec.showSelfTimer) { 2169 buttonManager.disableButton(ButtonManager.BUTTON_COUNTDOWN); 2170 } else { 2171 buttonManager.hideButton(ButtonManager.BUTTON_COUNTDOWN); 2172 } 2173 } 2174 2175 if (bottomBarSpec.enablePanoOrientation 2176 && PhotoSphereHelper.getPanoramaOrientationOptionArrayId() > 0) { 2177 buttonManager.initializePanoOrientationButtons(bottomBarSpec.panoOrientationCallback); 2178 } 2179 2180 2181 2182 // If manual exposure is enabled both in SettingsManager and 2183 // BottomBarSpec,then show the exposure button. 2184 // If manual exposure is disabled in the BottomBarSpec (eg. HDR+ 2185 // enabled), but the device/module has the feature, then disable the exposure 2186 // button. 2187 // Otherwise, hide the button. 2188 if (bottomBarSpec.enableExposureCompensation 2189 && !(bottomBarSpec.minExposureCompensation == 0 && bottomBarSpec.maxExposureCompensation == 0) 2190 && mController.getSettingsManager().getBoolean(SettingsManager.SCOPE_GLOBAL, 2191 Keys.KEY_EXPOSURE_COMPENSATION_ENABLED)) { 2192 buttonManager.initializePushButton(ButtonManager.BUTTON_EXPOSURE_COMPENSATION, 2193 new View.OnClickListener() { 2194 @Override 2195 public void onClick(View v) { 2196 mModeOptionsOverlay.showExposureOptions(); 2197 } 2198 }); 2199 buttonManager.setExposureCompensationParameters( 2200 bottomBarSpec.minExposureCompensation, 2201 bottomBarSpec.maxExposureCompensation, 2202 bottomBarSpec.exposureCompensationStep); 2203 2204 buttonManager.setExposureCompensationCallback( 2205 bottomBarSpec.exposureCompensationSetCallback); 2206 buttonManager.updateExposureButtons(); 2207 } else if (mController.getSettingsManager().getBoolean(SettingsManager.SCOPE_GLOBAL, 2208 Keys.KEY_EXPOSURE_COMPENSATION_ENABLED) 2209 && bottomBarSpec.isExposureCompensationSupported) { 2210 buttonManager.disableButton(ButtonManager.BUTTON_EXPOSURE_COMPENSATION); 2211 } else { 2212 buttonManager.hideButton(ButtonManager.BUTTON_EXPOSURE_COMPENSATION); 2213 buttonManager.setExposureCompensationCallback(null); 2214 } 2215 2216 /** Intent UI */ 2217 if (bottomBarSpec.showCancel) { 2218 buttonManager.initializePushButton(ButtonManager.BUTTON_CANCEL, 2219 bottomBarSpec.cancelCallback); 2220 } 2221 if (bottomBarSpec.showDone) { 2222 buttonManager.initializePushButton(ButtonManager.BUTTON_DONE, 2223 bottomBarSpec.doneCallback); 2224 } 2225 if (bottomBarSpec.showRetake) { 2226 buttonManager.initializePushButton(ButtonManager.BUTTON_RETAKE, 2227 bottomBarSpec.retakeCallback, 2228 R.drawable.ic_back, 2229 R.string.retake_button_description); 2230 } 2231 if (bottomBarSpec.showReview) { 2232 buttonManager.initializePushButton(ButtonManager.BUTTON_REVIEW, 2233 bottomBarSpec.reviewCallback, 2234 R.drawable.ic_play, 2235 R.string.review_button_description); 2236 } 2237 } 2238 2239 /** 2240 * Returns a {@link com.android.camera.ButtonManager.ButtonCallback} that 2241 * will disable the button identified by the parameter. 2242 * 2243 * @param conflictingButton The button id to be disabled. 2244 */ 2245 private ButtonManager.ButtonCallback getDisableButtonCallback(final int conflictingButton) { 2246 return new ButtonManager.ButtonCallback() { 2247 @Override 2248 public void onStateChanged(int state) { 2249 mController.getButtonManager().disableButton(conflictingButton); 2250 } 2251 }; 2252 } 2253 2254 private String getResourceString(int stringId) { 2255 try { 2256 return mController.getAndroidContext().getResources().getString(stringId); 2257 } catch (Resources.NotFoundException e) { 2258 // String not found, returning empty string. 2259 return ""; 2260 } 2261 } 2262 2263 /** 2264 * Shows the given tutorial on the screen. 2265 */ 2266 public void showTutorial(AbstractTutorialOverlay tutorial, LayoutInflater inflater) { 2267 tutorial.show(mTutorialsPlaceHolderWrapper, inflater); 2268 } 2269 2270 /** 2271 * Whether the capture ratio selector dialog must be shown on this device. 2272 * */ 2273 public boolean shouldShowAspectRatioDialog() { 2274 final boolean isAspectRatioPreferenceSet = mController.getSettingsManager().getBoolean( 2275 SettingsManager.SCOPE_GLOBAL, Keys.KEY_USER_SELECTED_ASPECT_RATIO); 2276 final boolean isAspectRatioDevice = 2277 ApiHelper.IS_NEXUS_4 || ApiHelper.IS_NEXUS_5 || ApiHelper.IS_NEXUS_6; 2278 return isAspectRatioDevice && !isAspectRatioPreferenceSet; 2279 } 2280 2281 2282 /***************************Filmstrip api *****************************/ 2283 2284 public void showFilmstrip() { 2285 mModeListView.onBackPressed(); 2286 mFilmstripLayout.showFilmstrip(); 2287 } 2288 2289 public void hideFilmstrip() { 2290 mFilmstripLayout.hideFilmstrip(); 2291 } 2292 2293 public int getFilmstripVisibility() { 2294 return mFilmstripLayout.getVisibility(); 2295 } 2296 } 2297