NotificationStackScrollLayout.java revision ff9c9c473f9cf4e273c9cfeeb595c5870d1053b8
1/* 2 * Copyright (C) 2014 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.systemui.statusbar.stack; 18 19import android.content.Context; 20import android.content.res.Configuration; 21import android.graphics.Canvas; 22import android.graphics.Paint; 23import android.util.AttributeSet; 24import android.util.Log; 25import android.view.MotionEvent; 26import android.view.VelocityTracker; 27import android.view.View; 28import android.view.ViewConfiguration; 29import android.view.ViewGroup; 30import android.view.ViewTreeObserver; 31import android.view.animation.AnimationUtils; 32import android.widget.OverScroller; 33 34import com.android.systemui.ExpandHelper; 35import com.android.systemui.R; 36import com.android.systemui.SwipeHelper; 37import com.android.systemui.statusbar.ActivatableNotificationView; 38import com.android.systemui.statusbar.DismissView; 39import com.android.systemui.statusbar.ExpandableNotificationRow; 40import com.android.systemui.statusbar.ExpandableView; 41import com.android.systemui.statusbar.SpeedBumpView; 42import com.android.systemui.statusbar.policy.ScrollAdapter; 43import com.android.systemui.statusbar.stack.StackScrollState.ViewState; 44 45import java.util.ArrayList; 46import java.util.HashSet; 47 48/** 49 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack. 50 */ 51public class NotificationStackScrollLayout extends ViewGroup 52 implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter, 53 ExpandableView.OnHeightChangedListener { 54 55 private static final String TAG = "NotificationStackScrollLayout"; 56 private static final boolean DEBUG = false; 57 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f; 58 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f; 59 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f; 60 61 /** 62 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}. 63 */ 64 private static final int INVALID_POINTER = -1; 65 66 private ExpandHelper mExpandHelper; 67 private SwipeHelper mSwipeHelper; 68 private boolean mSwipingInProgress; 69 private int mCurrentStackHeight = Integer.MAX_VALUE; 70 private int mOwnScrollY; 71 private int mMaxLayoutHeight; 72 73 private VelocityTracker mVelocityTracker; 74 private OverScroller mScroller; 75 private int mTouchSlop; 76 private int mMinimumVelocity; 77 private int mMaximumVelocity; 78 private int mOverflingDistance; 79 private float mMaxOverScroll; 80 private boolean mIsBeingDragged; 81 private int mLastMotionY; 82 private int mDownX; 83 private int mActivePointerId; 84 85 private int mSidePaddings; 86 private Paint mDebugPaint; 87 private int mContentHeight; 88 private int mCollapsedSize; 89 private int mBottomStackSlowDownHeight; 90 private int mBottomStackPeekSize; 91 private int mPaddingBetweenElements; 92 private int mPaddingBetweenElementsDimmed; 93 private int mPaddingBetweenElementsNormal; 94 private int mTopPadding; 95 private int mCollapseSecondCardPadding; 96 97 /** 98 * The algorithm which calculates the properties for our children 99 */ 100 private StackScrollAlgorithm mStackScrollAlgorithm; 101 102 /** 103 * The current State this Layout is in 104 */ 105 private StackScrollState mCurrentStackScrollState = new StackScrollState(this); 106 private AmbientState mAmbientState = new AmbientState(); 107 private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>(); 108 private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>(); 109 private ArrayList<View> mSnappedBackChildren = new ArrayList<View>(); 110 private ArrayList<View> mDragAnimPendingChildren = new ArrayList<View>(); 111 private ArrayList<View> mChildrenChangingPositions = new ArrayList<View>(); 112 private HashSet<View> mFromMoreCardAdditions = new HashSet<>(); 113 private ArrayList<AnimationEvent> mAnimationEvents 114 = new ArrayList<AnimationEvent>(); 115 private ArrayList<View> mSwipedOutViews = new ArrayList<View>(); 116 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this); 117 private boolean mAnimationsEnabled; 118 private boolean mChangePositionInProgress; 119 120 /** 121 * The raw amount of the overScroll on the top, which is not rubber-banded. 122 */ 123 private float mOverScrolledTopPixels; 124 125 /** 126 * The raw amount of the overScroll on the bottom, which is not rubber-banded. 127 */ 128 private float mOverScrolledBottomPixels; 129 130 private OnChildLocationsChangedListener mListener; 131 private OnOverscrollTopChangedListener mOverscrollTopChangedListener; 132 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener; 133 private boolean mNeedsAnimation; 134 private boolean mTopPaddingNeedsAnimation; 135 private boolean mDimmedNeedsAnimation; 136 private boolean mHideSensitiveNeedsAnimation; 137 private boolean mDarkNeedsAnimation; 138 private boolean mActivateNeedsAnimation; 139 private boolean mGoToFullShadeNeedsAnimation; 140 private boolean mIsExpanded = true; 141 private boolean mChildrenUpdateRequested; 142 private SpeedBumpView mSpeedBumpView; 143 private boolean mIsExpansionChanging; 144 private boolean mExpandingNotification; 145 private boolean mExpandedInThisMotion; 146 private boolean mScrollingEnabled; 147 private DismissView mDismissView; 148 private boolean mDismissAllInProgress; 149 150 /** 151 * Was the scroller scrolled to the top when the down motion was observed? 152 */ 153 private boolean mScrolledToTopOnFirstDown; 154 155 /** 156 * The minimal amount of over scroll which is needed in order to switch to the quick settings 157 * when over scrolling on a expanded card. 158 */ 159 private float mMinTopOverScrollToEscape; 160 private int mIntrinsicPadding; 161 private int mNotificationTopPadding; 162 private float mTopPaddingOverflow; 163 private boolean mDontReportNextOverScroll; 164 165 /** 166 * The maximum scrollPosition which we are allowed to reach when a notification was expanded. 167 * This is needed to avoid scrolling too far after the notification was collapsed in the same 168 * motion. 169 */ 170 private int mMaxScrollAfterExpand; 171 private SwipeHelper.LongPressListener mLongPressListener; 172 173 /** 174 * Should in this touch motion only be scrolling allowed? It's true when the scroller was 175 * animating. 176 */ 177 private boolean mOnlyScrollingInThisMotion; 178 private ViewGroup mScrollView; 179 private boolean mInterceptDelegateEnabled; 180 private boolean mDelegateToScrollView; 181 private boolean mDisallowScrollingInThisMotion; 182 private long mGoToFullShadeDelay; 183 184 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater 185 = new ViewTreeObserver.OnPreDrawListener() { 186 @Override 187 public boolean onPreDraw() { 188 updateChildren(); 189 mChildrenUpdateRequested = false; 190 getViewTreeObserver().removeOnPreDrawListener(this); 191 return true; 192 } 193 }; 194 195 public NotificationStackScrollLayout(Context context) { 196 this(context, null); 197 } 198 199 public NotificationStackScrollLayout(Context context, AttributeSet attrs) { 200 this(context, attrs, 0); 201 } 202 203 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) { 204 this(context, attrs, defStyleAttr, 0); 205 } 206 207 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr, 208 int defStyleRes) { 209 super(context, attrs, defStyleAttr, defStyleRes); 210 initView(context); 211 if (DEBUG) { 212 setWillNotDraw(false); 213 mDebugPaint = new Paint(); 214 mDebugPaint.setColor(0xffff0000); 215 mDebugPaint.setStrokeWidth(2); 216 mDebugPaint.setStyle(Paint.Style.STROKE); 217 } 218 } 219 220 @Override 221 protected void onDraw(Canvas canvas) { 222 if (DEBUG) { 223 int y = mCollapsedSize; 224 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 225 y = (int) (getLayoutHeight() - mBottomStackPeekSize 226 - mBottomStackSlowDownHeight); 227 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 228 y = (int) (getLayoutHeight() - mBottomStackPeekSize); 229 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 230 y = (int) getLayoutHeight(); 231 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 232 y = getHeight() - getEmptyBottomMargin(); 233 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 234 } 235 } 236 237 private void initView(Context context) { 238 mScroller = new OverScroller(getContext()); 239 setFocusable(true); 240 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 241 setClipChildren(false); 242 final ViewConfiguration configuration = ViewConfiguration.get(context); 243 mTouchSlop = configuration.getScaledTouchSlop(); 244 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 245 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 246 mOverflingDistance = configuration.getScaledOverflingDistance(); 247 float densityScale = getResources().getDisplayMetrics().density; 248 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); 249 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext()); 250 mSwipeHelper.setLongPressListener(mLongPressListener); 251 252 mSidePaddings = context.getResources() 253 .getDimensionPixelSize(R.dimen.notification_side_padding); 254 mCollapsedSize = context.getResources() 255 .getDimensionPixelSize(R.dimen.notification_min_height); 256 mBottomStackPeekSize = context.getResources() 257 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount); 258 mStackScrollAlgorithm = new StackScrollAlgorithm(context); 259 mStackScrollAlgorithm.setDimmed(mAmbientState.isDimmed()); 260 mPaddingBetweenElementsDimmed = context.getResources() 261 .getDimensionPixelSize(R.dimen.notification_padding_dimmed); 262 mPaddingBetweenElementsNormal = context.getResources() 263 .getDimensionPixelSize(R.dimen.notification_padding); 264 updatePadding(mAmbientState.isDimmed()); 265 int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); 266 int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height); 267 mExpandHelper = new ExpandHelper(getContext(), this, 268 minHeight, maxHeight); 269 mExpandHelper.setEventSource(this); 270 mExpandHelper.setScrollAdapter(this); 271 mMinTopOverScrollToEscape = getResources().getDimensionPixelSize( 272 R.dimen.min_top_overscroll_to_qs); 273 mNotificationTopPadding = getResources().getDimensionPixelSize( 274 R.dimen.notifications_top_padding); 275 mCollapseSecondCardPadding = getResources().getDimensionPixelSize( 276 R.dimen.notification_collapse_second_card_padding); 277 } 278 279 private void updatePadding(boolean dimmed) { 280 mPaddingBetweenElements = dimmed 281 ? mPaddingBetweenElementsDimmed 282 : mPaddingBetweenElementsNormal; 283 mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength(); 284 updateContentHeight(); 285 notifyHeightChangeListener(null); 286 } 287 288 private void notifyHeightChangeListener(ExpandableView view) { 289 if (mOnHeightChangedListener != null) { 290 mOnHeightChangedListener.onHeightChanged(view); 291 } 292 } 293 294 @Override 295 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 296 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 297 int mode = MeasureSpec.getMode(widthMeasureSpec); 298 int size = MeasureSpec.getSize(widthMeasureSpec); 299 int childMeasureSpec = MeasureSpec.makeMeasureSpec(size - 2 * mSidePaddings, mode); 300 measureChildren(childMeasureSpec, heightMeasureSpec); 301 } 302 303 @Override 304 protected void onLayout(boolean changed, int l, int t, int r, int b) { 305 306 // we layout all our children centered on the top 307 float centerX = getWidth() / 2.0f; 308 for (int i = 0; i < getChildCount(); i++) { 309 View child = getChildAt(i); 310 float width = child.getMeasuredWidth(); 311 float height = child.getMeasuredHeight(); 312 child.layout((int) (centerX - width / 2.0f), 313 0, 314 (int) (centerX + width / 2.0f), 315 (int) height); 316 } 317 setMaxLayoutHeight(getHeight()); 318 updateContentHeight(); 319 clampScrollPosition(); 320 requestChildrenUpdate(); 321 } 322 323 public void updateSpeedBumpIndex(int newIndex) { 324 int currentIndex = indexOfChild(mSpeedBumpView); 325 326 // If we are currently layouted before the new speed bump index, we have to decrease it. 327 boolean validIndex = newIndex > 0; 328 if (newIndex > getChildCount() - 1) { 329 validIndex = false; 330 newIndex = -1; 331 } 332 if (validIndex && currentIndex != newIndex) { 333 changeViewPosition(mSpeedBumpView, newIndex); 334 } 335 updateSpeedBump(validIndex); 336 mAmbientState.setSpeedBumpIndex(newIndex); 337 } 338 339 public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) { 340 mListener = listener; 341 } 342 343 /** 344 * Returns the location the given child is currently rendered at. 345 * 346 * @param child the child to get the location for 347 * @return one of {@link ViewState}'s <code>LOCATION_*</code> constants 348 */ 349 public int getChildLocation(View child) { 350 ViewState childViewState = mCurrentStackScrollState.getViewStateForView(child); 351 if (childViewState == null) { 352 return ViewState.LOCATION_UNKNOWN; 353 } 354 return childViewState.location; 355 } 356 357 private void setMaxLayoutHeight(int maxLayoutHeight) { 358 mMaxLayoutHeight = maxLayoutHeight; 359 updateAlgorithmHeightAndPadding(); 360 } 361 362 private void updateAlgorithmHeightAndPadding() { 363 mStackScrollAlgorithm.setLayoutHeight(getLayoutHeight()); 364 mStackScrollAlgorithm.setTopPadding(mTopPadding); 365 } 366 367 /** 368 * @return whether the height of the layout needs to be adapted, in order to ensure that the 369 * last child is not in the bottom stack. 370 */ 371 private boolean needsHeightAdaption() { 372 return getNotGoneChildCount() > 1; 373 } 374 375 /** 376 * Updates the children views according to the stack scroll algorithm. Call this whenever 377 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout. 378 */ 379 private void updateChildren() { 380 mAmbientState.setScrollY(mOwnScrollY); 381 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState); 382 if (!isCurrentlyAnimating() && !mNeedsAnimation) { 383 applyCurrentState(); 384 } else { 385 startAnimationToState(); 386 } 387 } 388 389 private void requestChildrenUpdate() { 390 if (!mChildrenUpdateRequested) { 391 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater); 392 mChildrenUpdateRequested = true; 393 invalidate(); 394 } 395 } 396 397 private boolean isCurrentlyAnimating() { 398 return mStateAnimator.isRunning(); 399 } 400 401 private void clampScrollPosition() { 402 int scrollRange = getScrollRange(); 403 if (scrollRange < mOwnScrollY) { 404 mOwnScrollY = scrollRange; 405 } 406 } 407 408 public int getTopPadding() { 409 return mTopPadding; 410 } 411 412 private void setTopPadding(int topPadding, boolean animate) { 413 if (mTopPadding != topPadding) { 414 mTopPadding = topPadding; 415 updateAlgorithmHeightAndPadding(); 416 updateContentHeight(); 417 if (animate && mAnimationsEnabled && mIsExpanded) { 418 mTopPaddingNeedsAnimation = true; 419 mNeedsAnimation = true; 420 } 421 requestChildrenUpdate(); 422 notifyHeightChangeListener(null); 423 } 424 } 425 426 /** 427 * Update the height of the stack to a new height. 428 * 429 * @param height the new height of the stack 430 */ 431 public void setStackHeight(float height) { 432 setIsExpanded(height > 0.0f); 433 int newStackHeight = (int) height; 434 int minStackHeight = getMinStackHeight(); 435 int stackHeight; 436 if (newStackHeight - mTopPadding >= minStackHeight || getNotGoneChildCount() == 0) { 437 setTranslationY(mTopPaddingOverflow); 438 stackHeight = newStackHeight; 439 } else { 440 441 // We did not reach the position yet where we actually start growing, 442 // so we translate the stack upwards. 443 int translationY = (newStackHeight - minStackHeight); 444 // A slight parallax effect is introduced in order for the stack to catch up with 445 // the top card. 446 float partiallyThere = (float) (newStackHeight - mTopPadding) / minStackHeight; 447 partiallyThere = Math.max(0, partiallyThere); 448 translationY += (1 - partiallyThere) * (mBottomStackPeekSize + 449 mCollapseSecondCardPadding); 450 setTranslationY(translationY - mTopPadding); 451 stackHeight = (int) (height - (translationY - mTopPadding)); 452 } 453 if (stackHeight != mCurrentStackHeight) { 454 mCurrentStackHeight = stackHeight; 455 updateAlgorithmHeightAndPadding(); 456 requestChildrenUpdate(); 457 } 458 } 459 460 /** 461 * Get the current height of the view. This is at most the msize of the view given by a the 462 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight} 463 * 464 * @return either the layout height or the externally defined height, whichever is smaller 465 */ 466 private int getLayoutHeight() { 467 return Math.min(mMaxLayoutHeight, mCurrentStackHeight); 468 } 469 470 public int getItemHeight() { 471 return mCollapsedSize; 472 } 473 474 public int getBottomStackPeekSize() { 475 return mBottomStackPeekSize; 476 } 477 478 public int getCollapseSecondCardPadding() { 479 return mCollapseSecondCardPadding; 480 } 481 482 public void setLongPressListener(SwipeHelper.LongPressListener listener) { 483 mSwipeHelper.setLongPressListener(listener); 484 mLongPressListener = listener; 485 } 486 487 public void setScrollView(ViewGroup scrollView) { 488 mScrollView = scrollView; 489 } 490 491 public void setInterceptDelegateEnabled(boolean interceptDelegateEnabled) { 492 mInterceptDelegateEnabled = interceptDelegateEnabled; 493 } 494 495 public void onChildDismissed(View v) { 496 if (mDismissAllInProgress) { 497 return; 498 } 499 if (DEBUG) Log.v(TAG, "onChildDismissed: " + v); 500 final View veto = v.findViewById(R.id.veto); 501 if (veto != null && veto.getVisibility() != View.GONE) { 502 veto.performClick(); 503 } 504 setSwipingInProgress(false); 505 if (mDragAnimPendingChildren.contains(v)) { 506 // We start the swipe and finish it in the same frame, we don't want any animation 507 // for the drag 508 mDragAnimPendingChildren.remove(v); 509 } 510 mSwipedOutViews.add(v); 511 mAmbientState.onDragFinished(v); 512 } 513 514 @Override 515 public void onChildSnappedBack(View animView) { 516 mAmbientState.onDragFinished(animView); 517 if (!mDragAnimPendingChildren.contains(animView)) { 518 if (mAnimationsEnabled) { 519 mSnappedBackChildren.add(animView); 520 mNeedsAnimation = true; 521 } 522 requestChildrenUpdate(); 523 } else { 524 // We start the swipe and snap back in the same frame, we don't want any animation 525 mDragAnimPendingChildren.remove(animView); 526 } 527 } 528 529 @Override 530 public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { 531 return false; 532 } 533 534 public void onBeginDrag(View v) { 535 setSwipingInProgress(true); 536 mAmbientState.onBeginDrag(v); 537 if (mAnimationsEnabled) { 538 mDragAnimPendingChildren.add(v); 539 mNeedsAnimation = true; 540 } 541 requestChildrenUpdate(); 542 } 543 544 public void onDragCancelled(View v) { 545 setSwipingInProgress(false); 546 } 547 548 public View getChildAtPosition(MotionEvent ev) { 549 return getChildAtPosition(ev.getX(), ev.getY()); 550 } 551 552 public ExpandableView getChildAtRawPosition(float touchX, float touchY) { 553 int[] location = new int[2]; 554 getLocationOnScreen(location); 555 return getChildAtPosition(touchX - location[0], touchY - location[1]); 556 } 557 558 public ExpandableView getChildAtPosition(float touchX, float touchY) { 559 // find the view under the pointer, accounting for GONE views 560 final int count = getChildCount(); 561 for (int childIdx = 0; childIdx < count; childIdx++) { 562 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx); 563 if (slidingChild.getVisibility() == GONE) { 564 continue; 565 } 566 float childTop = slidingChild.getTranslationY(); 567 float top = childTop + slidingChild.getClipTopAmount(); 568 float bottom = top + slidingChild.getActualHeight(); 569 int left = slidingChild.getLeft(); 570 int right = slidingChild.getRight(); 571 572 if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) { 573 return slidingChild; 574 } 575 } 576 return null; 577 } 578 579 public boolean canChildBeExpanded(View v) { 580 return v instanceof ExpandableNotificationRow 581 && ((ExpandableNotificationRow) v).isExpandable(); 582 } 583 584 public void setUserExpandedChild(View v, boolean userExpanded) { 585 if (v instanceof ExpandableNotificationRow) { 586 ((ExpandableNotificationRow) v).setUserExpanded(userExpanded); 587 } 588 } 589 590 public void setUserLockedChild(View v, boolean userLocked) { 591 if (v instanceof ExpandableNotificationRow) { 592 ((ExpandableNotificationRow) v).setUserLocked(userLocked); 593 } 594 removeLongPressCallback(); 595 requestDisallowInterceptTouchEvent(true); 596 } 597 598 @Override 599 public void expansionStateChanged(boolean isExpanding) { 600 mExpandingNotification = isExpanding; 601 if (!mExpandedInThisMotion) { 602 mMaxScrollAfterExpand = mOwnScrollY; 603 mExpandedInThisMotion = true; 604 } 605 } 606 607 public void setScrollingEnabled(boolean enable) { 608 mScrollingEnabled = enable; 609 } 610 611 public void setExpandingEnabled(boolean enable) { 612 mExpandHelper.setEnabled(enable); 613 } 614 615 private boolean isScrollingEnabled() { 616 return mScrollingEnabled; 617 } 618 619 public View getChildContentView(View v) { 620 return v; 621 } 622 623 public boolean canChildBeDismissed(View v) { 624 final View veto = v.findViewById(R.id.veto); 625 return (veto != null && veto.getVisibility() != View.GONE); 626 } 627 628 private void setSwipingInProgress(boolean isSwiped) { 629 mSwipingInProgress = isSwiped; 630 if(isSwiped) { 631 requestDisallowInterceptTouchEvent(true); 632 } 633 } 634 635 @Override 636 protected void onConfigurationChanged(Configuration newConfig) { 637 super.onConfigurationChanged(newConfig); 638 float densityScale = getResources().getDisplayMetrics().density; 639 mSwipeHelper.setDensityScale(densityScale); 640 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); 641 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); 642 initView(getContext()); 643 } 644 645 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) { 646 child.setClipBounds(null); 647 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration); 648 } 649 650 @Override 651 public boolean onTouchEvent(MotionEvent ev) { 652 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL 653 || ev.getActionMasked()== MotionEvent.ACTION_UP; 654 if (mDelegateToScrollView) { 655 if (isCancelOrUp) { 656 mDelegateToScrollView = false; 657 } 658 transformTouchEvent(ev, this, mScrollView); 659 return mScrollView.onTouchEvent(ev); 660 } 661 boolean expandWantsIt = false; 662 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) { 663 if (isCancelOrUp) { 664 mExpandHelper.onlyObserveMovements(false); 665 } 666 boolean wasExpandingBefore = mExpandingNotification; 667 expandWantsIt = mExpandHelper.onTouchEvent(ev); 668 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore 669 && !mDisallowScrollingInThisMotion) { 670 dispatchDownEventToScroller(ev); 671 } 672 } 673 boolean scrollerWantsIt = false; 674 if (!mSwipingInProgress && !mExpandingNotification && !mDisallowScrollingInThisMotion) { 675 scrollerWantsIt = onScrollTouch(ev); 676 } 677 boolean horizontalSwipeWantsIt = false; 678 if (!mIsBeingDragged 679 && !mExpandingNotification 680 && !mExpandedInThisMotion 681 && !mOnlyScrollingInThisMotion) { 682 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev); 683 } 684 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev); 685 } 686 687 private void dispatchDownEventToScroller(MotionEvent ev) { 688 MotionEvent downEvent = MotionEvent.obtain(ev); 689 downEvent.setAction(MotionEvent.ACTION_DOWN); 690 onScrollTouch(downEvent); 691 downEvent.recycle(); 692 } 693 694 private boolean onScrollTouch(MotionEvent ev) { 695 if (!isScrollingEnabled()) { 696 return false; 697 } 698 initVelocityTrackerIfNotExists(); 699 mVelocityTracker.addMovement(ev); 700 701 final int action = ev.getAction(); 702 703 switch (action & MotionEvent.ACTION_MASK) { 704 case MotionEvent.ACTION_DOWN: { 705 if (getChildCount() == 0 || !isInContentBounds(ev)) { 706 return false; 707 } 708 boolean isBeingDragged = !mScroller.isFinished(); 709 setIsBeingDragged(isBeingDragged); 710 711 /* 712 * If being flinged and user touches, stop the fling. isFinished 713 * will be false if being flinged. 714 */ 715 if (!mScroller.isFinished()) { 716 mScroller.forceFinished(true); 717 } 718 719 // Remember where the motion event started 720 mLastMotionY = (int) ev.getY(); 721 mDownX = (int) ev.getX(); 722 mActivePointerId = ev.getPointerId(0); 723 break; 724 } 725 case MotionEvent.ACTION_MOVE: 726 final int activePointerIndex = ev.findPointerIndex(mActivePointerId); 727 if (activePointerIndex == -1) { 728 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent"); 729 break; 730 } 731 732 final int y = (int) ev.getY(activePointerIndex); 733 final int x = (int) ev.getX(activePointerIndex); 734 int deltaY = mLastMotionY - y; 735 final int xDiff = Math.abs(x - mDownX); 736 final int yDiff = Math.abs(deltaY); 737 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) { 738 setIsBeingDragged(true); 739 if (deltaY > 0) { 740 deltaY -= mTouchSlop; 741 } else { 742 deltaY += mTouchSlop; 743 } 744 } 745 if (mIsBeingDragged) { 746 // Scroll to follow the motion event 747 mLastMotionY = y; 748 int range = getScrollRange(); 749 if (mExpandedInThisMotion) { 750 range = Math.min(range, mMaxScrollAfterExpand); 751 } 752 753 float scrollAmount; 754 if (deltaY < 0) { 755 scrollAmount = overScrollDown(deltaY); 756 } else { 757 scrollAmount = overScrollUp(deltaY, range); 758 } 759 760 // Calling overScrollBy will call onOverScrolled, which 761 // calls onScrollChanged if applicable. 762 if (scrollAmount != 0.0f) { 763 // The scrolling motion could not be compensated with the 764 // existing overScroll, we have to scroll the view 765 overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY, 766 0, range, 0, getHeight() / 2, true); 767 } 768 } 769 break; 770 case MotionEvent.ACTION_UP: 771 if (mIsBeingDragged) { 772 final VelocityTracker velocityTracker = mVelocityTracker; 773 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 774 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); 775 776 if (shouldOverScrollFling(initialVelocity)) { 777 onOverScrollFling(true, initialVelocity); 778 } else { 779 if (getChildCount() > 0) { 780 if ((Math.abs(initialVelocity) > mMinimumVelocity)) { 781 float currentOverScrollTop = getCurrentOverScrollAmount(true); 782 if (currentOverScrollTop == 0.0f || initialVelocity > 0) { 783 fling(-initialVelocity); 784 } else { 785 onOverScrollFling(false, initialVelocity); 786 } 787 } else { 788 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, 789 getScrollRange())) { 790 postInvalidateOnAnimation(); 791 } 792 } 793 } 794 } 795 796 mActivePointerId = INVALID_POINTER; 797 endDrag(); 798 } 799 800 break; 801 case MotionEvent.ACTION_CANCEL: 802 if (mIsBeingDragged && getChildCount() > 0) { 803 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { 804 postInvalidateOnAnimation(); 805 } 806 mActivePointerId = INVALID_POINTER; 807 endDrag(); 808 } 809 break; 810 case MotionEvent.ACTION_POINTER_DOWN: { 811 final int index = ev.getActionIndex(); 812 mLastMotionY = (int) ev.getY(index); 813 mDownX = (int) ev.getX(index); 814 mActivePointerId = ev.getPointerId(index); 815 break; 816 } 817 case MotionEvent.ACTION_POINTER_UP: 818 onSecondaryPointerUp(ev); 819 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId)); 820 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId)); 821 break; 822 } 823 return true; 824 } 825 826 private void onOverScrollFling(boolean open, int initialVelocity) { 827 if (mOverscrollTopChangedListener != null) { 828 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open); 829 } 830 mDontReportNextOverScroll = true; 831 setOverScrollAmount(0.0f, true, false); 832 } 833 834 /** 835 * Perform a scroll upwards and adapt the overscroll amounts accordingly 836 * 837 * @param deltaY The amount to scroll upwards, has to be positive. 838 * @return The amount of scrolling to be performed by the scroller, 839 * not handled by the overScroll amount. 840 */ 841 private float overScrollUp(int deltaY, int range) { 842 deltaY = Math.max(deltaY, 0); 843 float currentTopAmount = getCurrentOverScrollAmount(true); 844 float newTopAmount = currentTopAmount - deltaY; 845 if (currentTopAmount > 0) { 846 setOverScrollAmount(newTopAmount, true /* onTop */, 847 false /* animate */); 848 } 849 // Top overScroll might not grab all scrolling motion, 850 // we have to scroll as well. 851 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f; 852 float newScrollY = mOwnScrollY + scrollAmount; 853 if (newScrollY > range) { 854 if (!mExpandedInThisMotion) { 855 float currentBottomPixels = getCurrentOverScrolledPixels(false); 856 // We overScroll on the top 857 setOverScrolledPixels(currentBottomPixels + newScrollY - range, 858 false /* onTop */, 859 false /* animate */); 860 } 861 mOwnScrollY = range; 862 scrollAmount = 0.0f; 863 } 864 return scrollAmount; 865 } 866 867 /** 868 * Perform a scroll downward and adapt the overscroll amounts accordingly 869 * 870 * @param deltaY The amount to scroll downwards, has to be negative. 871 * @return The amount of scrolling to be performed by the scroller, 872 * not handled by the overScroll amount. 873 */ 874 private float overScrollDown(int deltaY) { 875 deltaY = Math.min(deltaY, 0); 876 float currentBottomAmount = getCurrentOverScrollAmount(false); 877 float newBottomAmount = currentBottomAmount + deltaY; 878 if (currentBottomAmount > 0) { 879 setOverScrollAmount(newBottomAmount, false /* onTop */, 880 false /* animate */); 881 } 882 // Bottom overScroll might not grab all scrolling motion, 883 // we have to scroll as well. 884 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f; 885 float newScrollY = mOwnScrollY + scrollAmount; 886 if (newScrollY < 0) { 887 float currentTopPixels = getCurrentOverScrolledPixels(true); 888 // We overScroll on the top 889 setOverScrolledPixels(currentTopPixels - newScrollY, 890 true /* onTop */, 891 false /* animate */); 892 mOwnScrollY = 0; 893 scrollAmount = 0.0f; 894 } 895 return scrollAmount; 896 } 897 898 private void onSecondaryPointerUp(MotionEvent ev) { 899 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> 900 MotionEvent.ACTION_POINTER_INDEX_SHIFT; 901 final int pointerId = ev.getPointerId(pointerIndex); 902 if (pointerId == mActivePointerId) { 903 // This was our active pointer going up. Choose a new 904 // active pointer and adjust accordingly. 905 // TODO: Make this decision more intelligent. 906 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 907 mLastMotionY = (int) ev.getY(newPointerIndex); 908 mActivePointerId = ev.getPointerId(newPointerIndex); 909 if (mVelocityTracker != null) { 910 mVelocityTracker.clear(); 911 } 912 } 913 } 914 915 private void initVelocityTrackerIfNotExists() { 916 if (mVelocityTracker == null) { 917 mVelocityTracker = VelocityTracker.obtain(); 918 } 919 } 920 921 private void recycleVelocityTracker() { 922 if (mVelocityTracker != null) { 923 mVelocityTracker.recycle(); 924 mVelocityTracker = null; 925 } 926 } 927 928 private void initOrResetVelocityTracker() { 929 if (mVelocityTracker == null) { 930 mVelocityTracker = VelocityTracker.obtain(); 931 } else { 932 mVelocityTracker.clear(); 933 } 934 } 935 936 @Override 937 public void computeScroll() { 938 if (mScroller.computeScrollOffset()) { 939 // This is called at drawing time by ViewGroup. 940 int oldX = mScrollX; 941 int oldY = mOwnScrollY; 942 int x = mScroller.getCurrX(); 943 int y = mScroller.getCurrY(); 944 945 if (oldX != x || oldY != y) { 946 final int range = getScrollRange(); 947 if (y < 0 && oldY >= 0 || y > range && oldY <= range) { 948 float currVelocity = mScroller.getCurrVelocity(); 949 if (currVelocity >= mMinimumVelocity) { 950 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance; 951 } 952 } 953 954 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range, 955 0, (int) (mMaxOverScroll), false); 956 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY); 957 } 958 959 // Keep on drawing until the animation has finished. 960 postInvalidateOnAnimation(); 961 } 962 } 963 964 @Override 965 protected boolean overScrollBy(int deltaX, int deltaY, 966 int scrollX, int scrollY, 967 int scrollRangeX, int scrollRangeY, 968 int maxOverScrollX, int maxOverScrollY, 969 boolean isTouchEvent) { 970 971 int newScrollY = scrollY + deltaY; 972 973 final int top = -maxOverScrollY; 974 final int bottom = maxOverScrollY + scrollRangeY; 975 976 boolean clampedY = false; 977 if (newScrollY > bottom) { 978 newScrollY = bottom; 979 clampedY = true; 980 } else if (newScrollY < top) { 981 newScrollY = top; 982 clampedY = true; 983 } 984 985 onOverScrolled(0, newScrollY, false, clampedY); 986 987 return clampedY; 988 } 989 990 /** 991 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded 992 * overscroll effect based on numPixels. By default this will also cancel animations on the 993 * same overScroll edge. 994 * 995 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to 996 * the rubber-banding logic. 997 * @param onTop Should the effect be applied on top of the scroller. 998 * @param animate Should an animation be performed. 999 */ 1000 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) { 1001 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true); 1002 } 1003 1004 /** 1005 * Set the effective overScroll amount which will be directly reflected in the layout. 1006 * By default this will also cancel animations on the same overScroll edge. 1007 * 1008 * @param amount The amount to overScroll by. 1009 * @param onTop Should the effect be applied on top of the scroller. 1010 * @param animate Should an animation be performed. 1011 */ 1012 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) { 1013 setOverScrollAmount(amount, onTop, animate, true); 1014 } 1015 1016 /** 1017 * Set the effective overScroll amount which will be directly reflected in the layout. 1018 * 1019 * @param amount The amount to overScroll by. 1020 * @param onTop Should the effect be applied on top of the scroller. 1021 * @param animate Should an animation be performed. 1022 * @param cancelAnimators Should running animations be cancelled. 1023 */ 1024 public void setOverScrollAmount(float amount, boolean onTop, boolean animate, 1025 boolean cancelAnimators) { 1026 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop)); 1027 } 1028 1029 /** 1030 * Set the effective overScroll amount which will be directly reflected in the layout. 1031 * 1032 * @param amount The amount to overScroll by. 1033 * @param onTop Should the effect be applied on top of the scroller. 1034 * @param animate Should an animation be performed. 1035 * @param cancelAnimators Should running animations be cancelled. 1036 * @param isRubberbanded The value which will be passed to 1037 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged} 1038 */ 1039 public void setOverScrollAmount(float amount, boolean onTop, boolean animate, 1040 boolean cancelAnimators, boolean isRubberbanded) { 1041 if (cancelAnimators) { 1042 mStateAnimator.cancelOverScrollAnimators(onTop); 1043 } 1044 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded); 1045 } 1046 1047 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate, 1048 boolean isRubberbanded) { 1049 amount = Math.max(0, amount); 1050 if (animate) { 1051 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded); 1052 } else { 1053 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop); 1054 mAmbientState.setOverScrollAmount(amount, onTop); 1055 if (onTop) { 1056 notifyOverscrollTopListener(amount, isRubberbanded); 1057 } 1058 requestChildrenUpdate(); 1059 } 1060 } 1061 1062 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) { 1063 mExpandHelper.onlyObserveMovements(amount > 1.0f); 1064 if (mDontReportNextOverScroll) { 1065 mDontReportNextOverScroll = false; 1066 return; 1067 } 1068 if (mOverscrollTopChangedListener != null) { 1069 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded); 1070 } 1071 } 1072 1073 public void setOverscrollTopChangedListener( 1074 OnOverscrollTopChangedListener overscrollTopChangedListener) { 1075 mOverscrollTopChangedListener = overscrollTopChangedListener; 1076 } 1077 1078 public float getCurrentOverScrollAmount(boolean top) { 1079 return mAmbientState.getOverScrollAmount(top); 1080 } 1081 1082 public float getCurrentOverScrolledPixels(boolean top) { 1083 return top? mOverScrolledTopPixels : mOverScrolledBottomPixels; 1084 } 1085 1086 private void setOverScrolledPixels(float amount, boolean onTop) { 1087 if (onTop) { 1088 mOverScrolledTopPixels = amount; 1089 } else { 1090 mOverScrolledBottomPixels = amount; 1091 } 1092 } 1093 1094 private void customScrollTo(int y) { 1095 mOwnScrollY = y; 1096 updateChildren(); 1097 } 1098 1099 @Override 1100 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { 1101 // Treat animating scrolls differently; see #computeScroll() for why. 1102 if (!mScroller.isFinished()) { 1103 final int oldX = mScrollX; 1104 final int oldY = mOwnScrollY; 1105 mScrollX = scrollX; 1106 mOwnScrollY = scrollY; 1107 if (clampedY) { 1108 springBack(); 1109 } else { 1110 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY); 1111 invalidateParentIfNeeded(); 1112 updateChildren(); 1113 float overScrollTop = getCurrentOverScrollAmount(true); 1114 if (mOwnScrollY < 0) { 1115 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true)); 1116 } else { 1117 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true)); 1118 } 1119 } 1120 } else { 1121 customScrollTo(scrollY); 1122 scrollTo(scrollX, mScrollY); 1123 } 1124 } 1125 1126 private void springBack() { 1127 int scrollRange = getScrollRange(); 1128 boolean overScrolledTop = mOwnScrollY <= 0; 1129 boolean overScrolledBottom = mOwnScrollY >= scrollRange; 1130 if (overScrolledTop || overScrolledBottom) { 1131 boolean onTop; 1132 float newAmount; 1133 if (overScrolledTop) { 1134 onTop = true; 1135 newAmount = -mOwnScrollY; 1136 mOwnScrollY = 0; 1137 mDontReportNextOverScroll = true; 1138 } else { 1139 onTop = false; 1140 newAmount = mOwnScrollY - scrollRange; 1141 mOwnScrollY = scrollRange; 1142 } 1143 setOverScrollAmount(newAmount, onTop, false); 1144 setOverScrollAmount(0.0f, onTop, true); 1145 mScroller.forceFinished(true); 1146 } 1147 } 1148 1149 private int getScrollRange() { 1150 int scrollRange = 0; 1151 ExpandableView firstChild = (ExpandableView) getFirstChildNotGone(); 1152 if (firstChild != null) { 1153 int contentHeight = getContentHeight(); 1154 int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild); 1155 scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize 1156 + mBottomStackSlowDownHeight); 1157 if (scrollRange > 0) { 1158 View lastChild = getLastChildNotGone(); 1159 // We want to at least be able collapse the first item and not ending in a weird 1160 // end state. 1161 scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight - mCollapsedSize); 1162 } 1163 } 1164 return scrollRange; 1165 } 1166 1167 /** 1168 * @return the first child which has visibility unequal to GONE 1169 */ 1170 private View getFirstChildNotGone() { 1171 int childCount = getChildCount(); 1172 for (int i = 0; i < childCount; i++) { 1173 View child = getChildAt(i); 1174 if (child.getVisibility() != View.GONE) { 1175 return child; 1176 } 1177 } 1178 return null; 1179 } 1180 1181 /** 1182 * @return The first child which has visibility unequal to GONE which is currently below the 1183 * given translationY or equal to it. 1184 */ 1185 private View getFirstChildBelowTranlsationY(float translationY) { 1186 int childCount = getChildCount(); 1187 for (int i = 0; i < childCount; i++) { 1188 View child = getChildAt(i); 1189 if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) { 1190 return child; 1191 } 1192 } 1193 return null; 1194 } 1195 1196 /** 1197 * @return the last child which has visibility unequal to GONE 1198 */ 1199 public View getLastChildNotGone() { 1200 int childCount = getChildCount(); 1201 for (int i = childCount - 1; i >= 0; i--) { 1202 View child = getChildAt(i); 1203 if (child.getVisibility() != View.GONE) { 1204 return child; 1205 } 1206 } 1207 return null; 1208 } 1209 1210 /** 1211 * @return the number of children which have visibility unequal to GONE 1212 */ 1213 public int getNotGoneChildCount() { 1214 int childCount = getChildCount(); 1215 int count = 0; 1216 for (int i = 0; i < childCount; i++) { 1217 View child = getChildAt(i); 1218 if (child.getVisibility() != View.GONE) { 1219 count++; 1220 } 1221 } 1222 if (mDismissView.willBeGone()) { 1223 count--; 1224 } 1225 return count; 1226 } 1227 1228 private int getMaxExpandHeight(View view) { 1229 if (view instanceof ExpandableNotificationRow) { 1230 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 1231 return row.getIntrinsicHeight(); 1232 } 1233 return view.getHeight(); 1234 } 1235 1236 public int getContentHeight() { 1237 return mContentHeight; 1238 } 1239 1240 private void updateContentHeight() { 1241 int height = 0; 1242 for (int i = 0; i < getChildCount(); i++) { 1243 View child = getChildAt(i); 1244 if (child.getVisibility() != View.GONE) { 1245 if (height != 0) { 1246 // add the padding before this element 1247 height += mPaddingBetweenElements; 1248 } 1249 if (child instanceof ExpandableNotificationRow) { 1250 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 1251 height += row.getIntrinsicHeight(); 1252 } else if (child instanceof ExpandableView) { 1253 ExpandableView expandableView = (ExpandableView) child; 1254 height += expandableView.getActualHeight(); 1255 } 1256 } 1257 } 1258 mContentHeight = height + mTopPadding; 1259 } 1260 1261 /** 1262 * Fling the scroll view 1263 * 1264 * @param velocityY The initial velocity in the Y direction. Positive 1265 * numbers mean that the finger/cursor is moving down the screen, 1266 * which means we want to scroll towards the top. 1267 */ 1268 private void fling(int velocityY) { 1269 if (getChildCount() > 0) { 1270 int scrollRange = getScrollRange(); 1271 1272 float topAmount = getCurrentOverScrollAmount(true); 1273 float bottomAmount = getCurrentOverScrollAmount(false); 1274 if (velocityY < 0 && topAmount > 0) { 1275 mOwnScrollY -= (int) topAmount; 1276 mDontReportNextOverScroll = true; 1277 setOverScrollAmount(0, true, false); 1278 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */) 1279 * mOverflingDistance + topAmount; 1280 } else if (velocityY > 0 && bottomAmount > 0) { 1281 mOwnScrollY += bottomAmount; 1282 setOverScrollAmount(0, false, false); 1283 mMaxOverScroll = Math.abs(velocityY) / 1000f 1284 * getRubberBandFactor(false /* onTop */) * mOverflingDistance 1285 + bottomAmount; 1286 } else { 1287 // it will be set once we reach the boundary 1288 mMaxOverScroll = 0.0f; 1289 } 1290 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, 1291 Math.max(0, scrollRange), 0, Integer.MAX_VALUE / 2); 1292 1293 postInvalidateOnAnimation(); 1294 } 1295 } 1296 1297 /** 1298 * @return Whether a fling performed on the top overscroll edge lead to the expanded 1299 * overScroll view (i.e QS). 1300 */ 1301 private boolean shouldOverScrollFling(int initialVelocity) { 1302 float topOverScroll = getCurrentOverScrollAmount(true); 1303 return mScrolledToTopOnFirstDown 1304 && !mExpandedInThisMotion 1305 && topOverScroll > mMinTopOverScrollToEscape 1306 && initialVelocity > 0; 1307 } 1308 1309 public void updateTopPadding(float qsHeight, int scrollY, boolean animate) { 1310 float start = qsHeight - scrollY + mNotificationTopPadding; 1311 float stackHeight = getHeight() - start; 1312 int minStackHeight = getMinStackHeight(); 1313 if (stackHeight <= minStackHeight) { 1314 float overflow = minStackHeight - stackHeight; 1315 stackHeight = minStackHeight; 1316 start = getHeight() - stackHeight; 1317 setTranslationY(overflow); 1318 mTopPaddingOverflow = overflow; 1319 } else { 1320 setTranslationY(0); 1321 mTopPaddingOverflow = 0; 1322 } 1323 setTopPadding(clampPadding((int) start), animate); 1324 } 1325 1326 public int getNotificationTopPadding() { 1327 return mNotificationTopPadding; 1328 } 1329 1330 public int getMinStackHeight() { 1331 return mCollapsedSize + mBottomStackPeekSize + mCollapseSecondCardPadding; 1332 } 1333 1334 public float getTopPaddingOverflow() { 1335 return mTopPaddingOverflow; 1336 } 1337 1338 public int getPeekHeight() { 1339 return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize 1340 + mCollapseSecondCardPadding; 1341 } 1342 1343 private int clampPadding(int desiredPadding) { 1344 return Math.max(desiredPadding, mIntrinsicPadding); 1345 } 1346 1347 private float getRubberBandFactor(boolean onTop) { 1348 if (!onTop) { 1349 return RUBBER_BAND_FACTOR_NORMAL; 1350 } 1351 if (mExpandedInThisMotion) { 1352 return RUBBER_BAND_FACTOR_AFTER_EXPAND; 1353 } else if (mIsExpansionChanging) { 1354 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND; 1355 } else if (mScrolledToTopOnFirstDown) { 1356 return 1.0f; 1357 } 1358 return RUBBER_BAND_FACTOR_NORMAL; 1359 } 1360 1361 /** 1362 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is 1363 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the 1364 * overscroll view (e.g. expand QS). 1365 */ 1366 private boolean isRubberbanded(boolean onTop) { 1367 return !onTop || mExpandedInThisMotion || mIsExpansionChanging 1368 || !mScrolledToTopOnFirstDown; 1369 } 1370 1371 private void endDrag() { 1372 setIsBeingDragged(false); 1373 1374 recycleVelocityTracker(); 1375 1376 if (getCurrentOverScrollAmount(true /* onTop */) > 0) { 1377 setOverScrollAmount(0, true /* onTop */, true /* animate */); 1378 } 1379 if (getCurrentOverScrollAmount(false /* onTop */) > 0) { 1380 setOverScrollAmount(0, false /* onTop */, true /* animate */); 1381 } 1382 } 1383 1384 private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) { 1385 ev.offsetLocation(sourceView.getX(), sourceView.getY()); 1386 ev.offsetLocation(-targetView.getX(), -targetView.getY()); 1387 } 1388 1389 @Override 1390 public boolean onInterceptTouchEvent(MotionEvent ev) { 1391 if (mInterceptDelegateEnabled) { 1392 transformTouchEvent(ev, this, mScrollView); 1393 if (mScrollView.onInterceptTouchEvent(ev)) { 1394 mDelegateToScrollView = true; 1395 removeLongPressCallback(); 1396 return true; 1397 } 1398 transformTouchEvent(ev, mScrollView, this); 1399 } 1400 initDownStates(ev); 1401 boolean expandWantsIt = false; 1402 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) { 1403 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev); 1404 } 1405 boolean scrollWantsIt = false; 1406 if (!mSwipingInProgress && !mExpandingNotification) { 1407 scrollWantsIt = onInterceptTouchEventScroll(ev); 1408 } 1409 boolean swipeWantsIt = false; 1410 if (!mIsBeingDragged 1411 && !mExpandingNotification 1412 && !mExpandedInThisMotion 1413 && !mOnlyScrollingInThisMotion) { 1414 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev); 1415 } 1416 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev); 1417 } 1418 1419 private void initDownStates(MotionEvent ev) { 1420 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 1421 mExpandedInThisMotion = false; 1422 mOnlyScrollingInThisMotion = !mScroller.isFinished(); 1423 mDisallowScrollingInThisMotion = false; 1424 } 1425 } 1426 1427 @Override 1428 protected void onViewRemoved(View child) { 1429 super.onViewRemoved(child); 1430 mStackScrollAlgorithm.notifyChildrenChanged(this); 1431 if (mChangePositionInProgress) { 1432 // This is only a position change, don't do anything special 1433 return; 1434 } 1435 ((ExpandableView) child).setOnHeightChangedListener(null); 1436 mCurrentStackScrollState.removeViewStateForView(child); 1437 updateScrollStateForRemovedChild(child); 1438 boolean animationGenerated = generateRemoveAnimation(child); 1439 if (animationGenerated && !mSwipedOutViews.contains(child)) { 1440 // Add this view to an overlay in order to ensure that it will still be temporary 1441 // drawn when removed 1442 getOverlay().add(child); 1443 } 1444 } 1445 1446 /** 1447 * Generate a remove animation for a child view. 1448 * 1449 * @param child The view to generate the remove animation for. 1450 * @return Whether an animation was generated. 1451 */ 1452 private boolean generateRemoveAnimation(View child) { 1453 if (mIsExpanded && mAnimationsEnabled) { 1454 if (!mChildrenToAddAnimated.contains(child)) { 1455 // Generate Animations 1456 mChildrenToRemoveAnimated.add(child); 1457 mNeedsAnimation = true; 1458 return true; 1459 } else { 1460 mChildrenToAddAnimated.remove(child); 1461 mFromMoreCardAdditions.remove(child); 1462 return false; 1463 } 1464 } 1465 return false; 1466 } 1467 1468 /** 1469 * Updates the scroll position when a child was removed 1470 * 1471 * @param removedChild the removed child 1472 */ 1473 private void updateScrollStateForRemovedChild(View removedChild) { 1474 int startingPosition = getPositionInLinearLayout(removedChild); 1475 int childHeight = getIntrinsicHeight(removedChild) + mPaddingBetweenElements; 1476 int endPosition = startingPosition + childHeight; 1477 if (endPosition <= mOwnScrollY) { 1478 // This child is fully scrolled of the top, so we have to deduct its height from the 1479 // scrollPosition 1480 mOwnScrollY -= childHeight; 1481 } else if (startingPosition < mOwnScrollY) { 1482 // This child is currently being scrolled into, set the scroll position to the start of 1483 // this child 1484 mOwnScrollY = startingPosition; 1485 } 1486 } 1487 1488 private int getIntrinsicHeight(View view) { 1489 if (view instanceof ExpandableView) { 1490 ExpandableView expandableView = (ExpandableView) view; 1491 return expandableView.getIntrinsicHeight(); 1492 } 1493 return view.getHeight(); 1494 } 1495 1496 private int getPositionInLinearLayout(View requestedChild) { 1497 int position = 0; 1498 for (int i = 0; i < getChildCount(); i++) { 1499 View child = getChildAt(i); 1500 if (child == requestedChild) { 1501 return position; 1502 } 1503 if (child.getVisibility() != View.GONE) { 1504 position += child.getHeight(); 1505 if (i < getChildCount()-1) { 1506 position += mPaddingBetweenElements; 1507 } 1508 } 1509 } 1510 return 0; 1511 } 1512 1513 @Override 1514 protected void onViewAdded(View child) { 1515 super.onViewAdded(child); 1516 mStackScrollAlgorithm.notifyChildrenChanged(this); 1517 ((ExpandableView) child).setOnHeightChangedListener(this); 1518 generateAddAnimation(child, false /* fromMoreCard */); 1519 } 1520 1521 public void setAnimationsEnabled(boolean animationsEnabled) { 1522 mAnimationsEnabled = animationsEnabled; 1523 } 1524 1525 public boolean isAddOrRemoveAnimationPending() { 1526 return mNeedsAnimation 1527 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty()); 1528 } 1529 /** 1530 * Generate an animation for an added child view. 1531 * 1532 * @param child The view to be added. 1533 * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen. 1534 */ 1535 public void generateAddAnimation(View child, boolean fromMoreCard) { 1536 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) { 1537 // Generate Animations 1538 mChildrenToAddAnimated.add(child); 1539 if (fromMoreCard) { 1540 mFromMoreCardAdditions.add(child); 1541 } 1542 mNeedsAnimation = true; 1543 } 1544 } 1545 1546 /** 1547 * Change the position of child to a new location 1548 * 1549 * @param child the view to change the position for 1550 * @param newIndex the new index 1551 */ 1552 public void changeViewPosition(View child, int newIndex) { 1553 int currentIndex = indexOfChild(child); 1554 if (child != null && child.getParent() == this && currentIndex != newIndex) { 1555 mChangePositionInProgress = true; 1556 removeView(child); 1557 addView(child, newIndex); 1558 mChangePositionInProgress = false; 1559 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) { 1560 mChildrenChangingPositions.add(child); 1561 mNeedsAnimation = true; 1562 } 1563 } 1564 } 1565 1566 private void startAnimationToState() { 1567 if (mNeedsAnimation) { 1568 generateChildHierarchyEvents(); 1569 mNeedsAnimation = false; 1570 } 1571 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) { 1572 mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState, 1573 mGoToFullShadeDelay); 1574 mAnimationEvents.clear(); 1575 } else { 1576 applyCurrentState(); 1577 } 1578 mGoToFullShadeDelay = 0; 1579 } 1580 1581 private void generateChildHierarchyEvents() { 1582 generateChildRemovalEvents(); 1583 generateChildAdditionEvents(); 1584 generatePositionChangeEvents(); 1585 generateSnapBackEvents(); 1586 generateDragEvents(); 1587 generateTopPaddingEvent(); 1588 generateActivateEvent(); 1589 generateDimmedEvent(); 1590 generateHideSensitiveEvent(); 1591 generateDarkEvent(); 1592 generateGoToFullShadeEvent(); 1593 mNeedsAnimation = false; 1594 } 1595 1596 private void generateSnapBackEvents() { 1597 for (View child : mSnappedBackChildren) { 1598 mAnimationEvents.add(new AnimationEvent(child, 1599 AnimationEvent.ANIMATION_TYPE_SNAP_BACK)); 1600 } 1601 mSnappedBackChildren.clear(); 1602 } 1603 1604 private void generateDragEvents() { 1605 for (View child : mDragAnimPendingChildren) { 1606 mAnimationEvents.add(new AnimationEvent(child, 1607 AnimationEvent.ANIMATION_TYPE_START_DRAG)); 1608 } 1609 mDragAnimPendingChildren.clear(); 1610 } 1611 1612 private void generateChildRemovalEvents() { 1613 for (View child : mChildrenToRemoveAnimated) { 1614 boolean childWasSwipedOut = mSwipedOutViews.contains(child); 1615 int animationType = childWasSwipedOut 1616 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT 1617 : AnimationEvent.ANIMATION_TYPE_REMOVE; 1618 AnimationEvent event = new AnimationEvent(child, animationType); 1619 1620 // we need to know the view after this one 1621 event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY()); 1622 mAnimationEvents.add(event); 1623 } 1624 mSwipedOutViews.clear(); 1625 mChildrenToRemoveAnimated.clear(); 1626 } 1627 1628 private void generatePositionChangeEvents() { 1629 for (View child : mChildrenChangingPositions) { 1630 mAnimationEvents.add(new AnimationEvent(child, 1631 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)); 1632 } 1633 mChildrenChangingPositions.clear(); 1634 } 1635 1636 private void generateChildAdditionEvents() { 1637 for (View child : mChildrenToAddAnimated) { 1638 if (mFromMoreCardAdditions.contains(child)) { 1639 mAnimationEvents.add(new AnimationEvent(child, 1640 AnimationEvent.ANIMATION_TYPE_ADD, 1641 StackStateAnimator.ANIMATION_DURATION_STANDARD)); 1642 } else { 1643 mAnimationEvents.add(new AnimationEvent(child, 1644 AnimationEvent.ANIMATION_TYPE_ADD)); 1645 } 1646 } 1647 mChildrenToAddAnimated.clear(); 1648 mFromMoreCardAdditions.clear(); 1649 } 1650 1651 private void generateTopPaddingEvent() { 1652 if (mTopPaddingNeedsAnimation) { 1653 mAnimationEvents.add( 1654 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED)); 1655 } 1656 mTopPaddingNeedsAnimation = false; 1657 } 1658 1659 private void generateActivateEvent() { 1660 if (mActivateNeedsAnimation) { 1661 mAnimationEvents.add( 1662 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD)); 1663 } 1664 mActivateNeedsAnimation = false; 1665 } 1666 1667 private void generateDimmedEvent() { 1668 if (mDimmedNeedsAnimation) { 1669 mAnimationEvents.add( 1670 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED)); 1671 } 1672 mDimmedNeedsAnimation = false; 1673 } 1674 1675 private void generateHideSensitiveEvent() { 1676 if (mHideSensitiveNeedsAnimation) { 1677 mAnimationEvents.add( 1678 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE)); 1679 } 1680 mHideSensitiveNeedsAnimation = false; 1681 } 1682 1683 private void generateDarkEvent() { 1684 if (mDarkNeedsAnimation) { 1685 mAnimationEvents.add( 1686 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK)); 1687 } 1688 mDarkNeedsAnimation = false; 1689 } 1690 1691 private void generateGoToFullShadeEvent() { 1692 if (mGoToFullShadeNeedsAnimation) { 1693 mAnimationEvents.add( 1694 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE)); 1695 } 1696 mGoToFullShadeNeedsAnimation = false; 1697 } 1698 1699 private boolean onInterceptTouchEventScroll(MotionEvent ev) { 1700 if (!isScrollingEnabled()) { 1701 return false; 1702 } 1703 /* 1704 * This method JUST determines whether we want to intercept the motion. 1705 * If we return true, onMotionEvent will be called and we do the actual 1706 * scrolling there. 1707 */ 1708 1709 /* 1710 * Shortcut the most recurring case: the user is in the dragging 1711 * state and he is moving his finger. We want to intercept this 1712 * motion. 1713 */ 1714 final int action = ev.getAction(); 1715 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { 1716 return true; 1717 } 1718 1719 switch (action & MotionEvent.ACTION_MASK) { 1720 case MotionEvent.ACTION_MOVE: { 1721 /* 1722 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 1723 * whether the user has moved far enough from his original down touch. 1724 */ 1725 1726 /* 1727 * Locally do absolute value. mLastMotionY is set to the y value 1728 * of the down event. 1729 */ 1730 final int activePointerId = mActivePointerId; 1731 if (activePointerId == INVALID_POINTER) { 1732 // If we don't have a valid id, the touch down wasn't on content. 1733 break; 1734 } 1735 1736 final int pointerIndex = ev.findPointerIndex(activePointerId); 1737 if (pointerIndex == -1) { 1738 Log.e(TAG, "Invalid pointerId=" + activePointerId 1739 + " in onInterceptTouchEvent"); 1740 break; 1741 } 1742 1743 final int y = (int) ev.getY(pointerIndex); 1744 final int x = (int) ev.getX(pointerIndex); 1745 final int yDiff = Math.abs(y - mLastMotionY); 1746 final int xDiff = Math.abs(x - mDownX); 1747 if (yDiff > mTouchSlop && yDiff > xDiff) { 1748 setIsBeingDragged(true); 1749 mLastMotionY = y; 1750 mDownX = x; 1751 initVelocityTrackerIfNotExists(); 1752 mVelocityTracker.addMovement(ev); 1753 } 1754 break; 1755 } 1756 1757 case MotionEvent.ACTION_DOWN: { 1758 final int y = (int) ev.getY(); 1759 if (getChildAtPosition(ev.getX(), y) == null) { 1760 setIsBeingDragged(false); 1761 recycleVelocityTracker(); 1762 break; 1763 } 1764 1765 /* 1766 * Remember location of down touch. 1767 * ACTION_DOWN always refers to pointer index 0. 1768 */ 1769 mLastMotionY = y; 1770 mDownX = (int) ev.getX(); 1771 mActivePointerId = ev.getPointerId(0); 1772 mScrolledToTopOnFirstDown = isScrolledToTop(); 1773 1774 initOrResetVelocityTracker(); 1775 mVelocityTracker.addMovement(ev); 1776 /* 1777 * If being flinged and user touches the screen, initiate drag; 1778 * otherwise don't. mScroller.isFinished should be false when 1779 * being flinged. 1780 */ 1781 boolean isBeingDragged = !mScroller.isFinished(); 1782 setIsBeingDragged(isBeingDragged); 1783 break; 1784 } 1785 1786 case MotionEvent.ACTION_CANCEL: 1787 case MotionEvent.ACTION_UP: 1788 /* Release the drag */ 1789 setIsBeingDragged(false); 1790 mActivePointerId = INVALID_POINTER; 1791 recycleVelocityTracker(); 1792 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { 1793 postInvalidateOnAnimation(); 1794 } 1795 break; 1796 case MotionEvent.ACTION_POINTER_UP: 1797 onSecondaryPointerUp(ev); 1798 break; 1799 } 1800 1801 /* 1802 * The only time we want to intercept motion events is if we are in the 1803 * drag mode. 1804 */ 1805 return mIsBeingDragged; 1806 } 1807 1808 /** 1809 * @return Whether the specified motion event is actually happening over the content. 1810 */ 1811 private boolean isInContentBounds(MotionEvent event) { 1812 return event.getY() < getHeight() - getEmptyBottomMargin(); 1813 } 1814 1815 private void setIsBeingDragged(boolean isDragged) { 1816 mIsBeingDragged = isDragged; 1817 if (isDragged) { 1818 requestDisallowInterceptTouchEvent(true); 1819 removeLongPressCallback(); 1820 } 1821 } 1822 1823 @Override 1824 public void onWindowFocusChanged(boolean hasWindowFocus) { 1825 super.onWindowFocusChanged(hasWindowFocus); 1826 if (!hasWindowFocus) { 1827 removeLongPressCallback(); 1828 } 1829 } 1830 1831 public void removeLongPressCallback() { 1832 mSwipeHelper.removeLongPressCallback(); 1833 } 1834 1835 @Override 1836 public boolean isScrolledToTop() { 1837 return mOwnScrollY == 0; 1838 } 1839 1840 @Override 1841 public boolean isScrolledToBottom() { 1842 return mOwnScrollY >= getScrollRange(); 1843 } 1844 1845 @Override 1846 public View getHostView() { 1847 return this; 1848 } 1849 1850 public int getEmptyBottomMargin() { 1851 int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize; 1852 if (needsHeightAdaption()) { 1853 emptyMargin -= mBottomStackSlowDownHeight; 1854 } else { 1855 emptyMargin -= mCollapseSecondCardPadding; 1856 } 1857 return Math.max(emptyMargin, 0); 1858 } 1859 1860 public void onExpansionStarted() { 1861 mIsExpansionChanging = true; 1862 mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState); 1863 } 1864 1865 public void onExpansionStopped() { 1866 mIsExpansionChanging = false; 1867 mStackScrollAlgorithm.onExpansionStopped(); 1868 if (!mIsExpanded) { 1869 mOwnScrollY = 0; 1870 } 1871 } 1872 1873 private void setIsExpanded(boolean isExpanded) { 1874 mIsExpanded = isExpanded; 1875 mStackScrollAlgorithm.setIsExpanded(isExpanded); 1876 } 1877 1878 @Override 1879 public void onHeightChanged(ExpandableView view) { 1880 updateContentHeight(); 1881 updateScrollPositionOnExpandInBottom(view); 1882 clampScrollPosition(); 1883 notifyHeightChangeListener(view); 1884 requestChildrenUpdate(); 1885 } 1886 1887 private void updateScrollPositionOnExpandInBottom(ExpandableView view) { 1888 if (view instanceof ExpandableNotificationRow) { 1889 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 1890 if (row.isUserLocked()) { 1891 // We are actually expanding this view 1892 float endPosition = row.getTranslationY() + row.getActualHeight(); 1893 int stackEnd = mMaxLayoutHeight - mBottomStackPeekSize - 1894 mBottomStackSlowDownHeight; 1895 if (endPosition > stackEnd) { 1896 mOwnScrollY += endPosition - stackEnd; 1897 mDisallowScrollingInThisMotion = true; 1898 } 1899 } 1900 } 1901 } 1902 1903 public void setOnHeightChangedListener( 1904 ExpandableView.OnHeightChangedListener mOnHeightChangedListener) { 1905 this.mOnHeightChangedListener = mOnHeightChangedListener; 1906 } 1907 1908 public void onChildAnimationFinished() { 1909 requestChildrenUpdate(); 1910 } 1911 1912 /** 1913 * See {@link AmbientState#setDimmed}. 1914 */ 1915 public void setDimmed(boolean dimmed, boolean animate) { 1916 mStackScrollAlgorithm.setDimmed(dimmed); 1917 mAmbientState.setDimmed(dimmed); 1918 updatePadding(dimmed); 1919 if (animate && mAnimationsEnabled) { 1920 mDimmedNeedsAnimation = true; 1921 mNeedsAnimation = true; 1922 } 1923 requestChildrenUpdate(); 1924 } 1925 1926 public void setHideSensitive(boolean hideSensitive, boolean animate) { 1927 if (hideSensitive != mAmbientState.isHideSensitive()) { 1928 int childCount = getChildCount(); 1929 for (int i = 0; i < childCount; i++) { 1930 ExpandableView v = (ExpandableView) getChildAt(i); 1931 v.setHideSensitiveForIntrinsicHeight(hideSensitive); 1932 } 1933 mAmbientState.setHideSensitive(hideSensitive); 1934 if (animate && mAnimationsEnabled) { 1935 mHideSensitiveNeedsAnimation = true; 1936 mNeedsAnimation = true; 1937 } 1938 requestChildrenUpdate(); 1939 } 1940 } 1941 1942 /** 1943 * See {@link AmbientState#setActivatedChild}. 1944 */ 1945 public void setActivatedChild(ActivatableNotificationView activatedChild) { 1946 mAmbientState.setActivatedChild(activatedChild); 1947 if (mAnimationsEnabled) { 1948 mActivateNeedsAnimation = true; 1949 mNeedsAnimation = true; 1950 } 1951 requestChildrenUpdate(); 1952 } 1953 1954 public ActivatableNotificationView getActivatedChild() { 1955 return mAmbientState.getActivatedChild(); 1956 } 1957 1958 private void applyCurrentState() { 1959 mCurrentStackScrollState.apply(); 1960 if (mListener != null) { 1961 mListener.onChildLocationsChanged(this); 1962 } 1963 } 1964 1965 public void setSpeedBumpView(SpeedBumpView speedBumpView) { 1966 mSpeedBumpView = speedBumpView; 1967 addView(speedBumpView); 1968 } 1969 1970 private void updateSpeedBump(boolean visible) { 1971 boolean notGoneBefore = mSpeedBumpView.getVisibility() != GONE; 1972 if (visible != notGoneBefore) { 1973 int newVisibility = visible ? VISIBLE : GONE; 1974 mSpeedBumpView.setVisibility(newVisibility); 1975 if (visible) { 1976 // Make invisible to ensure that the appear animation is played. 1977 mSpeedBumpView.setInvisible(); 1978 } else { 1979 // TODO: This doesn't really work, because the view is already set to GONE above. 1980 generateRemoveAnimation(mSpeedBumpView); 1981 } 1982 } 1983 } 1984 1985 public void goToFullShade(long delay) { 1986 updateSpeedBump(true /* visibility */); 1987 mDismissView.setInvisible(); 1988 mGoToFullShadeNeedsAnimation = true; 1989 mGoToFullShadeDelay = delay; 1990 mNeedsAnimation = true; 1991 requestChildrenUpdate(); 1992 } 1993 1994 public void cancelExpandHelper() { 1995 mExpandHelper.cancel(); 1996 } 1997 1998 public void setIntrinsicPadding(int intrinsicPadding) { 1999 mIntrinsicPadding = intrinsicPadding; 2000 } 2001 2002 public int getIntrinsicPadding() { 2003 return mIntrinsicPadding; 2004 } 2005 2006 /** 2007 * @return the y position of the first notification 2008 */ 2009 public float getNotificationsTopY() { 2010 return mTopPadding + getTranslationY(); 2011 } 2012 2013 @Override 2014 public boolean shouldDelayChildPressedState() { 2015 return true; 2016 } 2017 2018 public void setScrimAlpha(float progress) { 2019 if (progress != mAmbientState.getScrimAmount()) { 2020 mAmbientState.setScrimAmount(progress); 2021 requestChildrenUpdate(); 2022 } 2023 } 2024 2025 /** 2026 * See {@link AmbientState#setDark}. 2027 */ 2028 public void setDark(boolean dark, boolean animate) { 2029 mAmbientState.setDark(dark); 2030 if (animate && mAnimationsEnabled) { 2031 mDarkNeedsAnimation = true; 2032 mNeedsAnimation = true; 2033 } 2034 requestChildrenUpdate(); 2035 } 2036 2037 public void setDismissView(DismissView dismissView) { 2038 mDismissView = dismissView; 2039 addView(mDismissView); 2040 } 2041 2042 public void updateDismissView(boolean visible) { 2043 int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility(); 2044 int newVisibility = visible ? VISIBLE : GONE; 2045 if (oldVisibility != newVisibility) { 2046 if (oldVisibility == GONE) { 2047 if (mDismissView.willBeGone()) { 2048 mDismissView.cancelAnimation(); 2049 } else { 2050 mDismissView.setInvisible(); 2051 mDismissView.setVisibility(newVisibility); 2052 } 2053 mDismissView.setWillBeGone(false); 2054 updateContentHeight(); 2055 } else { 2056 mDismissView.setWillBeGone(true); 2057 mDismissView.performVisibilityAnimation(false, new Runnable() { 2058 @Override 2059 public void run() { 2060 mDismissView.setVisibility(GONE); 2061 mDismissView.setWillBeGone(false); 2062 updateContentHeight(); 2063 } 2064 }); 2065 } 2066 } 2067 } 2068 2069 public void setDismissAllInProgress(boolean dismissAllInProgress) { 2070 mDismissAllInProgress = dismissAllInProgress; 2071 } 2072 2073 public boolean isDismissViewNotGone() { 2074 return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone(); 2075 } 2076 2077 public boolean isDismissViewVisible() { 2078 return mDismissView.isVisible(); 2079 } 2080 2081 public int getDismissViewHeight() { 2082 return mDismissView.getHeight() + mPaddingBetweenElementsNormal; 2083 } 2084 2085 /** 2086 * A listener that is notified when some child locations might have changed. 2087 */ 2088 public interface OnChildLocationsChangedListener { 2089 public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout); 2090 } 2091 2092 /** 2093 * A listener that gets notified when the overscroll at the top has changed. 2094 */ 2095 public interface OnOverscrollTopChangedListener { 2096 2097 /** 2098 * Notifies a listener that the overscroll has changed. 2099 * 2100 * @param amount the amount of overscroll, in pixels 2101 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an 2102 * unrubberbanded motion to directly expand overscroll view (e.g expand 2103 * QS) 2104 */ 2105 public void onOverscrollTopChanged(float amount, boolean isRubberbanded); 2106 2107 /** 2108 * Notify a listener that the scroller wants to escape from the scrolling motion and 2109 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS) 2110 * 2111 * @param velocity The velocity that the Scroller had when over flinging 2112 * @param open Should the fling open or close the overscroll view. 2113 */ 2114 public void flingTopOverscroll(float velocity, boolean open); 2115 } 2116 2117 static class AnimationEvent { 2118 2119 static AnimationFilter[] FILTERS = new AnimationFilter[] { 2120 2121 // ANIMATION_TYPE_ADD 2122 new AnimationFilter() 2123 .animateAlpha() 2124 .animateHeight() 2125 .animateTopInset() 2126 .animateY() 2127 .animateZ() 2128 .hasDelays(), 2129 2130 // ANIMATION_TYPE_REMOVE 2131 new AnimationFilter() 2132 .animateAlpha() 2133 .animateHeight() 2134 .animateTopInset() 2135 .animateY() 2136 .animateZ() 2137 .hasDelays(), 2138 2139 // ANIMATION_TYPE_REMOVE_SWIPED_OUT 2140 new AnimationFilter() 2141 .animateAlpha() 2142 .animateHeight() 2143 .animateTopInset() 2144 .animateY() 2145 .animateZ() 2146 .hasDelays(), 2147 2148 // ANIMATION_TYPE_TOP_PADDING_CHANGED 2149 new AnimationFilter() 2150 .animateAlpha() 2151 .animateHeight() 2152 .animateTopInset() 2153 .animateY() 2154 .animateDimmed() 2155 .animateScale() 2156 .animateZ(), 2157 2158 // ANIMATION_TYPE_START_DRAG 2159 new AnimationFilter() 2160 .animateAlpha(), 2161 2162 // ANIMATION_TYPE_SNAP_BACK 2163 new AnimationFilter() 2164 .animateAlpha() 2165 .animateHeight(), 2166 2167 // ANIMATION_TYPE_ACTIVATED_CHILD 2168 new AnimationFilter() 2169 .animateScale() 2170 .animateAlpha(), 2171 2172 // ANIMATION_TYPE_DIMMED 2173 new AnimationFilter() 2174 .animateY() 2175 .animateScale() 2176 .animateDimmed(), 2177 2178 // ANIMATION_TYPE_CHANGE_POSITION 2179 new AnimationFilter() 2180 .animateAlpha() 2181 .animateHeight() 2182 .animateTopInset() 2183 .animateY() 2184 .animateZ(), 2185 2186 // ANIMATION_TYPE_DARK 2187 new AnimationFilter() 2188 .animateDark(), 2189 2190 // ANIMATION_TYPE_GO_TO_FULL_SHADE 2191 new AnimationFilter() 2192 .animateAlpha() 2193 .animateHeight() 2194 .animateTopInset() 2195 .animateY() 2196 .animateDimmed() 2197 .animateScale() 2198 .animateZ() 2199 .hasDelays(), 2200 2201 // ANIMATION_TYPE_HIDE_SENSITIVE 2202 new AnimationFilter() 2203 .animateHideSensitive(), 2204 }; 2205 2206 static int[] LENGTHS = new int[] { 2207 2208 // ANIMATION_TYPE_ADD 2209 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR, 2210 2211 // ANIMATION_TYPE_REMOVE 2212 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR, 2213 2214 // ANIMATION_TYPE_REMOVE_SWIPED_OUT 2215 StackStateAnimator.ANIMATION_DURATION_STANDARD, 2216 2217 // ANIMATION_TYPE_TOP_PADDING_CHANGED 2218 StackStateAnimator.ANIMATION_DURATION_STANDARD, 2219 2220 // ANIMATION_TYPE_START_DRAG 2221 StackStateAnimator.ANIMATION_DURATION_STANDARD, 2222 2223 // ANIMATION_TYPE_SNAP_BACK 2224 StackStateAnimator.ANIMATION_DURATION_STANDARD, 2225 2226 // ANIMATION_TYPE_ACTIVATED_CHILD 2227 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED, 2228 2229 // ANIMATION_TYPE_DIMMED 2230 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED, 2231 2232 // ANIMATION_TYPE_CHANGE_POSITION 2233 StackStateAnimator.ANIMATION_DURATION_STANDARD, 2234 2235 // ANIMATION_TYPE_DARK 2236 StackStateAnimator.ANIMATION_DURATION_STANDARD, 2237 2238 // ANIMATION_TYPE_GO_TO_FULL_SHADE 2239 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, 2240 2241 // ANIMATION_TYPE_HIDE_SENSITIVE 2242 StackStateAnimator.ANIMATION_DURATION_STANDARD, 2243 }; 2244 2245 static final int ANIMATION_TYPE_ADD = 0; 2246 static final int ANIMATION_TYPE_REMOVE = 1; 2247 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2; 2248 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3; 2249 static final int ANIMATION_TYPE_START_DRAG = 4; 2250 static final int ANIMATION_TYPE_SNAP_BACK = 5; 2251 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6; 2252 static final int ANIMATION_TYPE_DIMMED = 7; 2253 static final int ANIMATION_TYPE_CHANGE_POSITION = 8; 2254 static final int ANIMATION_TYPE_DARK = 9; 2255 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10; 2256 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11; 2257 2258 final long eventStartTime; 2259 final View changingView; 2260 final int animationType; 2261 final AnimationFilter filter; 2262 final long length; 2263 View viewAfterChangingView; 2264 2265 AnimationEvent(View view, int type) { 2266 this(view, type, LENGTHS[type]); 2267 } 2268 2269 AnimationEvent(View view, int type, long length) { 2270 eventStartTime = AnimationUtils.currentAnimationTimeMillis(); 2271 changingView = view; 2272 animationType = type; 2273 filter = FILTERS[type]; 2274 this.length = length; 2275 } 2276 2277 /** 2278 * Combines the length of several animation events into a single value. 2279 * 2280 * @param events The events of the lengths to combine. 2281 * @return The combined length. Depending on the event types, this might be the maximum of 2282 * all events or the length of a specific event. 2283 */ 2284 static long combineLength(ArrayList<AnimationEvent> events) { 2285 long length = 0; 2286 int size = events.size(); 2287 for (int i = 0; i < size; i++) { 2288 AnimationEvent event = events.get(i); 2289 length = Math.max(length, event.length); 2290 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) { 2291 return event.length; 2292 } 2293 } 2294 return length; 2295 } 2296 } 2297 2298} 2299