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