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