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