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