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 218 public NotificationStackScrollLayout(Context context) { 219 this(context, null); 220 } 221 222 public NotificationStackScrollLayout(Context context, AttributeSet attrs) { 223 this(context, attrs, 0); 224 } 225 226 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) { 227 this(context, attrs, defStyleAttr, 0); 228 } 229 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 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 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 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 310 private void notifyHeightChangeListener(ExpandableView view) { 311 if (mOnHeightChangedListener != null) { 312 mOnHeightChangedListener.onHeightChanged(view); 313 } 314 } 315 316 @Override 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 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 346 private void requestAnimationOnViewResize() { 347 if (mRequestViewResizeAnimationOnLayout && mIsExpanded && mAnimationsEnabled) { 348 mNeedViewResizeAnimation = true; 349 mNeedsAnimation = true; 350 } 351 mRequestViewResizeAnimationOnLayout = false; 352 } 353 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 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 */ 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 391 private void setMaxLayoutHeight(int maxLayoutHeight) { 392 mMaxLayoutHeight = maxLayoutHeight; 393 updateAlgorithmHeightAndPadding(); 394 } 395 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 */ 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 */ 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 423 private void requestChildrenUpdate() { 424 if (!mChildrenUpdateRequested) { 425 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater); 426 mChildrenUpdateRequested = true; 427 invalidate(); 428 } 429 } 430 431 private boolean isCurrentlyAnimating() { 432 return mStateAnimator.isRunning(); 433 } 434 435 private void clampScrollPosition() { 436 int scrollRange = getScrollRange(); 437 if (scrollRange < mOwnScrollY) { 438 mOwnScrollY = scrollRange; 439 } 440 } 441 442 public int getTopPadding() { 443 return mTopPadding; 444 } 445 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 */ 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 */ 503 private int getLayoutHeight() { 504 return Math.min(mMaxLayoutHeight, mCurrentStackHeight); 505 } 506 507 public int getItemHeight() { 508 return mCollapsedSize; 509 } 510 511 public int getBottomStackPeekSize() { 512 return mBottomStackPeekSize; 513 } 514 515 public int getCollapseSecondCardPadding() { 516 return mCollapseSecondCardPadding; 517 } 518 519 public void setLongPressListener(SwipeHelper.LongPressListener listener) { 520 mSwipeHelper.setLongPressListener(listener); 521 mLongPressListener = listener; 522 } 523 524 public void setScrollView(ViewGroup scrollView) { 525 mScrollView = scrollView; 526 } 527 528 public void setInterceptDelegateEnabled(boolean interceptDelegateEnabled) { 529 mInterceptDelegateEnabled = interceptDelegateEnabled; 530 } 531 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 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 567 public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { 568 return false; 569 } 570 571 @Override 572 public float getFalsingThresholdFactor() { 573 return mPhoneStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f; 574 } 575 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 586 public void onDragCancelled(View v) { 587 setSwipingInProgress(false); 588 } 589 590 public View getChildAtPosition(MotionEvent ev) { 591 return getChildAtPosition(ev.getX(), ev.getY()); 592 } 593 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 623 public ExpandableView getChildAtRawPosition(float touchX, float touchY) { 624 getLocationOnScreen(mTempInt2); 625 return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]); 626 } 627 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 654 public boolean canChildBeExpanded(View v) { 655 return v instanceof ExpandableNotificationRow 656 && ((ExpandableNotificationRow) v).isExpandable(); 657 } 658 659 public void setUserExpandedChild(View v, boolean userExpanded) { 660 if (v instanceof ExpandableNotificationRow) { 661 ((ExpandableNotificationRow) v).setUserExpanded(userExpanded); 662 } 663 } 664 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 674 public void expansionStateChanged(boolean isExpanding) { 675 mExpandingNotification = isExpanding; 676 if (!mExpandedInThisMotion) { 677 mMaxScrollAfterExpand = mOwnScrollY; 678 mExpandedInThisMotion = true; 679 } 680 } 681 682 public void setScrollingEnabled(boolean enable) { 683 mScrollingEnabled = enable; 684 } 685 686 public void setExpandingEnabled(boolean enable) { 687 mExpandHelper.setEnabled(enable); 688 } 689 690 private boolean isScrollingEnabled() { 691 return mScrollingEnabled; 692 } 693 694 public View getChildContentView(View v) { 695 return v; 696 } 697 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 704 public boolean isAntiFalsingNeeded() { 705 return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD; 706 } 707 708 private void setSwipingInProgress(boolean isSwiped) { 709 mSwipingInProgress = isSwiped; 710 if(isSwiped) { 711 requestDisallowInterceptTouchEvent(true); 712 } 713 } 714 715 @Override 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 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 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 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 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 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 */ 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 */ 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 996 private void initVelocityTrackerIfNotExists() { 997 if (mVelocityTracker == null) { 998 mVelocityTracker = VelocityTracker.obtain(); 999 } 1000 } 1001 1002 private void recycleVelocityTracker() { 1003 if (mVelocityTracker != null) { 1004 mVelocityTracker.recycle(); 1005 mVelocityTracker = null; 1006 } 1007 } 1008 1009 private void initOrResetVelocityTracker() { 1010 if (mVelocityTracker == null) { 1011 mVelocityTracker = VelocityTracker.obtain(); 1012 } else { 1013 mVelocityTracker.clear(); 1014 } 1015 } 1016 1017 @Override 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 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 */ 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 */ 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 */ 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 */ 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 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 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 1154 public void setOverscrollTopChangedListener( 1155 OnOverscrollTopChangedListener overscrollTopChangedListener) { 1156 mOverscrollTopChangedListener = overscrollTopChangedListener; 1157 } 1158 1159 public float getCurrentOverScrollAmount(boolean top) { 1160 return mAmbientState.getOverScrollAmount(top); 1161 } 1162 1163 public float getCurrentOverScrolledPixels(boolean top) { 1164 return top? mOverScrolledTopPixels : mOverScrolledBottomPixels; 1165 } 1166 1167 private void setOverScrolledPixels(float amount, boolean onTop) { 1168 if (onTop) { 1169 mOverScrolledTopPixels = amount; 1170 } else { 1171 mOverScrolledBottomPixels = amount; 1172 } 1173 } 1174 1175 private void customScrollTo(int y) { 1176 mOwnScrollY = y; 1177 updateChildren(); 1178 } 1179 1180 @Override 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 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 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 */ 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 */ 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 */ 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 */ 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 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 1320 public int getContentHeight() { 1321 return mContentHeight; 1322 } 1323 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 */ 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 */ 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 */ 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 1422 public int getNotificationTopPadding() { 1423 return mNotificationTopPadding; 1424 } 1425 1426 public int getMinStackHeight() { 1427 return mCollapsedSize + mBottomStackPeekSize + mCollapseSecondCardPadding; 1428 } 1429 1430 public float getTopPaddingOverflow() { 1431 return mTopPaddingOverflow; 1432 } 1433 1434 public int getPeekHeight() { 1435 return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize 1436 + mCollapseSecondCardPadding; 1437 } 1438 1439 private int clampPadding(int desiredPadding) { 1440 return Math.max(desiredPadding, mIntrinsicPadding); 1441 } 1442 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 */ 1462 private boolean isRubberbanded(boolean onTop) { 1463 return !onTop || mExpandedInThisMotion || mIsExpansionChanging 1464 || !mScrolledToTopOnFirstDown; 1465 } 1466 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 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 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 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 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 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 */ 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 */ 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 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 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 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 1649 public void setAnimationsEnabled(boolean animationsEnabled) { 1650 mAnimationsEnabled = animationsEnabled; 1651 updateNotificationAnimationStates(); 1652 } 1653 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 1663 private void updateAnimationState(View child) { 1664 updateAnimationState(mAnimationsEnabled && mIsExpanded, child); 1665 } 1666 1667 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 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 */ 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 */ 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 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 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 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 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 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 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 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 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 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 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 1827 private void generateAnimateEverythingEvent() { 1828 if (mEverythingNeedsAnimation) { 1829 mAnimationEvents.add( 1830 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING)); 1831 } 1832 mEverythingNeedsAnimation = false; 1833 } 1834 1835 private void generateDimmedEvent() { 1836 if (mDimmedNeedsAnimation) { 1837 mAnimationEvents.add( 1838 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED)); 1839 } 1840 mDimmedNeedsAnimation = false; 1841 } 1842 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 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 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 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 */ 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 */ 1987 public boolean isInContentBounds(float y) { 1988 return y < getHeight() - getEmptyBottomMargin(); 1989 } 1990 1991 private void setIsBeingDragged(boolean isDragged) { 1992 mIsBeingDragged = isDragged; 1993 if (isDragged) { 1994 requestDisallowInterceptTouchEvent(true); 1995 removeLongPressCallback(); 1996 } 1997 } 1998 1999 @Override 2000 public void onWindowFocusChanged(boolean hasWindowFocus) { 2001 super.onWindowFocusChanged(hasWindowFocus); 2002 if (!hasWindowFocus) { 2003 removeLongPressCallback(); 2004 } 2005 } 2006 2007 public void removeLongPressCallback() { 2008 mSwipeHelper.removeLongPressCallback(); 2009 } 2010 2011 @Override 2012 public boolean isScrolledToTop() { 2013 return mOwnScrollY == 0; 2014 } 2015 2016 @Override 2017 public boolean isScrolledToBottom() { 2018 return mOwnScrollY >= getScrollRange(); 2019 } 2020 2021 @Override 2022 public View getHostView() { 2023 return this; 2024 } 2025 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 2036 public void onExpansionStarted() { 2037 mIsExpansionChanging = true; 2038 mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState); 2039 } 2040 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 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 2062 public void onHeightChanged(ExpandableView view) { 2063 updateContentHeight(); 2064 updateScrollPositionOnExpandInBottom(view); 2065 clampScrollPosition(); 2066 notifyHeightChangeListener(view); 2067 requestChildrenUpdate(); 2068 } 2069 2070 @Override 2071 public void onReset(ExpandableView view) { 2072 if (mIsExpanded && mAnimationsEnabled) { 2073 mRequestViewResizeAnimationOnLayout = true; 2074 } 2075 mStackScrollAlgorithm.onReset(view); 2076 updateAnimationState(view); 2077 } 2078 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 2095 public void setOnHeightChangedListener( 2096 ExpandableView.OnHeightChangedListener mOnHeightChangedListener) { 2097 this.mOnHeightChangedListener = mOnHeightChangedListener; 2098 } 2099 2100 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) { 2101 mOnEmptySpaceClickListener = listener; 2102 } 2103 2104 public void onChildAnimationFinished() { 2105 requestChildrenUpdate(); 2106 } 2107 2108 /** 2109 * See {@link AmbientState#setDimmed}. 2110 */ 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 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 */ 2141 public void setActivatedChild(ActivatableNotificationView activatedChild) { 2142 mAmbientState.setActivatedChild(activatedChild); 2143 if (mAnimationsEnabled) { 2144 mActivateNeedsAnimation = true; 2145 mNeedsAnimation = true; 2146 } 2147 requestChildrenUpdate(); 2148 } 2149 2150 public ActivatableNotificationView getActivatedChild() { 2151 return mAmbientState.getActivatedChild(); 2152 } 2153 2154 private void applyCurrentState() { 2155 mCurrentStackScrollState.apply(); 2156 if (mListener != null) { 2157 mListener.onChildLocationsChanged(this); 2158 } 2159 } 2160 2161 public void setSpeedBumpView(SpeedBumpView speedBumpView) { 2162 mSpeedBumpView = speedBumpView; 2163 addView(speedBumpView); 2164 } 2165 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 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 2191 public void cancelExpandHelper() { 2192 mExpandHelper.cancel(); 2193 } 2194 2195 public void setIntrinsicPadding(int intrinsicPadding) { 2196 mIntrinsicPadding = intrinsicPadding; 2197 } 2198 2199 public int getIntrinsicPadding() { 2200 return mIntrinsicPadding; 2201 } 2202 2203 /** 2204 * @return the y position of the first notification 2205 */ 2206 public float getNotificationsTopY() { 2207 return mTopPadding + getTranslationY(); 2208 } 2209 2210 @Override 2211 public boolean shouldDelayChildPressedState() { 2212 return true; 2213 } 2214 2215 /** 2216 * See {@link AmbientState#setDark}. 2217 */ 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 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 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 2258 public void setDismissView(DismissView dismissView) { 2259 mDismissView = dismissView; 2260 addView(mDismissView); 2261 } 2262 2263 public void setEmptyShadeView(EmptyShadeView emptyShadeView) { 2264 mEmptyShadeView = emptyShadeView; 2265 addView(mEmptyShadeView); 2266 } 2267 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 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 2338 public void setDismissAllInProgress(boolean dismissAllInProgress) { 2339 mDismissAllInProgress = dismissAllInProgress; 2340 mDismissView.setDismissAllInProgress(dismissAllInProgress); 2341 } 2342 2343 public boolean isDismissViewNotGone() { 2344 return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone(); 2345 } 2346 2347 public boolean isDismissViewVisible() { 2348 return mDismissView.isVisible(); 2349 } 2350 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 2363 public int getEmptyShadeViewHeight() { 2364 return mEmptyShadeView.getHeight(); 2365 } 2366 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 */ 2387 public void updateIsSmallScreen(int qsMinHeight) { 2388 mStackScrollAlgorithm.updateIsSmallScreen(mMaxLayoutHeight - qsMinHeight); 2389 } 2390 2391 public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) { 2392 this.mPhoneStatusBar = phoneStatusBar; 2393 } 2394 2395 public void onGoToKeyguard() { 2396 if (mIsExpanded && mAnimationsEnabled) { 2397 mEverythingNeedsAnimation = true; 2398 requestChildrenUpdate(); 2399 } 2400 } 2401 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 { 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 { 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 */ 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 */ 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 2640 AnimationEvent(View view, int type) { 2641 this(view, type, LENGTHS[type]); 2642 } 2643 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 */ 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