CameraAppUI.java revision 426901fb6adf728742ad2841ac595bc1414f3568
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.app.Activity; 20import android.content.Context; 21import android.graphics.Matrix; 22import android.graphics.SurfaceTexture; 23import android.hardware.display.DisplayManager; 24import android.util.Log; 25import android.view.GestureDetector; 26import android.view.LayoutInflater; 27import android.view.MotionEvent; 28import android.view.TextureView; 29import android.view.View; 30import android.view.ViewConfiguration; 31import android.view.ViewGroup; 32import android.widget.FrameLayout; 33 34import com.android.camera.AnimationManager; 35import com.android.camera.ButtonManager; 36import com.android.camera.ShutterButton; 37import com.android.camera.TextureViewHelper; 38import com.android.camera.filmstrip.FilmstripContentPanel; 39import com.android.camera.hardware.HardwareSpec; 40import com.android.camera.module.ModuleController; 41import com.android.camera.settings.SettingsManager; 42import com.android.camera.ui.BottomBar; 43import com.android.camera.ui.CaptureAnimationOverlay; 44import com.android.camera.ui.MainActivityLayout; 45import com.android.camera.ui.ModeListView; 46import com.android.camera.ui.ModeTransitionView; 47import com.android.camera.ui.PreviewOverlay; 48import com.android.camera.ui.PreviewStatusListener; 49import com.android.camera.util.ApiHelper; 50import com.android.camera.util.CameraUtil; 51import com.android.camera.util.PhotoSphereHelper; 52import com.android.camera.util.UsageStatistics; 53import com.android.camera.widget.FilmstripLayout; 54import com.android.camera.widget.IndicatorIconController; 55import com.android.camera.widget.IndicatorOverlay; 56import com.android.camera2.R; 57 58import com.google.common.logging.eventprotos; 59 60/** 61 * CameraAppUI centralizes control of views shared across modules. Whereas module 62 * specific views will be handled in each Module UI. For example, we can now 63 * bring the flash animation and capture animation up from each module to app 64 * level, as these animations are largely the same for all modules. 65 * 66 * This class also serves to disambiguate touch events. It recognizes all the 67 * swipe gestures that happen on the preview by attaching a touch listener to 68 * a full-screen view on top of preview TextureView. Since CameraAppUI has knowledge 69 * of how swipe from each direction should be handled, it can then redirect these 70 * events to appropriate recipient views. 71 */ 72public class CameraAppUI implements ModeListView.ModeSwitchListener, 73 TextureView.SurfaceTextureListener, ModeListView.ModeListOpenListener { 74 75 /** 76 * The bottom controls on the filmstrip. 77 */ 78 public static interface BottomControls { 79 /** Values for the view state of the button. */ 80 public final int VIEWER_NONE = 0; 81 public final int VIEWER_PHOTO_SPHERE = 1; 82 public final int VIEWER_REFOCUS = 2; 83 public final int VIEWER_OTHER = 3; 84 85 /** 86 * Sets a new or replaces an existing listener for bottom control events. 87 */ 88 void setListener(Listener listener); 89 90 /** 91 * Set if the bottom controls are visible. 92 * @param visible {@code true} if visible. 93 */ 94 void setVisible(boolean visible); 95 96 /** 97 * @param visible Whether the button is visible. 98 */ 99 void setEditButtonVisibility(boolean visible); 100 101 /** 102 * @param enabled Whether the button is enabled. 103 */ 104 void setEditEnabled(boolean enabled); 105 106 /** 107 * Sets the visibility of the view-photosphere button. 108 * 109 * @param state one of {@link #VIEWER_NONE}, {@link #VIEWER_PHOTO_SPHERE}, 110 * {@link #VIEWER_REFOCUS}. 111 */ 112 void setViewerButtonVisibility(int state); 113 114 /** 115 * @param enabled Whether the button is enabled. 116 */ 117 void setViewEnabled(boolean enabled); 118 119 /** 120 * @param visible Whether the button is visible. 121 */ 122 void setTinyPlanetButtonVisibility(boolean visible); 123 124 /** 125 * @param enabled Whether the button is enabled. 126 */ 127 void setTinyPlanetEnabled(boolean enabled); 128 129 /** 130 * @param visible Whether the button is visible. 131 */ 132 void setDeleteButtonVisibility(boolean visible); 133 134 /** 135 * @param enabled Whether the button is enabled. 136 */ 137 void setDeleteEnabled(boolean enabled); 138 139 /** 140 * @param visible Whether the button is visible. 141 */ 142 void setShareButtonVisibility(boolean visible); 143 144 /** 145 * @param enabled Whether the button is enabled. 146 */ 147 void setShareEnabled(boolean enabled); 148 149 /** 150 * Classes implementing this interface can listen for events on the bottom 151 * controls. 152 */ 153 public static interface Listener { 154 /** 155 * Called when the user pressed the "view" button to e.g. view a photo 156 * sphere or RGBZ image. 157 */ 158 public void onExternalViewer(); 159 160 /** 161 * Called when the "edit" button is pressed. 162 */ 163 public void onEdit(); 164 165 /** 166 * Called when the "tiny planet" button is pressed. 167 */ 168 public void onTinyPlanet(); 169 170 /** 171 * Called when the "delete" button is pressed. 172 */ 173 public void onDelete(); 174 175 /** 176 * Called when the "share" button is pressed. 177 */ 178 public void onShare(); 179 } 180 } 181 182 /** 183 * BottomBarUISpec provides a structure for modules 184 * to specify their ideal bottom bar mode options layout. 185 * 186 * Once constructed by a module, this class should be 187 * treated as read only. 188 * 189 * The application then edits this spec according to 190 * hardware limitations and displays the final bottom 191 * bar ui. 192 */ 193 public static class BottomBarUISpec { 194 /** Mode options UI */ 195 196 /** 197 * Set true if the camera option should be enabled. 198 * If not set or false, and multiple cameras are supported, 199 * the camera option will be disabled. 200 * 201 * If multiple cameras are not supported, this preference 202 * is ignored and the camera option will not be visible. 203 */ 204 public boolean enableCamera; 205 206 /** 207 * Set true if the photo flash option should be enabled. 208 * If not set or false, the photo flash option will be 209 * disabled. 210 * 211 * If the hardware does not support multiple flash values, 212 * this preference is ignored and the flash option will 213 * be disabled. It will not be made invisible in order to 214 * preserve a consistent experience across devices and between 215 * front and back cameras. 216 */ 217 public boolean enableFlash; 218 219 /** 220 * Set true if the video flash option should be enabled. 221 * Same disable rules apply as the photo flash option. 222 */ 223 public boolean enableTorchFlash; 224 225 /** 226 * Set true if the hdr/hdr+ option should be enabled. 227 * If not set or false, the hdr/hdr+ option will be disabled. 228 * 229 * Hdr or hdr+ will be chosen based on hardware limitations, 230 * with hdr+ prefered. 231 * 232 * If hardware supports neither hdr nor hdr+, then the hdr/hdr+ 233 * will not be visible. 234 */ 235 public boolean enableHdr; 236 237 /** 238 * Set true if hdr/hdr+ should not be visible, regardless of 239 * hardware limitations. 240 */ 241 public boolean hideHdr; 242 243 /** 244 * Set true if the refocus option should be enabled. 245 * If not set or false, the refocus option will be disabled. 246 * 247 * This option is not constrained by hardware limitations. 248 */ 249 public boolean enableRefocus; 250 251 /** 252 * Set true if refocus should not be visible. 253 */ 254 public boolean hideRefocus; 255 256 /** 257 * Set true if the panorama horizontal option should be visible. 258 * 259 * This option is not constrained by hardware limitations. 260 */ 261 public boolean enablePanoHorizontal; 262 263 /** 264 * Set true if the panorama vertical option should be visible. 265 * 266 * This option is not constrained by hardware limitations. 267 */ 268 public boolean enablePanoVertical; 269 270 /** Intent UI */ 271 272 /** 273 * Set true if the intent ui cancel option should be visible. 274 */ 275 public boolean showCancel; 276 /** 277 * Set true if the intent ui done option should be visible. 278 */ 279 public boolean showDone; 280 /** 281 * Set true if the intent ui retake option should be visible. 282 */ 283 public boolean showRetake; 284 /** 285 * Set true if the intent ui review option should be visible. 286 */ 287 public boolean showReview; 288 289 /** Mode options callbacks */ 290 291 /** 292 * A {@link android.com.android.camera.ButtonManager.ButtonCallback} 293 * that will be executed when the camera option is pressed. This 294 * callback can be null. 295 */ 296 public ButtonManager.ButtonCallback cameraCallback; 297 298 /** 299 * A {@link android.com.android.camera.ButtonManager.ButtonCallback} 300 * that will be executed when the flash option is pressed. This 301 * callback can be null. 302 */ 303 public ButtonManager.ButtonCallback flashCallback; 304 305 /** 306 * A {@link android.com.android.camera.ButtonManager.ButtonCallback} 307 * that will be executed when the hdr/hdr+ option is pressed. This 308 * callback can be null. 309 */ 310 public ButtonManager.ButtonCallback hdrCallback; 311 312 /** 313 * A {@link android.com.android.camera.ButtonManager.ButtonCallback} 314 * that will be executed when the refocus option is pressed. This 315 * callback can be null. 316 */ 317 public ButtonManager.ButtonCallback refocusCallback; 318 319 /** 320 * A {@link android.view.View.OnClickListener} that will execute 321 * when the panorama horizontal option is pressed. 322 * This callback can be null. 323 */ 324 public View.OnClickListener panoHorizontalCallback; 325 326 /** 327 * A {@link android.view.View.OnClickListener} that will execute 328 * when the panorama vertical option is pressed. 329 * This callback can be null. 330 */ 331 public View.OnClickListener panoVerticalCallback; 332 333 /** Intent UI callbacks */ 334 335 /** 336 * A {@link android.view.View.OnClickListener} that will execute 337 * when the cancel option is pressed. This callback can be null. 338 */ 339 public View.OnClickListener cancelCallback; 340 341 /** 342 * A {@link android.view.View.OnClickListener} that will execute 343 * when the done option is pressed. This callback can be null. 344 */ 345 public View.OnClickListener doneCallback; 346 347 /** 348 * A {@link android.view.View.OnClickListener} that will execute 349 * when the retake option is pressed. This callback can be null. 350 */ 351 public View.OnClickListener retakeCallback; 352 353 /** 354 * A {@link android.view.View.OnClickListener} that will execute 355 * when the review option is pressed. This callback can be null. 356 */ 357 public View.OnClickListener reviewCallback; 358 } 359 360 361 private final static String TAG = "CameraAppUI"; 362 363 private final AppController mController; 364 private final boolean mIsCaptureIntent; 365 private final AnimationManager mAnimationManager; 366 367 // Swipe states: 368 private final static int IDLE = 0; 369 private final static int SWIPE_UP = 1; 370 private final static int SWIPE_DOWN = 2; 371 private final static int SWIPE_LEFT = 3; 372 private final static int SWIPE_RIGHT = 4; 373 private boolean mSwipeEnabled = true; 374 375 // Touch related measures: 376 private final int mSlop; 377 private final static int SWIPE_TIME_OUT_MS = 500; 378 379 private final static int SHIMMY_DELAY_MS = 1000; 380 381 // Mode cover states: 382 private final static int COVER_HIDDEN = 0; 383 private final static int COVER_SHOWN = 1; 384 private final static int COVER_WILL_HIDE_AT_NEXT_FRAME = 2; 385 private static final int COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE = 3; 386 387 // App level views: 388 private final FrameLayout mCameraRootView; 389 private final ModeTransitionView mModeTransitionView; 390 private final MainActivityLayout mAppRootView; 391 private final ModeListView mModeListView; 392 private final FilmstripLayout mFilmstripLayout; 393 private TextureView mTextureView; 394 private FrameLayout mModuleUI; 395 private BottomBar mBottomBar; 396 private IndicatorOverlay mIndicatorOverlay; 397 private boolean mShouldShowShimmy = false; 398 private IndicatorIconController mIndicatorIconController; 399 400 private TextureViewHelper mTextureViewHelper; 401 private final GestureDetector mGestureDetector; 402 private DisplayManager.DisplayListener mDisplayListener; 403 private int mLastRotation; 404 private int mSwipeState = IDLE; 405 private PreviewOverlay mPreviewOverlay; 406 private CaptureAnimationOverlay mCaptureOverlay; 407 private PreviewStatusListener mPreviewStatusListener; 408 private int mModeCoverState = COVER_HIDDEN; 409 private final FilmstripBottomControls mFilmstripBottomControls; 410 private final FilmstripContentPanel mFilmstripPanel; 411 private Runnable mHideCoverRunnable; 412 private final View.OnLayoutChangeListener mPreviewLayoutChangeListener 413 = new View.OnLayoutChangeListener() { 414 @Override 415 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 416 int oldTop, int oldRight, int oldBottom) { 417 if (mPreviewStatusListener != null) { 418 mPreviewStatusListener.onPreviewLayoutChanged(v, left, top, right, bottom, oldLeft, 419 oldTop, oldRight, oldBottom); 420 } 421 } 422 }; 423 424 private long mCoverHiddenTime = -1; // System time when preview cover was hidden. 425 426 public long getCoverHiddenTime() { 427 return mCoverHiddenTime; 428 } 429 430 public void updatePreviewAspectRatio(float aspectRatio) { 431 mTextureViewHelper.updateAspectRatio(aspectRatio); 432 } 433 434 /** 435 * This is to support modules that calculate their own transform matrix because 436 * they need to use a transform matrix to rotate the preview. 437 * 438 * @param matrix transform matrix to be set on the TextureView 439 */ 440 public void updatePreviewTransform(Matrix matrix) { 441 mTextureViewHelper.updateTransform(matrix); 442 } 443 444 public interface AnimationFinishedListener { 445 public void onAnimationFinished(boolean success); 446 } 447 448 private class MyTouchListener implements View.OnTouchListener { 449 private boolean mScaleStarted = false; 450 @Override 451 public boolean onTouch(View v, MotionEvent event) { 452 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 453 mScaleStarted = false; 454 } else if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { 455 mScaleStarted = true; 456 } 457 return (!mScaleStarted) && mGestureDetector.onTouchEvent(event); 458 } 459 } 460 461 /** 462 * This gesture listener finds out the direction of the scroll gestures and 463 * sends them to CameraAppUI to do further handling. 464 */ 465 private class MyGestureListener extends GestureDetector.SimpleOnGestureListener { 466 private MotionEvent mDown; 467 468 @Override 469 public boolean onScroll(MotionEvent e1, MotionEvent ev, float distanceX, float distanceY) { 470 if (ev.getEventTime() - ev.getDownTime() > SWIPE_TIME_OUT_MS 471 || mSwipeState != IDLE 472 || mIsCaptureIntent 473 || !mSwipeEnabled) { 474 return false; 475 } 476 477 int deltaX = (int) (ev.getX() - mDown.getX()); 478 int deltaY = (int) (ev.getY() - mDown.getY()); 479 if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) { 480 if (Math.abs(deltaX) > mSlop || Math.abs(deltaY) > mSlop) { 481 // Calculate the direction of the swipe. 482 if (deltaX >= Math.abs(deltaY)) { 483 // Swipe right. 484 setSwipeState(SWIPE_RIGHT); 485 } else if (deltaX <= -Math.abs(deltaY)) { 486 // Swipe left. 487 setSwipeState(SWIPE_LEFT); 488 } else if (deltaY >= Math.abs(deltaX)) { 489 // Swipe down. 490 setSwipeState(SWIPE_DOWN); 491 } else if (deltaY <= -Math.abs(deltaX)) { 492 // Swipe up. 493 setSwipeState(SWIPE_UP); 494 } 495 } 496 } 497 return true; 498 } 499 500 private void setSwipeState(int swipeState) { 501 mSwipeState = swipeState; 502 // Notify new swipe detected. 503 onSwipeDetected(swipeState); 504 } 505 506 @Override 507 public boolean onDown(MotionEvent ev) { 508 mDown = MotionEvent.obtain(ev); 509 mSwipeState = IDLE; 510 return false; 511 } 512 } 513 514 public CameraAppUI(AppController controller, MainActivityLayout appRootView, 515 boolean isCaptureIntent) { 516 mSlop = ViewConfiguration.get(controller.getAndroidContext()).getScaledTouchSlop(); 517 mController = controller; 518 mIsCaptureIntent = isCaptureIntent; 519 520 mAppRootView = appRootView; 521 mFilmstripLayout = (FilmstripLayout) appRootView.findViewById(R.id.filmstrip_layout); 522 mCameraRootView = (FrameLayout) appRootView.findViewById(R.id.camera_app_root); 523 mModeTransitionView = (ModeTransitionView) 524 mAppRootView.findViewById(R.id.mode_transition_view); 525 mFilmstripBottomControls = new FilmstripBottomControls( 526 (ViewGroup) mAppRootView.findViewById(R.id.filmstrip_bottom_controls)); 527 mFilmstripPanel = (FilmstripContentPanel) mAppRootView.findViewById(R.id.filmstrip_layout); 528 mGestureDetector = new GestureDetector(controller.getAndroidContext(), 529 new MyGestureListener()); 530 mModeListView = (ModeListView) appRootView.findViewById(R.id.mode_list_layout); 531 if (mModeListView != null) { 532 mModeListView.setModeSwitchListener(this); 533 mModeListView.setModeListOpenListener(this); 534 } else { 535 Log.e(TAG, "Cannot find mode list in the view hierarchy"); 536 } 537 mAnimationManager = new AnimationManager(); 538 initDisplayListener(); 539 } 540 541 /** 542 * Enable or disable swipe gestures. We want to disable them e.g. while we 543 * record a video. 544 */ 545 public void setSwipeEnabled(boolean enabled) { 546 mAppRootView.setSwipeEnabled(enabled); 547 mSwipeEnabled = enabled; 548 } 549 550 /** 551 * Initializes the display listener to listen to display changes such as 552 * 180-degree rotation change, which will not have an onConfigurationChanged 553 * callback. 554 */ 555 private void initDisplayListener() { 556 if (ApiHelper.HAS_DISPLAY_LISTENER) { 557 mLastRotation = CameraUtil.getDisplayRotation( 558 (Activity) mController.getAndroidContext()); 559 560 mDisplayListener = new DisplayManager.DisplayListener() { 561 @Override 562 public void onDisplayAdded(int arg0) { 563 // Do nothing. 564 } 565 566 @Override 567 public void onDisplayChanged(int displayId) { 568 int rotation = CameraUtil.getDisplayRotation( 569 (Activity) mController.getAndroidContext()); 570 if ((rotation - mLastRotation + 360) % 360 == 180 571 && mPreviewStatusListener != null) { 572 mPreviewStatusListener.onPreviewFlipped(); 573 } 574 mLastRotation = rotation; 575 } 576 577 @Override 578 public void onDisplayRemoved(int arg0) { 579 // Do nothing. 580 } 581 }; 582 583 ((DisplayManager) mController.getAndroidContext() 584 .getSystemService(Context.DISPLAY_SERVICE)) 585 .registerDisplayListener(mDisplayListener, null); 586 } 587 } 588 589 /** 590 * Redirects touch events to appropriate recipient views based on swipe direction. 591 * More specifically, swipe up and swipe down will be handled by the view that handles 592 * mode transition; swipe left will be send to filmstrip; swipe right will be redirected 593 * to mode list in order to bring up mode list. 594 */ 595 private void onSwipeDetected(int swipeState) { 596 if (swipeState == SWIPE_UP || swipeState == SWIPE_DOWN) { 597 // Quick switch between modes. 598 int currentModuleIndex = mController.getCurrentModuleIndex(); 599 final int moduleToTransitionTo = 600 mController.getQuickSwitchToModuleId(currentModuleIndex); 601 if (currentModuleIndex != moduleToTransitionTo) { 602 mAppRootView.redirectTouchEventsTo(mModeTransitionView); 603 604 int shadeColorId = CameraUtil.getCameraThemeColorId(moduleToTransitionTo, 605 mController.getAndroidContext()); 606 int iconRes = CameraUtil.getCameraModeIconResId(moduleToTransitionTo, 607 mController.getAndroidContext()); 608 609 AnimationFinishedListener listener = new AnimationFinishedListener() { 610 @Override 611 public void onAnimationFinished(boolean success) { 612 if (success) { 613 mHideCoverRunnable = new Runnable() { 614 @Override 615 public void run() { 616 mModeTransitionView.startPeepHoleAnimation(); 617 } 618 }; 619 mModeCoverState = COVER_SHOWN; 620 // Go to new module when the previous operation is successful. 621 mController.onModeSelected(moduleToTransitionTo); 622 } 623 } 624 }; 625 if (mSwipeState == SWIPE_UP) { 626 mModeTransitionView.prepareToPullUpShade(shadeColorId, iconRes, listener); 627 } else { 628 mModeTransitionView.prepareToPullDownShade(shadeColorId, iconRes, listener); 629 } 630 } 631 } else if (swipeState == SWIPE_LEFT) { 632 // Pass the touch sequence to filmstrip layout. 633 UsageStatistics.changeScreen(eventprotos.NavigationChange.Mode.FILMSTRIP, 634 eventprotos.CameraEvent.InteractionCause.SWIPE_LEFT); 635 mAppRootView.redirectTouchEventsTo(mFilmstripLayout); 636 } else if (swipeState == SWIPE_RIGHT) { 637 // Pass the touch to mode switcher 638 mAppRootView.redirectTouchEventsTo(mModeListView); 639 } 640 } 641 642 /** 643 * Gets called when activity resumes in preview. 644 */ 645 public void resume() { 646 if (mTextureView == null || mTextureView.getSurfaceTexture() != null) { 647 if (!mIsCaptureIntent) { 648 showShimmyDelayed(); 649 } 650 } else { 651 // Show mode theme cover until preview is ready 652 showModeCoverUntilPreviewReady(); 653 } 654 // Hide action bar first since we are in full screen mode first, and 655 // switch the system UI to lights-out mode. 656 mFilmstripPanel.hide(); 657 } 658 659 /** 660 * A cover view showing the mode theme color and mode icon will be visible on 661 * top of preview until preview is ready (i.e. camera preview is started and 662 * the first frame has been received). 663 */ 664 private void showModeCoverUntilPreviewReady() { 665 int modeId = mController.getCurrentModuleIndex(); 666 int colorId = CameraUtil.getCameraThemeColorId(modeId, mController.getAndroidContext()); 667 int iconId = CameraUtil.getCameraModeIconResId(modeId, mController.getAndroidContext()); 668 mModeTransitionView.setupModeCover(colorId, iconId); 669 mHideCoverRunnable = new Runnable() { 670 @Override 671 public void run() { 672 mModeTransitionView.hideModeCover(new AnimationFinishedListener() { 673 @Override 674 public void onAnimationFinished(boolean success) { 675 if (success) { 676 showShimmyDelayed(); 677 } 678 } 679 }); 680 } 681 }; 682 mModeCoverState = COVER_SHOWN; 683 } 684 685 private void showShimmyDelayed() { 686 if (!mIsCaptureIntent) { 687 // Show shimmy in SHIMMY_DELAY_MS 688 mShouldShowShimmy = mController.shouldShowShimmy(); 689 if (mShouldShowShimmy) { 690 mModeListView.startAccordionAnimationWithDelay(SHIMMY_DELAY_MS); 691 } 692 } 693 } 694 695 private void hideModeCover() { 696 if (mHideCoverRunnable != null) { 697 mAppRootView.post(mHideCoverRunnable); 698 mHideCoverRunnable = null; 699 } 700 mModeCoverState = COVER_HIDDEN; 701 if (mCoverHiddenTime < 0) { 702 mCoverHiddenTime = System.currentTimeMillis(); 703 } 704 } 705 706 @Override 707 public void onOpenFullScreen() { 708 if (mShouldShowShimmy) { 709 mController.decrementShimmyPlayTimes(); 710 // Sets should show shimmy flag to false for this session (i.e. until 711 // next onResume) 712 mShouldShowShimmy = false; 713 } 714 } 715 716 /** 717 * Called when the back key is pressed. 718 * 719 * @return Whether the UI responded to the key event. 720 */ 721 public boolean onBackPressed() { 722 if (mFilmstripLayout.getVisibility() == View.VISIBLE) { 723 return mFilmstripLayout.onBackPressed(); 724 } else { 725 return mModeListView.onBackPressed(); 726 } 727 } 728 729 /** 730 * Sets a {@link com.android.camera.ui.PreviewStatusListener} that 731 * listens to SurfaceTexture changes. In addition, listeners are set on 732 * dependent app ui elements. 733 * 734 * @param previewStatusListener the listener that gets notified when SurfaceTexture 735 * changes 736 */ 737 public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) { 738 mPreviewStatusListener = previewStatusListener; 739 if (mPreviewStatusListener != null) { 740 onPreviewListenerChanged(); 741 } 742 } 743 744 /** 745 * When the PreviewStatusListener changes, listeners need to be 746 * set on the following app ui elements: 747 * {@link com.android.camera.ui.PreviewOverlay}, 748 * {@link com.android.camera.ui.BottomBar}, 749 * {@link com.android.camera.ui.IndicatorOverlay}, 750 * {@link com.android.camera.ui.IndicatorIconController}. 751 */ 752 private void onPreviewListenerChanged() { 753 // Set a listener for recognizing preview gestures. 754 GestureDetector.OnGestureListener gestureListener 755 = mPreviewStatusListener.getGestureListener(); 756 if (gestureListener != null) { 757 mPreviewOverlay.setGestureListener(gestureListener); 758 } 759 760 // Set a listener for resizing the bottom bar on 761 // preview size changes. 762 mTextureViewHelper.setAutoAdjustTransform( 763 mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout()); 764 if (mPreviewStatusListener.shouldAutoAdjustBottomBar()) { 765 mBottomBar = (BottomBar) mCameraRootView.findViewById(R.id.bottom_bar); 766 mTextureViewHelper.addPreviewAreaSizeChangedListener(mBottomBar); 767 } 768 769 // Set a listener for resizing the indicator overlay on 770 // preview size changes. 771 mIndicatorOverlay = (IndicatorOverlay) mAppRootView.findViewById( 772 R.id.indicator_overlay); 773 mTextureViewHelper.addPreviewAreaSizeChangedListener(mIndicatorOverlay); 774 775 if (mIndicatorIconController == null) { 776 mIndicatorIconController = 777 new IndicatorIconController(mController, mAppRootView); 778 } 779 mController.getSettingsManager().addListener(mIndicatorIconController); 780 } 781 782 /** 783 * This method should be called in onCameraOpened. It defines CameraAppUI 784 * specific changes that depend on the camera or camera settings. 785 */ 786 public void onChangeCamera() { 787 ModuleController moduleController = mController.getCurrentModuleController(); 788 if (moduleController.isUsingBottomBar()) { 789 applyModuleSpecs(moduleController.getHardwareSpec(), 790 moduleController.getBottomBarSpec()); 791 } 792 793 if (mIndicatorIconController != null) { 794 // Sync the settings state with the indicator state. 795 mIndicatorIconController.syncIndicators(); 796 } 797 } 798 799 /** 800 * Adds a listener to receive callbacks when preview area size changes. 801 */ 802 public void addPreviewAreaSizeChangedListener( 803 PreviewStatusListener.PreviewAreaSizeChangedListener listener) { 804 mTextureViewHelper.addPreviewAreaSizeChangedListener(listener); 805 } 806 807 /** 808 * Removes a listener that receives callbacks when preview area size changes. 809 */ 810 public void removePreviewAreaSizeChangedListener( 811 PreviewStatusListener.PreviewAreaSizeChangedListener listener) { 812 mTextureViewHelper.removePreviewAreaSizeChangedListener(listener); 813 } 814 815 /** 816 * This inflates generic_module layout, which contains all the shared views across 817 * modules. Then each module inflates their own views in the given view group. For 818 * now, this is called every time switching from a not-yet-refactored module to a 819 * refactored module. In the future, this should only need to be done once per app 820 * start. 821 */ 822 public void prepareModuleUI() { 823 mCameraRootView.removeAllViews(); 824 LayoutInflater inflater = (LayoutInflater) mController.getAndroidContext() 825 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 826 inflater.inflate(R.layout.generic_module, mCameraRootView, true); 827 828 mModuleUI = (FrameLayout) mCameraRootView.findViewById(R.id.module_layout); 829 mTextureView = (TextureView) mCameraRootView.findViewById(R.id.preview_content); 830 mTextureViewHelper = new TextureViewHelper(mTextureView); 831 mTextureViewHelper.setSurfaceTextureListener(this); 832 mTextureViewHelper.setOnLayoutChangeListener(mPreviewLayoutChangeListener); 833 834 mBottomBar = (BottomBar) mCameraRootView.findViewById(R.id.bottom_bar); 835 mBottomBar.setupToggle(mIsCaptureIntent); 836 837 mPreviewOverlay = (PreviewOverlay) mCameraRootView.findViewById(R.id.preview_overlay); 838 mPreviewOverlay.setOnTouchListener(new MyTouchListener()); 839 mPreviewOverlay.setOnPreviewTouchedListener(mBottomBar); 840 841 mCaptureOverlay = (CaptureAnimationOverlay) 842 mCameraRootView.findViewById(R.id.capture_overlay); 843 mTextureViewHelper.addPreviewAreaSizeChangedListener(mPreviewOverlay); 844 mTextureViewHelper.addPreviewAreaSizeChangedListener(mCaptureOverlay); 845 846 if (mIndicatorIconController == null) { 847 mIndicatorIconController = 848 new IndicatorIconController(mController, mAppRootView); 849 } 850 851 mController.getButtonManager().load(mCameraRootView); 852 mController.getButtonManager().setListener(mIndicatorIconController); 853 } 854 855 // TODO: Remove this when refactor is done. 856 // This is here to ensure refactored modules can work with not-yet-refactored ones. 857 public void clearCameraUI() { 858 mCameraRootView.removeAllViews(); 859 mModuleUI = null; 860 mTextureView = null; 861 mPreviewOverlay = null; 862 mBottomBar = null; 863 mIndicatorOverlay = null; 864 mIndicatorIconController = null; 865 setBottomBarShutterListener(null); 866 } 867 868 /** 869 * Called indirectly from each module in their initialization to get a view group 870 * to inflate the module specific views in. 871 * 872 * @return a view group for modules to attach views to 873 */ 874 public FrameLayout getModuleRootView() { 875 // TODO: Change it to mModuleUI when refactor is done 876 return mCameraRootView; 877 } 878 879 /** 880 * Remove all the module specific views. 881 */ 882 public void clearModuleUI() { 883 if (mModuleUI != null) { 884 mModuleUI.removeAllViews(); 885 } 886 mTextureViewHelper.addPreviewAreaSizeChangedListener(null); 887 888 mPreviewStatusListener = null; 889 mPreviewOverlay.reset(); 890 } 891 892 /** 893 * Gets called when preview is ready to start. It sets up one shot preview callback 894 * in order to receive a callback when the preview frame is available, so that 895 * the preview cover can be hidden to reveal preview. 896 * 897 * An alternative for getting the timing to hide preview cover is through 898 * {@link CameraAppUI#onSurfaceTextureUpdated(android.graphics.SurfaceTexture)}, 899 * which is less accurate but therefore is the fallback for modules that manage 900 * their own preview callbacks (as setting one preview callback will override 901 * any other installed preview callbacks), or use camera2 API. 902 */ 903 public void onPreviewReadyToStart() { 904 if (mModeCoverState == COVER_SHOWN) { 905 mModeCoverState = COVER_WILL_HIDE_AT_NEXT_FRAME; 906 mController.setupOneShotPreviewListener(); 907 } 908 } 909 910 /** 911 * Gets called when preview is started. 912 */ 913 public void onPreviewStarted() { 914 if (mModeCoverState == COVER_SHOWN) { 915 mModeCoverState = COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE; 916 } 917 } 918 919 /** 920 * Gets notified when next preview frame comes in. 921 */ 922 public void onNewPreviewFrame() { 923 hideModeCover(); 924 mModeCoverState = COVER_HIDDEN; 925 } 926 927 /** 928 * Gets called when a mode is selected from {@link com.android.camera.ui.ModeListView} 929 * 930 * @param modeIndex mode index of the selected mode 931 */ 932 @Override 933 public void onModeSelected(int modeIndex) { 934 mHideCoverRunnable = new Runnable() { 935 @Override 936 public void run() { 937 mModeListView.startModeSelectionAnimation(); 938 } 939 }; 940 mModeCoverState = COVER_SHOWN; 941 942 int lastIndex = mController.getCurrentModuleIndex(); 943 mController.onModeSelected(modeIndex); 944 int currentIndex = mController.getCurrentModuleIndex(); 945 946 if (mTextureView == null) { 947 // TODO: Remove this when all the modules use TextureView 948 int temporaryDelay = 600; // ms 949 mModeListView.postDelayed(new Runnable() { 950 @Override 951 public void run() { 952 hideModeCover(); 953 } 954 }, temporaryDelay); 955 } else if (lastIndex == currentIndex) { 956 hideModeCover(); 957 } 958 } 959 960 /********************** Capture animation **********************/ 961 /* TODO: This session is subject to UX changes. In addition to the generic 962 flash animation and post capture animation, consider designating a parameter 963 for specifying the type of animation, as well as an animation finished listener 964 so that modules can have more knowledge of the status of the animation. */ 965 966 /** 967 * Starts the pre-capture animation. 968 */ 969 public void startPreCaptureAnimation() { 970 mCaptureOverlay.startFlashAnimation(); 971 } 972 973 /** 974 * Cancels the pre-capture animation. 975 */ 976 public void cancelPreCaptureAnimation() { 977 mAnimationManager.cancelAnimations(); 978 } 979 980 /** 981 * Cancels the post-capture animation. 982 */ 983 public void cancelPostCaptureAnimation() { 984 mAnimationManager.cancelAnimations(); 985 } 986 987 public FilmstripContentPanel getFilmstripContentPanel() { 988 return mFilmstripPanel; 989 } 990 991 /** 992 * @return The {@link com.android.camera.app.CameraAppUI.BottomControls} on the 993 * bottom of the filmstrip. 994 */ 995 public BottomControls getFilmstripBottomControls() { 996 return mFilmstripBottomControls; 997 } 998 999 /** 1000 * @param listener The listener for bottom controls. 1001 */ 1002 public void setFilmstripBottomControlsListener(BottomControls.Listener listener) { 1003 mFilmstripBottomControls.setListener(listener); 1004 } 1005 1006 /***************************SurfaceTexture Listener*********************************/ 1007 1008 @Override 1009 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 1010 Log.v(TAG, "SurfaceTexture is available"); 1011 if (mPreviewStatusListener != null) { 1012 mPreviewStatusListener.onSurfaceTextureAvailable(surface, width, height); 1013 } 1014 } 1015 1016 @Override 1017 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 1018 if (mPreviewStatusListener != null) { 1019 mPreviewStatusListener.onSurfaceTextureSizeChanged(surface, width, height); 1020 } 1021 } 1022 1023 @Override 1024 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 1025 Log.v(TAG, "SurfaceTexture is destroyed"); 1026 if (mPreviewStatusListener != null) { 1027 return mPreviewStatusListener.onSurfaceTextureDestroyed(surface); 1028 } 1029 return false; 1030 } 1031 1032 @Override 1033 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 1034 if (mModeCoverState == COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE) { 1035 hideModeCover(); 1036 mModeCoverState = COVER_HIDDEN; 1037 } 1038 if (mPreviewStatusListener != null) { 1039 mPreviewStatusListener.onSurfaceTextureUpdated(surface); 1040 } 1041 } 1042 1043 /** 1044 * Sets the color of the bottom bar. 1045 */ 1046 public void setBottomBarColor(int colorId) { 1047 mBottomBar.setBackgroundColor(colorId); 1048 } 1049 1050 /** 1051 * Sets the pressed color of the bottom bar. 1052 */ 1053 public void setBottomBarPressedColor(int colorId) { 1054 mBottomBar.setBackgroundPressedColor(colorId); 1055 } 1056 1057 // TODO: refactor this out so it can controlled by the app. 1058 /** 1059 * Sets the shutter button icon on the bottom bar 1060 */ 1061 public void setBottomBarShutterIcon(int shutterIconId) { 1062 mBottomBar.setShutterButtonIcon(shutterIconId); 1063 } 1064 1065 public void animateBottomBarToCircle(int shutterIconId) { 1066 mBottomBar.animateToCircle(shutterIconId); 1067 } 1068 1069 public void animateBottomBarToFullSize(int shutterIconId) { 1070 mBottomBar.animateToFullSize(shutterIconId); 1071 } 1072 1073 /** 1074 * Set the visibility of the bottom bar. 1075 */ 1076 // TODO: needed for when panorama is managed by the generic module ui. 1077 public void setBottomBarVisible(boolean visible) { 1078 mBottomBar.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); 1079 } 1080 1081 /** 1082 * If the bottom bar is visible (hence has been drawn), 1083 * this sets a {@link #ShutterButton.OnShutterButtonListener} 1084 * on the global shutter button, 1085 */ 1086 public void setBottomBarShutterListener( 1087 ShutterButton.OnShutterButtonListener listener) { 1088 ShutterButton shutterButton 1089 = (ShutterButton) mCameraRootView.findViewById(R.id.shutter_button); 1090 if (shutterButton != null) { 1091 shutterButton.setOnShutterButtonListener(listener); 1092 } 1093 } 1094 1095 /** 1096 * Performs a transition to the global intent layout. 1097 */ 1098 public void transitionToIntentLayout() { 1099 ModuleController moduleController = mController.getCurrentModuleController(); 1100 if (moduleController.isUsingBottomBar()) { 1101 applyModuleSpecs(moduleController.getHardwareSpec(), 1102 moduleController.getBottomBarSpec()); 1103 mBottomBar.transitionToIntentLayout(); 1104 } 1105 } 1106 1107 /** 1108 * Performs a transition to the global intent review layout. 1109 */ 1110 public void transitionToIntentReviewLayout() { 1111 ModuleController moduleController = mController.getCurrentModuleController(); 1112 if (moduleController.isUsingBottomBar()) { 1113 applyModuleSpecs(moduleController.getHardwareSpec(), 1114 moduleController.getBottomBarSpec()); 1115 mBottomBar.transitionToIntentReviewLayout(); 1116 } 1117 } 1118 1119 /** 1120 * Applies a {@link com.android.camera.CameraAppUI.BottomBarUISpec} 1121 * to the bottom bar mode options based on limitations from a 1122 * {@link com.android.camera.hardware.HardwareSpec}. 1123 * 1124 * Options not supported by the hardware are either hidden 1125 * or disabled, depending on the option. 1126 * 1127 * Otherwise, the option is fully enabled and clickable. 1128 */ 1129 public void applyModuleSpecs(final HardwareSpec hardwareSpec, 1130 final BottomBarUISpec bottomBarSpec) { 1131 if (hardwareSpec == null || bottomBarSpec == null) { 1132 return; 1133 } 1134 1135 ButtonManager buttonManager = mController.getButtonManager(); 1136 SettingsManager settingsManager = mController.getSettingsManager(); 1137 1138 /** Standard mode options */ 1139 if (hardwareSpec.isFrontCameraSupported()) { 1140 if (bottomBarSpec.enableCamera) { 1141 buttonManager.enableButton(ButtonManager.BUTTON_CAMERA, 1142 bottomBarSpec.cameraCallback); 1143 } else { 1144 buttonManager.disableButton(ButtonManager.BUTTON_CAMERA); 1145 } 1146 } else { 1147 // Hide camera icon if front camera not available. 1148 buttonManager.hideButton(ButtonManager.BUTTON_CAMERA); 1149 } 1150 1151 if (hardwareSpec.isFlashSupported()) { 1152 if (bottomBarSpec.enableFlash && settingsManager.isCameraBackFacing()) { 1153 buttonManager.enableButton(ButtonManager.BUTTON_FLASH, bottomBarSpec.flashCallback); 1154 } else if (bottomBarSpec.enableTorchFlash && settingsManager.isCameraBackFacing()) { 1155 buttonManager.enableButton(ButtonManager.BUTTON_TORCH, bottomBarSpec.flashCallback); 1156 } else { 1157 buttonManager.disableButton(ButtonManager.BUTTON_FLASH); 1158 } 1159 } else { 1160 // Disable flash icon if not supported by the hardware. 1161 buttonManager.disableButton(ButtonManager.BUTTON_FLASH); 1162 } 1163 1164 if (bottomBarSpec.hideHdr) { 1165 // Force hide hdr or hdr plus icon. 1166 buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS); 1167 } else { 1168 if (hardwareSpec.isHdrPlusSupported()) { 1169 if (bottomBarSpec.enableHdr && settingsManager.isCameraBackFacing()) { 1170 buttonManager.enableButton(ButtonManager.BUTTON_HDRPLUS, 1171 bottomBarSpec.hdrCallback); 1172 } else { 1173 buttonManager.disableButton(ButtonManager.BUTTON_HDRPLUS); 1174 } 1175 } else if (hardwareSpec.isHdrSupported()) { 1176 if (bottomBarSpec.enableHdr && settingsManager.isCameraBackFacing()) { 1177 buttonManager.enableButton(ButtonManager.BUTTON_HDR, 1178 bottomBarSpec.hdrCallback); 1179 } else { 1180 buttonManager.disableButton(ButtonManager.BUTTON_HDR); 1181 } 1182 } else { 1183 // Hide hdr plus or hdr icon if neither are supported. 1184 buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS); 1185 } 1186 } 1187 1188 if (bottomBarSpec.hideRefocus) { 1189 buttonManager.hideButton(ButtonManager.BUTTON_REFOCUS); 1190 } else { 1191 if (bottomBarSpec.enableRefocus) { 1192 buttonManager.enableButton(ButtonManager.BUTTON_REFOCUS, 1193 bottomBarSpec.refocusCallback); 1194 } else { 1195 // Disable refocus icon when not enabled, not dependent 1196 // on hardware spec. 1197 buttonManager.disableButton(ButtonManager.BUTTON_REFOCUS); 1198 } 1199 } 1200 1201 if (bottomBarSpec.enablePanoHorizontal 1202 && PhotoSphereHelper.getPanoramaHorizontalDrawableId() > 0) { 1203 buttonManager.enablePushButton(ButtonManager.BUTTON_PANO_HORIZONTAL, 1204 bottomBarSpec.panoHorizontalCallback, 1205 PhotoSphereHelper.getPanoramaHorizontalDrawableId()); 1206 } 1207 1208 if (bottomBarSpec.enablePanoVertical 1209 && PhotoSphereHelper.getPanoramaVerticalDrawableId() > 0) { 1210 buttonManager.enablePushButton(ButtonManager.BUTTON_PANO_VERTICAL, 1211 bottomBarSpec.panoVerticalCallback, 1212 PhotoSphereHelper.getPanoramaVerticalDrawableId()); 1213 } 1214 1215 /** Intent UI */ 1216 if (bottomBarSpec.showCancel) { 1217 buttonManager.enablePushButton(ButtonManager.BUTTON_CANCEL, 1218 bottomBarSpec.cancelCallback); 1219 } 1220 if (bottomBarSpec.showDone) { 1221 buttonManager.enablePushButton(ButtonManager.BUTTON_DONE, 1222 bottomBarSpec.doneCallback); 1223 } 1224 if (bottomBarSpec.showRetake) { 1225 buttonManager.enablePushButton(ButtonManager.BUTTON_RETAKE, 1226 bottomBarSpec.retakeCallback); 1227 } 1228 } 1229} 1230