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