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