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.isScreenOnComingFromTouch() ? 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 if (canChildBeDismissed(child)) { 1782 // Make sure the dismissButton is visible and not in the animated state. 1783 // We need to do this to avoid a race where a clearable notification is added after the 1784 // dismiss animation is finished 1785 mDismissView.showClearButton(); 1786 } 1787 } 1788 updateHideSensitiveForChild(View child)1789 private void updateHideSensitiveForChild(View child) { 1790 if (mAmbientState.isHideSensitive() && child instanceof ExpandableView) { 1791 ExpandableView expandableView = (ExpandableView) child; 1792 expandableView.setHideSensitiveForIntrinsicHeight(true); 1793 } 1794 } 1795 notifyGroupChildRemoved(View row)1796 public void notifyGroupChildRemoved(View row) { 1797 onViewRemovedInternal(row); 1798 } 1799 notifyGroupChildAdded(View row)1800 public void notifyGroupChildAdded(View row) { 1801 onViewAddedInternal(row); 1802 } 1803 setAnimationsEnabled(boolean animationsEnabled)1804 public void setAnimationsEnabled(boolean animationsEnabled) { 1805 mAnimationsEnabled = animationsEnabled; 1806 updateNotificationAnimationStates(); 1807 } 1808 updateNotificationAnimationStates()1809 private void updateNotificationAnimationStates() { 1810 boolean running = mAnimationsEnabled; 1811 int childCount = getChildCount(); 1812 for (int i = 0; i < childCount; i++) { 1813 View child = getChildAt(i); 1814 running &= mIsExpanded || isPinnedHeadsUp(child); 1815 updateAnimationState(running, child); 1816 } 1817 } 1818 updateAnimationState(View child)1819 private void updateAnimationState(View child) { 1820 updateAnimationState((mAnimationsEnabled || isPinnedHeadsUp(child)) && mIsExpanded, child); 1821 } 1822 1823 updateAnimationState(boolean running, View child)1824 private void updateAnimationState(boolean running, View child) { 1825 if (child instanceof ExpandableNotificationRow) { 1826 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 1827 row.setIconAnimationRunning(running); 1828 } 1829 } 1830 isAddOrRemoveAnimationPending()1831 public boolean isAddOrRemoveAnimationPending() { 1832 return mNeedsAnimation 1833 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty()); 1834 } 1835 /** 1836 * Generate an animation for an added child view. 1837 * 1838 * @param child The view to be added. 1839 * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen. 1840 */ generateAddAnimation(View child, boolean fromMoreCard)1841 public void generateAddAnimation(View child, boolean fromMoreCard) { 1842 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) { 1843 // Generate Animations 1844 mChildrenToAddAnimated.add(child); 1845 if (fromMoreCard) { 1846 mFromMoreCardAdditions.add(child); 1847 } 1848 mNeedsAnimation = true; 1849 } 1850 if (isHeadsUp(child)) { 1851 mAddedHeadsUpChildren.add(child); 1852 mChildrenToAddAnimated.remove(child); 1853 } 1854 } 1855 1856 /** 1857 * Change the position of child to a new location 1858 * 1859 * @param child the view to change the position for 1860 * @param newIndex the new index 1861 */ changeViewPosition(View child, int newIndex)1862 public void changeViewPosition(View child, int newIndex) { 1863 int currentIndex = indexOfChild(child); 1864 if (child != null && child.getParent() == this && currentIndex != newIndex) { 1865 mChangePositionInProgress = true; 1866 removeView(child); 1867 addView(child, newIndex); 1868 mChangePositionInProgress = false; 1869 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) { 1870 mChildrenChangingPositions.add(child); 1871 mNeedsAnimation = true; 1872 } 1873 } 1874 } 1875 startAnimationToState()1876 private void startAnimationToState() { 1877 if (mNeedsAnimation) { 1878 generateChildHierarchyEvents(); 1879 mNeedsAnimation = false; 1880 } 1881 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) { 1882 mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState, 1883 mGoToFullShadeDelay); 1884 mAnimationEvents.clear(); 1885 } else { 1886 applyCurrentState(); 1887 } 1888 mGoToFullShadeDelay = 0; 1889 } 1890 generateChildHierarchyEvents()1891 private void generateChildHierarchyEvents() { 1892 generateHeadsUpAnimationEvents(); 1893 generateChildRemovalEvents(); 1894 generateChildAdditionEvents(); 1895 generatePositionChangeEvents(); 1896 generateSnapBackEvents(); 1897 generateDragEvents(); 1898 generateTopPaddingEvent(); 1899 generateActivateEvent(); 1900 generateDimmedEvent(); 1901 generateHideSensitiveEvent(); 1902 generateDarkEvent(); 1903 generateGoToFullShadeEvent(); 1904 generateViewResizeEvent(); 1905 generateGroupExpansionEvent(); 1906 generateAnimateEverythingEvent(); 1907 mNeedsAnimation = false; 1908 } 1909 generateHeadsUpAnimationEvents()1910 private void generateHeadsUpAnimationEvents() { 1911 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) { 1912 ExpandableNotificationRow row = eventPair.first; 1913 boolean isHeadsUp = eventPair.second; 1914 int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER; 1915 boolean onBottom = false; 1916 boolean pinnedAndClosed = row.isPinned() && !mIsExpanded; 1917 if (!mIsExpanded && !isHeadsUp) { 1918 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR; 1919 } else { 1920 StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row); 1921 if (viewState == null) { 1922 // A view state was never generated for this view, so we don't need to animate 1923 // this. This may happen with notification children. 1924 continue; 1925 } 1926 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) { 1927 if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) { 1928 // Our custom add animation 1929 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR; 1930 } else { 1931 // Normal add animation 1932 type = AnimationEvent.ANIMATION_TYPE_ADD; 1933 } 1934 onBottom = !pinnedAndClosed; 1935 } 1936 } 1937 AnimationEvent event = new AnimationEvent(row, type); 1938 event.headsUpFromBottom = onBottom; 1939 mAnimationEvents.add(event); 1940 } 1941 mHeadsUpChangeAnimations.clear(); 1942 mAddedHeadsUpChildren.clear(); 1943 } 1944 shouldHunAppearFromBottom(StackViewState viewState)1945 private boolean shouldHunAppearFromBottom(StackViewState viewState) { 1946 if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) { 1947 return false; 1948 } 1949 return true; 1950 } 1951 generateGroupExpansionEvent()1952 private void generateGroupExpansionEvent() { 1953 // Generate a group expansion/collapsing event if there is such a group at all 1954 if (mExpandedGroupView != null) { 1955 mAnimationEvents.add(new AnimationEvent(mExpandedGroupView, 1956 AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED)); 1957 mExpandedGroupView = null; 1958 } 1959 } 1960 generateViewResizeEvent()1961 private void generateViewResizeEvent() { 1962 if (mNeedViewResizeAnimation) { 1963 mAnimationEvents.add( 1964 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE)); 1965 } 1966 mNeedViewResizeAnimation = false; 1967 } 1968 generateSnapBackEvents()1969 private void generateSnapBackEvents() { 1970 for (View child : mSnappedBackChildren) { 1971 mAnimationEvents.add(new AnimationEvent(child, 1972 AnimationEvent.ANIMATION_TYPE_SNAP_BACK)); 1973 } 1974 mSnappedBackChildren.clear(); 1975 } 1976 generateDragEvents()1977 private void generateDragEvents() { 1978 for (View child : mDragAnimPendingChildren) { 1979 mAnimationEvents.add(new AnimationEvent(child, 1980 AnimationEvent.ANIMATION_TYPE_START_DRAG)); 1981 } 1982 mDragAnimPendingChildren.clear(); 1983 } 1984 generateChildRemovalEvents()1985 private void generateChildRemovalEvents() { 1986 for (View child : mChildrenToRemoveAnimated) { 1987 boolean childWasSwipedOut = mSwipedOutViews.contains(child); 1988 int animationType = childWasSwipedOut 1989 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT 1990 : AnimationEvent.ANIMATION_TYPE_REMOVE; 1991 AnimationEvent event = new AnimationEvent(child, animationType); 1992 1993 // we need to know the view after this one 1994 event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY()); 1995 mAnimationEvents.add(event); 1996 } 1997 mSwipedOutViews.clear(); 1998 mChildrenToRemoveAnimated.clear(); 1999 } 2000 generatePositionChangeEvents()2001 private void generatePositionChangeEvents() { 2002 for (View child : mChildrenChangingPositions) { 2003 mAnimationEvents.add(new AnimationEvent(child, 2004 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)); 2005 } 2006 mChildrenChangingPositions.clear(); 2007 if (mGenerateChildOrderChangedEvent) { 2008 mAnimationEvents.add(new AnimationEvent(null, 2009 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)); 2010 mGenerateChildOrderChangedEvent = false; 2011 } 2012 } 2013 generateChildAdditionEvents()2014 private void generateChildAdditionEvents() { 2015 for (View child : mChildrenToAddAnimated) { 2016 if (mFromMoreCardAdditions.contains(child)) { 2017 mAnimationEvents.add(new AnimationEvent(child, 2018 AnimationEvent.ANIMATION_TYPE_ADD, 2019 StackStateAnimator.ANIMATION_DURATION_STANDARD)); 2020 } else { 2021 mAnimationEvents.add(new AnimationEvent(child, 2022 AnimationEvent.ANIMATION_TYPE_ADD)); 2023 } 2024 } 2025 mChildrenToAddAnimated.clear(); 2026 mFromMoreCardAdditions.clear(); 2027 } 2028 generateTopPaddingEvent()2029 private void generateTopPaddingEvent() { 2030 if (mTopPaddingNeedsAnimation) { 2031 mAnimationEvents.add( 2032 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED)); 2033 } 2034 mTopPaddingNeedsAnimation = false; 2035 } 2036 generateActivateEvent()2037 private void generateActivateEvent() { 2038 if (mActivateNeedsAnimation) { 2039 mAnimationEvents.add( 2040 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD)); 2041 } 2042 mActivateNeedsAnimation = false; 2043 } 2044 generateAnimateEverythingEvent()2045 private void generateAnimateEverythingEvent() { 2046 if (mEverythingNeedsAnimation) { 2047 mAnimationEvents.add( 2048 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING)); 2049 } 2050 mEverythingNeedsAnimation = false; 2051 } 2052 generateDimmedEvent()2053 private void generateDimmedEvent() { 2054 if (mDimmedNeedsAnimation) { 2055 mAnimationEvents.add( 2056 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED)); 2057 } 2058 mDimmedNeedsAnimation = false; 2059 } 2060 generateHideSensitiveEvent()2061 private void generateHideSensitiveEvent() { 2062 if (mHideSensitiveNeedsAnimation) { 2063 mAnimationEvents.add( 2064 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE)); 2065 } 2066 mHideSensitiveNeedsAnimation = false; 2067 } 2068 generateDarkEvent()2069 private void generateDarkEvent() { 2070 if (mDarkNeedsAnimation) { 2071 AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK); 2072 ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex; 2073 mAnimationEvents.add(ev); 2074 } 2075 mDarkNeedsAnimation = false; 2076 } 2077 generateGoToFullShadeEvent()2078 private void generateGoToFullShadeEvent() { 2079 if (mGoToFullShadeNeedsAnimation) { 2080 mAnimationEvents.add( 2081 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE)); 2082 } 2083 mGoToFullShadeNeedsAnimation = false; 2084 } 2085 onInterceptTouchEventScroll(MotionEvent ev)2086 private boolean onInterceptTouchEventScroll(MotionEvent ev) { 2087 if (!isScrollingEnabled()) { 2088 return false; 2089 } 2090 /* 2091 * This method JUST determines whether we want to intercept the motion. 2092 * If we return true, onMotionEvent will be called and we do the actual 2093 * scrolling there. 2094 */ 2095 2096 /* 2097 * Shortcut the most recurring case: the user is in the dragging 2098 * state and he is moving his finger. We want to intercept this 2099 * motion. 2100 */ 2101 final int action = ev.getAction(); 2102 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { 2103 return true; 2104 } 2105 2106 switch (action & MotionEvent.ACTION_MASK) { 2107 case MotionEvent.ACTION_MOVE: { 2108 /* 2109 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 2110 * whether the user has moved far enough from his original down touch. 2111 */ 2112 2113 /* 2114 * Locally do absolute value. mLastMotionY is set to the y value 2115 * of the down event. 2116 */ 2117 final int activePointerId = mActivePointerId; 2118 if (activePointerId == INVALID_POINTER) { 2119 // If we don't have a valid id, the touch down wasn't on content. 2120 break; 2121 } 2122 2123 final int pointerIndex = ev.findPointerIndex(activePointerId); 2124 if (pointerIndex == -1) { 2125 Log.e(TAG, "Invalid pointerId=" + activePointerId 2126 + " in onInterceptTouchEvent"); 2127 break; 2128 } 2129 2130 final int y = (int) ev.getY(pointerIndex); 2131 final int x = (int) ev.getX(pointerIndex); 2132 final int yDiff = Math.abs(y - mLastMotionY); 2133 final int xDiff = Math.abs(x - mDownX); 2134 if (yDiff > mTouchSlop && yDiff > xDiff) { 2135 setIsBeingDragged(true); 2136 mLastMotionY = y; 2137 mDownX = x; 2138 initVelocityTrackerIfNotExists(); 2139 mVelocityTracker.addMovement(ev); 2140 } 2141 break; 2142 } 2143 2144 case MotionEvent.ACTION_DOWN: { 2145 final int y = (int) ev.getY(); 2146 if (getChildAtPosition(ev.getX(), y) == null) { 2147 setIsBeingDragged(false); 2148 recycleVelocityTracker(); 2149 break; 2150 } 2151 2152 /* 2153 * Remember location of down touch. 2154 * ACTION_DOWN always refers to pointer index 0. 2155 */ 2156 mLastMotionY = y; 2157 mDownX = (int) ev.getX(); 2158 mActivePointerId = ev.getPointerId(0); 2159 mScrolledToTopOnFirstDown = isScrolledToTop(); 2160 2161 initOrResetVelocityTracker(); 2162 mVelocityTracker.addMovement(ev); 2163 /* 2164 * If being flinged and user touches the screen, initiate drag; 2165 * otherwise don't. mScroller.isFinished should be false when 2166 * being flinged. 2167 */ 2168 boolean isBeingDragged = !mScroller.isFinished(); 2169 setIsBeingDragged(isBeingDragged); 2170 break; 2171 } 2172 2173 case MotionEvent.ACTION_CANCEL: 2174 case MotionEvent.ACTION_UP: 2175 /* Release the drag */ 2176 setIsBeingDragged(false); 2177 mActivePointerId = INVALID_POINTER; 2178 recycleVelocityTracker(); 2179 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { 2180 postInvalidateOnAnimation(); 2181 } 2182 break; 2183 case MotionEvent.ACTION_POINTER_UP: 2184 onSecondaryPointerUp(ev); 2185 break; 2186 } 2187 2188 /* 2189 * The only time we want to intercept motion events is if we are in the 2190 * drag mode. 2191 */ 2192 return mIsBeingDragged; 2193 } 2194 2195 /** 2196 * @return Whether the specified motion event is actually happening over the content. 2197 */ isInContentBounds(MotionEvent event)2198 private boolean isInContentBounds(MotionEvent event) { 2199 return isInContentBounds(event.getY()); 2200 } 2201 2202 /** 2203 * @return Whether a y coordinate is inside the content. 2204 */ isInContentBounds(float y)2205 public boolean isInContentBounds(float y) { 2206 return y < getHeight() - getEmptyBottomMargin(); 2207 } 2208 setIsBeingDragged(boolean isDragged)2209 private void setIsBeingDragged(boolean isDragged) { 2210 mIsBeingDragged = isDragged; 2211 if (isDragged) { 2212 requestDisallowInterceptTouchEvent(true); 2213 removeLongPressCallback(); 2214 } 2215 } 2216 2217 @Override onWindowFocusChanged(boolean hasWindowFocus)2218 public void onWindowFocusChanged(boolean hasWindowFocus) { 2219 super.onWindowFocusChanged(hasWindowFocus); 2220 if (!hasWindowFocus) { 2221 removeLongPressCallback(); 2222 } 2223 } 2224 removeLongPressCallback()2225 public void removeLongPressCallback() { 2226 mSwipeHelper.removeLongPressCallback(); 2227 } 2228 2229 @Override isScrolledToTop()2230 public boolean isScrolledToTop() { 2231 return mOwnScrollY == 0; 2232 } 2233 2234 @Override isScrolledToBottom()2235 public boolean isScrolledToBottom() { 2236 return mOwnScrollY >= getScrollRange(); 2237 } 2238 2239 @Override getHostView()2240 public View getHostView() { 2241 return this; 2242 } 2243 getEmptyBottomMargin()2244 public int getEmptyBottomMargin() { 2245 int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize; 2246 if (needsHeightAdaption()) { 2247 emptyMargin -= mBottomStackSlowDownHeight; 2248 } else { 2249 emptyMargin -= mCollapseSecondCardPadding; 2250 } 2251 return Math.max(emptyMargin, 0); 2252 } 2253 onExpansionStarted()2254 public void onExpansionStarted() { 2255 mIsExpansionChanging = true; 2256 mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState); 2257 } 2258 onExpansionStopped()2259 public void onExpansionStopped() { 2260 mIsExpansionChanging = false; 2261 mStackScrollAlgorithm.onExpansionStopped(); 2262 if (!mIsExpanded) { 2263 mOwnScrollY = 0; 2264 2265 // lets make sure nothing is in the overlay anymore 2266 getOverlay().clear(); 2267 } 2268 } 2269 onPanelTrackingStarted()2270 public void onPanelTrackingStarted() { 2271 mPanelTracking = true; 2272 } onPanelTrackingStopped()2273 public void onPanelTrackingStopped() { 2274 mPanelTracking = false; 2275 } 2276 resetScrollPosition()2277 public void resetScrollPosition() { 2278 mScroller.abortAnimation(); 2279 mOwnScrollY = 0; 2280 } 2281 setIsExpanded(boolean isExpanded)2282 private void setIsExpanded(boolean isExpanded) { 2283 boolean changed = isExpanded != mIsExpanded; 2284 mIsExpanded = isExpanded; 2285 mStackScrollAlgorithm.setIsExpanded(isExpanded); 2286 if (changed) { 2287 updateNotificationAnimationStates(); 2288 } 2289 } 2290 2291 @Override onHeightChanged(ExpandableView view, boolean needsAnimation)2292 public void onHeightChanged(ExpandableView view, boolean needsAnimation) { 2293 updateContentHeight(); 2294 updateScrollPositionOnExpandInBottom(view); 2295 clampScrollPosition(); 2296 notifyHeightChangeListener(view); 2297 if (needsAnimation) { 2298 requestAnimationOnViewResize(); 2299 } 2300 requestChildrenUpdate(); 2301 } 2302 2303 @Override onReset(ExpandableView view)2304 public void onReset(ExpandableView view) { 2305 if (mIsExpanded && mAnimationsEnabled) { 2306 mRequestViewResizeAnimationOnLayout = true; 2307 } 2308 mStackScrollAlgorithm.onReset(view); 2309 updateAnimationState(view); 2310 } 2311 updateScrollPositionOnExpandInBottom(ExpandableView view)2312 private void updateScrollPositionOnExpandInBottom(ExpandableView view) { 2313 if (view instanceof ExpandableNotificationRow) { 2314 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 2315 if (row.isUserLocked() && row != getFirstChildNotGone()) { 2316 // We are actually expanding this view 2317 float endPosition = row.getTranslationY() + row.getActualHeight(); 2318 int stackEnd = mMaxLayoutHeight - mBottomStackPeekSize - 2319 mBottomStackSlowDownHeight + (int) mStackTranslation; 2320 if (endPosition > stackEnd) { 2321 mOwnScrollY += endPosition - stackEnd; 2322 mDisallowScrollingInThisMotion = true; 2323 } 2324 } 2325 } 2326 } 2327 setOnHeightChangedListener( ExpandableView.OnHeightChangedListener mOnHeightChangedListener)2328 public void setOnHeightChangedListener( 2329 ExpandableView.OnHeightChangedListener mOnHeightChangedListener) { 2330 this.mOnHeightChangedListener = mOnHeightChangedListener; 2331 } 2332 setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener)2333 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) { 2334 mOnEmptySpaceClickListener = listener; 2335 } 2336 onChildAnimationFinished()2337 public void onChildAnimationFinished() { 2338 requestChildrenUpdate(); 2339 runAnimationFinishedRunnables(); 2340 clearViewOverlays(); 2341 } 2342 clearViewOverlays()2343 private void clearViewOverlays() { 2344 for (View view : mClearOverlayViewsWhenFinished) { 2345 getOverlay().remove(view); 2346 } 2347 } 2348 runAnimationFinishedRunnables()2349 private void runAnimationFinishedRunnables() { 2350 for (Runnable runnable : mAnimationFinishedRunnables) { 2351 runnable.run(); 2352 } 2353 mAnimationFinishedRunnables.clear(); 2354 } 2355 2356 /** 2357 * See {@link AmbientState#setDimmed}. 2358 */ setDimmed(boolean dimmed, boolean animate)2359 public void setDimmed(boolean dimmed, boolean animate) { 2360 mStackScrollAlgorithm.setDimmed(dimmed); 2361 mAmbientState.setDimmed(dimmed); 2362 updatePadding(dimmed); 2363 if (animate && mAnimationsEnabled) { 2364 mDimmedNeedsAnimation = true; 2365 mNeedsAnimation = true; 2366 } 2367 requestChildrenUpdate(); 2368 } 2369 setHideSensitive(boolean hideSensitive, boolean animate)2370 public void setHideSensitive(boolean hideSensitive, boolean animate) { 2371 if (hideSensitive != mAmbientState.isHideSensitive()) { 2372 int childCount = getChildCount(); 2373 for (int i = 0; i < childCount; i++) { 2374 ExpandableView v = (ExpandableView) getChildAt(i); 2375 v.setHideSensitiveForIntrinsicHeight(hideSensitive); 2376 } 2377 mAmbientState.setHideSensitive(hideSensitive); 2378 if (animate && mAnimationsEnabled) { 2379 mHideSensitiveNeedsAnimation = true; 2380 mNeedsAnimation = true; 2381 } 2382 requestChildrenUpdate(); 2383 } 2384 } 2385 2386 /** 2387 * See {@link AmbientState#setActivatedChild}. 2388 */ setActivatedChild(ActivatableNotificationView activatedChild)2389 public void setActivatedChild(ActivatableNotificationView activatedChild) { 2390 mAmbientState.setActivatedChild(activatedChild); 2391 if (mAnimationsEnabled) { 2392 mActivateNeedsAnimation = true; 2393 mNeedsAnimation = true; 2394 } 2395 requestChildrenUpdate(); 2396 } 2397 getActivatedChild()2398 public ActivatableNotificationView getActivatedChild() { 2399 return mAmbientState.getActivatedChild(); 2400 } 2401 applyCurrentState()2402 private void applyCurrentState() { 2403 mCurrentStackScrollState.apply(); 2404 if (mListener != null) { 2405 mListener.onChildLocationsChanged(this); 2406 } 2407 runAnimationFinishedRunnables(); 2408 } 2409 setSpeedBumpView(SpeedBumpView speedBumpView)2410 public void setSpeedBumpView(SpeedBumpView speedBumpView) { 2411 mSpeedBumpView = speedBumpView; 2412 addView(speedBumpView); 2413 } 2414 updateSpeedBump(boolean visible)2415 private void updateSpeedBump(boolean visible) { 2416 boolean notGoneBefore = mSpeedBumpView.getVisibility() != GONE; 2417 if (visible != notGoneBefore) { 2418 int newVisibility = visible ? VISIBLE : GONE; 2419 mSpeedBumpView.setVisibility(newVisibility); 2420 if (visible) { 2421 // Make invisible to ensure that the appear animation is played. 2422 mSpeedBumpView.setInvisible(); 2423 } else { 2424 // TODO: This doesn't really work, because the view is already set to GONE above. 2425 generateRemoveAnimation(mSpeedBumpView); 2426 } 2427 } 2428 } 2429 goToFullShade(long delay)2430 public void goToFullShade(long delay) { 2431 updateSpeedBump(true /* visibility */); 2432 mDismissView.setInvisible(); 2433 mEmptyShadeView.setInvisible(); 2434 mGoToFullShadeNeedsAnimation = true; 2435 mGoToFullShadeDelay = delay; 2436 mNeedsAnimation = true; 2437 requestChildrenUpdate(); 2438 } 2439 cancelExpandHelper()2440 public void cancelExpandHelper() { 2441 mExpandHelper.cancel(); 2442 } 2443 setIntrinsicPadding(int intrinsicPadding)2444 public void setIntrinsicPadding(int intrinsicPadding) { 2445 mIntrinsicPadding = intrinsicPadding; 2446 } 2447 getIntrinsicPadding()2448 public int getIntrinsicPadding() { 2449 return mIntrinsicPadding; 2450 } 2451 2452 /** 2453 * @return the y position of the first notification 2454 */ getNotificationsTopY()2455 public float getNotificationsTopY() { 2456 return mTopPadding + getStackTranslation(); 2457 } 2458 2459 @Override shouldDelayChildPressedState()2460 public boolean shouldDelayChildPressedState() { 2461 return true; 2462 } 2463 2464 /** 2465 * See {@link AmbientState#setDark}. 2466 */ setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation)2467 public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) { 2468 mAmbientState.setDark(dark); 2469 if (animate && mAnimationsEnabled) { 2470 mDarkNeedsAnimation = true; 2471 mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation); 2472 mNeedsAnimation = true; 2473 } 2474 requestChildrenUpdate(); 2475 } 2476 findDarkAnimationOriginIndex(@ullable PointF screenLocation)2477 private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) { 2478 if (screenLocation == null || screenLocation.y < mTopPadding + mTopPaddingOverflow) { 2479 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE; 2480 } 2481 if (screenLocation.y > getBottomMostNotificationBottom()) { 2482 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW; 2483 } 2484 View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y); 2485 if (child != null) { 2486 return getNotGoneIndex(child); 2487 } else { 2488 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE; 2489 } 2490 } 2491 getNotGoneIndex(View child)2492 private int getNotGoneIndex(View child) { 2493 int count = getChildCount(); 2494 int notGoneIndex = 0; 2495 for (int i = 0; i < count; i++) { 2496 View v = getChildAt(i); 2497 if (child == v) { 2498 return notGoneIndex; 2499 } 2500 if (v.getVisibility() != View.GONE) { 2501 notGoneIndex++; 2502 } 2503 } 2504 return -1; 2505 } 2506 setDismissView(DismissView dismissView)2507 public void setDismissView(DismissView dismissView) { 2508 mDismissView = dismissView; 2509 addView(mDismissView); 2510 } 2511 setEmptyShadeView(EmptyShadeView emptyShadeView)2512 public void setEmptyShadeView(EmptyShadeView emptyShadeView) { 2513 mEmptyShadeView = emptyShadeView; 2514 addView(mEmptyShadeView); 2515 } 2516 updateEmptyShadeView(boolean visible)2517 public void updateEmptyShadeView(boolean visible) { 2518 int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility(); 2519 int newVisibility = visible ? VISIBLE : GONE; 2520 if (oldVisibility != newVisibility) { 2521 if (newVisibility != GONE) { 2522 if (mEmptyShadeView.willBeGone()) { 2523 mEmptyShadeView.cancelAnimation(); 2524 } else { 2525 mEmptyShadeView.setInvisible(); 2526 } 2527 mEmptyShadeView.setVisibility(newVisibility); 2528 mEmptyShadeView.setWillBeGone(false); 2529 updateContentHeight(); 2530 notifyHeightChangeListener(mEmptyShadeView); 2531 } else { 2532 Runnable onFinishedRunnable = new Runnable() { 2533 @Override 2534 public void run() { 2535 mEmptyShadeView.setVisibility(GONE); 2536 mEmptyShadeView.setWillBeGone(false); 2537 updateContentHeight(); 2538 notifyHeightChangeListener(mEmptyShadeView); 2539 } 2540 }; 2541 if (mAnimationsEnabled) { 2542 mEmptyShadeView.setWillBeGone(true); 2543 mEmptyShadeView.performVisibilityAnimation(false, onFinishedRunnable); 2544 } else { 2545 mEmptyShadeView.setInvisible(); 2546 onFinishedRunnable.run(); 2547 } 2548 } 2549 } 2550 } 2551 setOverflowContainer(NotificationOverflowContainer overFlowContainer)2552 public void setOverflowContainer(NotificationOverflowContainer overFlowContainer) { 2553 mOverflowContainer = overFlowContainer; 2554 addView(mOverflowContainer); 2555 } 2556 updateOverflowContainerVisibility(boolean visible)2557 public void updateOverflowContainerVisibility(boolean visible) { 2558 int oldVisibility = mOverflowContainer.willBeGone() ? GONE 2559 : mOverflowContainer.getVisibility(); 2560 final int newVisibility = visible ? VISIBLE : GONE; 2561 if (oldVisibility != newVisibility) { 2562 Runnable onFinishedRunnable = new Runnable() { 2563 @Override 2564 public void run() { 2565 mOverflowContainer.setVisibility(newVisibility); 2566 mOverflowContainer.setWillBeGone(false); 2567 updateContentHeight(); 2568 notifyHeightChangeListener(mOverflowContainer); 2569 } 2570 }; 2571 if (!mAnimationsEnabled || !mIsExpanded) { 2572 mOverflowContainer.cancelAppearDrawing(); 2573 onFinishedRunnable.run(); 2574 } else if (newVisibility != GONE) { 2575 mOverflowContainer.performAddAnimation(0, 2576 StackStateAnimator.ANIMATION_DURATION_STANDARD); 2577 mOverflowContainer.setVisibility(newVisibility); 2578 mOverflowContainer.setWillBeGone(false); 2579 updateContentHeight(); 2580 notifyHeightChangeListener(mOverflowContainer); 2581 } else { 2582 mOverflowContainer.performRemoveAnimation( 2583 StackStateAnimator.ANIMATION_DURATION_STANDARD, 2584 0.0f, 2585 onFinishedRunnable); 2586 mOverflowContainer.setWillBeGone(true); 2587 } 2588 } 2589 } 2590 updateDismissView(boolean visible)2591 public void updateDismissView(boolean visible) { 2592 int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility(); 2593 int newVisibility = visible ? VISIBLE : GONE; 2594 if (oldVisibility != newVisibility) { 2595 if (newVisibility != GONE) { 2596 if (mDismissView.willBeGone()) { 2597 mDismissView.cancelAnimation(); 2598 } else { 2599 mDismissView.setInvisible(); 2600 } 2601 mDismissView.setVisibility(newVisibility); 2602 mDismissView.setWillBeGone(false); 2603 updateContentHeight(); 2604 notifyHeightChangeListener(mDismissView); 2605 } else { 2606 Runnable dimissHideFinishRunnable = new Runnable() { 2607 @Override 2608 public void run() { 2609 mDismissView.setVisibility(GONE); 2610 mDismissView.setWillBeGone(false); 2611 updateContentHeight(); 2612 notifyHeightChangeListener(mDismissView); 2613 } 2614 }; 2615 if (mDismissView.isButtonVisible() && mIsExpanded && mAnimationsEnabled) { 2616 mDismissView.setWillBeGone(true); 2617 mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable); 2618 } else { 2619 dimissHideFinishRunnable.run(); 2620 mDismissView.showClearButton(); 2621 } 2622 } 2623 } 2624 } 2625 setDismissAllInProgress(boolean dismissAllInProgress)2626 public void setDismissAllInProgress(boolean dismissAllInProgress) { 2627 mDismissAllInProgress = dismissAllInProgress; 2628 mDismissView.setDismissAllInProgress(dismissAllInProgress); 2629 mAmbientState.setDismissAllInProgress(dismissAllInProgress); 2630 if (dismissAllInProgress) { 2631 disableClipOptimization(); 2632 } 2633 handleDismissAllClipping(); 2634 } 2635 handleDismissAllClipping()2636 private void handleDismissAllClipping() { 2637 final int count = getChildCount(); 2638 boolean previousChildWillBeDismissed = false; 2639 for (int i = 0; i < count; i++) { 2640 ExpandableView child = (ExpandableView) getChildAt(i); 2641 if (child.getVisibility() == GONE) { 2642 continue; 2643 } 2644 if (mDismissAllInProgress && previousChildWillBeDismissed) { 2645 child.setMinClipTopAmount(child.getClipTopAmount()); 2646 } else { 2647 child.setMinClipTopAmount(0); 2648 } 2649 previousChildWillBeDismissed = canChildBeDismissed(child); 2650 } 2651 } 2652 disableClipOptimization()2653 private void disableClipOptimization() { 2654 final int count = getChildCount(); 2655 for (int i = 0; i < count; i++) { 2656 ExpandableView child = (ExpandableView) getChildAt(i); 2657 if (child.getVisibility() == GONE) { 2658 continue; 2659 } 2660 child.setClipTopOptimization(0); 2661 } 2662 } 2663 isDismissViewNotGone()2664 public boolean isDismissViewNotGone() { 2665 return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone(); 2666 } 2667 isDismissViewVisible()2668 public boolean isDismissViewVisible() { 2669 return mDismissView.isVisible(); 2670 } 2671 getDismissViewHeight()2672 public int getDismissViewHeight() { 2673 int height = mDismissView.getHeight() + mPaddingBetweenElementsNormal; 2674 2675 // Hack: Accommodate for additional distance when we only have one notification and the 2676 // dismiss all button. 2677 if (getNotGoneChildCount() == 2 && getLastChildNotGone() == mDismissView 2678 && getFirstChildNotGone() instanceof ActivatableNotificationView) { 2679 height += mCollapseSecondCardPadding; 2680 } 2681 return height; 2682 } 2683 getEmptyShadeViewHeight()2684 public int getEmptyShadeViewHeight() { 2685 return mEmptyShadeView.getHeight(); 2686 } 2687 getBottomMostNotificationBottom()2688 public float getBottomMostNotificationBottom() { 2689 final int count = getChildCount(); 2690 float max = 0; 2691 for (int childIdx = 0; childIdx < count; childIdx++) { 2692 ExpandableView child = (ExpandableView) getChildAt(childIdx); 2693 if (child.getVisibility() == GONE) { 2694 continue; 2695 } 2696 float bottom = child.getTranslationY() + child.getActualHeight(); 2697 if (bottom > max) { 2698 max = bottom; 2699 } 2700 } 2701 return max + getStackTranslation(); 2702 } 2703 2704 /** 2705 * @param qsMinHeight The minimum height of the quick settings including padding 2706 * See {@link StackScrollAlgorithm#updateIsSmallScreen}. 2707 */ updateIsSmallScreen(int qsMinHeight)2708 public void updateIsSmallScreen(int qsMinHeight) { 2709 mStackScrollAlgorithm.updateIsSmallScreen(mMaxLayoutHeight - qsMinHeight); 2710 } 2711 setPhoneStatusBar(PhoneStatusBar phoneStatusBar)2712 public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) { 2713 this.mPhoneStatusBar = phoneStatusBar; 2714 } 2715 setGroupManager(NotificationGroupManager groupManager)2716 public void setGroupManager(NotificationGroupManager groupManager) { 2717 this.mGroupManager = groupManager; 2718 } 2719 onGoToKeyguard()2720 public void onGoToKeyguard() { 2721 requestAnimateEverything(); 2722 } 2723 requestAnimateEverything()2724 private void requestAnimateEverything() { 2725 if (mIsExpanded && mAnimationsEnabled) { 2726 mEverythingNeedsAnimation = true; 2727 mNeedsAnimation = true; 2728 requestChildrenUpdate(); 2729 } 2730 } 2731 isBelowLastNotification(float touchX, float touchY)2732 public boolean isBelowLastNotification(float touchX, float touchY) { 2733 int childCount = getChildCount(); 2734 for (int i = childCount - 1; i >= 0; i--) { 2735 ExpandableView child = (ExpandableView) getChildAt(i); 2736 if (child.getVisibility() != View.GONE) { 2737 float childTop = child.getY(); 2738 if (childTop > touchY) { 2739 // we are above a notification entirely let's abort 2740 return false; 2741 } 2742 boolean belowChild = touchY > childTop + child.getActualHeight(); 2743 if (child == mDismissView) { 2744 if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(), 2745 touchY - childTop)) { 2746 // We clicked on the dismiss button 2747 return false; 2748 } 2749 } else if (child == mEmptyShadeView) { 2750 // We arrived at the empty shade view, for which we accept all clicks 2751 return true; 2752 } else if (!belowChild){ 2753 // We are on a child 2754 return false; 2755 } 2756 } 2757 } 2758 return touchY > mTopPadding + mStackTranslation; 2759 } 2760 updateExpandButtons()2761 private void updateExpandButtons() { 2762 for (int i = 0; i < getChildCount(); i++) { 2763 View child = getChildAt(i); 2764 if (child instanceof ExpandableNotificationRow) { 2765 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 2766 row.updateExpandButton(); 2767 } 2768 } 2769 } 2770 2771 @Override onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded)2772 public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) { 2773 boolean animated = mAnimationsEnabled && mIsExpanded; 2774 if (animated) { 2775 mExpandedGroupView = changedRow; 2776 mNeedsAnimation = true; 2777 } 2778 changedRow.setChildrenExpanded(expanded, animated); 2779 onHeightChanged(changedRow, false /* needsAnimation */); 2780 } 2781 2782 @Override onGroupsProhibitedChanged()2783 public void onGroupsProhibitedChanged() { 2784 updateExpandButtons(); 2785 } 2786 2787 @Override onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group)2788 public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) { 2789 for (NotificationData.Entry entry : group.children) { 2790 ExpandableNotificationRow row = entry.row; 2791 if (indexOfChild(row) != -1) { 2792 removeView(row); 2793 group.summary.row.addChildNotification(row); 2794 } 2795 } 2796 } 2797 generateChildOrderChangedEvent()2798 public void generateChildOrderChangedEvent() { 2799 if (mIsExpanded && mAnimationsEnabled) { 2800 mGenerateChildOrderChangedEvent = true; 2801 mNeedsAnimation = true; 2802 requestChildrenUpdate(); 2803 } 2804 } 2805 runAfterAnimationFinished(Runnable runnable)2806 public void runAfterAnimationFinished(Runnable runnable) { 2807 mAnimationFinishedRunnables.add(runnable); 2808 } 2809 setHeadsUpManager(HeadsUpManager headsUpManager)2810 public void setHeadsUpManager(HeadsUpManager headsUpManager) { 2811 mHeadsUpManager = headsUpManager; 2812 mAmbientState.setHeadsUpManager(headsUpManager); 2813 mStackScrollAlgorithm.setHeadsUpManager(headsUpManager); 2814 } 2815 generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp)2816 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) { 2817 if (mAnimationsEnabled) { 2818 mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp)); 2819 mNeedsAnimation = true; 2820 requestChildrenUpdate(); 2821 } 2822 } 2823 setShadeExpanded(boolean shadeExpanded)2824 public void setShadeExpanded(boolean shadeExpanded) { 2825 mAmbientState.setShadeExpanded(shadeExpanded); 2826 mStateAnimator.setShadeExpanded(shadeExpanded); 2827 } 2828 2829 /** 2830 * Set the boundary for the bottom heads up position. The heads up will always be above this 2831 * position. 2832 * 2833 * @param height the height of the screen 2834 * @param bottomBarHeight the height of the bar on the bottom 2835 */ setHeadsUpBoundaries(int height, int bottomBarHeight)2836 public void setHeadsUpBoundaries(int height, int bottomBarHeight) { 2837 mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight); 2838 mStateAnimator.setHeadsUpAppearHeightBottom(height); 2839 requestChildrenUpdate(); 2840 } 2841 setTrackingHeadsUp(boolean trackingHeadsUp)2842 public void setTrackingHeadsUp(boolean trackingHeadsUp) { 2843 mTrackingHeadsUp = trackingHeadsUp; 2844 } 2845 setScrimController(ScrimController scrimController)2846 public void setScrimController(ScrimController scrimController) { 2847 mScrimController = scrimController; 2848 } 2849 forceNoOverlappingRendering(boolean force)2850 public void forceNoOverlappingRendering(boolean force) { 2851 mForceNoOverlappingRendering = force; 2852 } 2853 2854 @Override hasOverlappingRendering()2855 public boolean hasOverlappingRendering() { 2856 return !mForceNoOverlappingRendering && super.hasOverlappingRendering(); 2857 } 2858 2859 /** 2860 * A listener that is notified when some child locations might have changed. 2861 */ 2862 public interface OnChildLocationsChangedListener { onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout)2863 public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout); 2864 } 2865 2866 /** 2867 * A listener that is notified when the empty space below the notifications is clicked on 2868 */ 2869 public interface OnEmptySpaceClickListener { onEmptySpaceClicked(float x, float y)2870 public void onEmptySpaceClicked(float x, float y); 2871 } 2872 2873 /** 2874 * A listener that gets notified when the overscroll at the top has changed. 2875 */ 2876 public interface OnOverscrollTopChangedListener { 2877 2878 /** 2879 * Notifies a listener that the overscroll has changed. 2880 * 2881 * @param amount the amount of overscroll, in pixels 2882 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an 2883 * unrubberbanded motion to directly expand overscroll view (e.g expand 2884 * QS) 2885 */ onOverscrollTopChanged(float amount, boolean isRubberbanded)2886 public void onOverscrollTopChanged(float amount, boolean isRubberbanded); 2887 2888 /** 2889 * Notify a listener that the scroller wants to escape from the scrolling motion and 2890 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS) 2891 * 2892 * @param velocity The velocity that the Scroller had when over flinging 2893 * @param open Should the fling open or close the overscroll view. 2894 */ flingTopOverscroll(float velocity, boolean open)2895 public void flingTopOverscroll(float velocity, boolean open); 2896 } 2897 2898 static class AnimationEvent { 2899 2900 static AnimationFilter[] FILTERS = new AnimationFilter[] { 2901 2902 // ANIMATION_TYPE_ADD 2903 new AnimationFilter() 2904 .animateAlpha() 2905 .animateHeight() 2906 .animateTopInset() 2907 .animateY() 2908 .animateZ() 2909 .hasDelays(), 2910 2911 // ANIMATION_TYPE_REMOVE 2912 new AnimationFilter() 2913 .animateAlpha() 2914 .animateHeight() 2915 .animateTopInset() 2916 .animateY() 2917 .animateZ() 2918 .hasDelays(), 2919 2920 // ANIMATION_TYPE_REMOVE_SWIPED_OUT 2921 new AnimationFilter() 2922 .animateAlpha() 2923 .animateHeight() 2924 .animateTopInset() 2925 .animateY() 2926 .animateZ() 2927 .hasDelays(), 2928 2929 // ANIMATION_TYPE_TOP_PADDING_CHANGED 2930 new AnimationFilter() 2931 .animateAlpha() 2932 .animateHeight() 2933 .animateTopInset() 2934 .animateY() 2935 .animateDimmed() 2936 .animateScale() 2937 .animateZ(), 2938 2939 // ANIMATION_TYPE_START_DRAG 2940 new AnimationFilter() 2941 .animateAlpha(), 2942 2943 // ANIMATION_TYPE_SNAP_BACK 2944 new AnimationFilter() 2945 .animateAlpha() 2946 .animateHeight(), 2947 2948 // ANIMATION_TYPE_ACTIVATED_CHILD 2949 new AnimationFilter() 2950 .animateScale() 2951 .animateAlpha(), 2952 2953 // ANIMATION_TYPE_DIMMED 2954 new AnimationFilter() 2955 .animateY() 2956 .animateScale() 2957 .animateDimmed(), 2958 2959 // ANIMATION_TYPE_CHANGE_POSITION 2960 new AnimationFilter() 2961 .animateAlpha() 2962 .animateHeight() 2963 .animateTopInset() 2964 .animateY() 2965 .animateZ(), 2966 2967 // ANIMATION_TYPE_DARK 2968 new AnimationFilter() 2969 .animateDark() 2970 .hasDelays(), 2971 2972 // ANIMATION_TYPE_GO_TO_FULL_SHADE 2973 new AnimationFilter() 2974 .animateAlpha() 2975 .animateHeight() 2976 .animateTopInset() 2977 .animateY() 2978 .animateDimmed() 2979 .animateScale() 2980 .animateZ() 2981 .hasDelays(), 2982 2983 // ANIMATION_TYPE_HIDE_SENSITIVE 2984 new AnimationFilter() 2985 .animateHideSensitive(), 2986 2987 // ANIMATION_TYPE_VIEW_RESIZE 2988 new AnimationFilter() 2989 .animateAlpha() 2990 .animateHeight() 2991 .animateTopInset() 2992 .animateY() 2993 .animateZ(), 2994 2995 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED 2996 new AnimationFilter() 2997 .animateAlpha() 2998 .animateHeight() 2999 .animateTopInset() 3000 .animateY() 3001 .animateZ(), 3002 3003 // ANIMATION_TYPE_HEADS_UP_APPEAR 3004 new AnimationFilter() 3005 .animateAlpha() 3006 .animateHeight() 3007 .animateTopInset() 3008 .animateY() 3009 .animateZ(), 3010 3011 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR 3012 new AnimationFilter() 3013 .animateAlpha() 3014 .animateHeight() 3015 .animateTopInset() 3016 .animateY() 3017 .animateZ(), 3018 3019 // ANIMATION_TYPE_HEADS_UP_OTHER 3020 new AnimationFilter() 3021 .animateAlpha() 3022 .animateHeight() 3023 .animateTopInset() 3024 .animateY() 3025 .animateZ(), 3026 3027 // ANIMATION_TYPE_EVERYTHING 3028 new AnimationFilter() 3029 .animateAlpha() 3030 .animateDark() 3031 .animateScale() 3032 .animateDimmed() 3033 .animateHideSensitive() 3034 .animateHeight() 3035 .animateTopInset() 3036 .animateY() 3037 .animateZ(), 3038 }; 3039 3040 static int[] LENGTHS = new int[] { 3041 3042 // ANIMATION_TYPE_ADD 3043 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR, 3044 3045 // ANIMATION_TYPE_REMOVE 3046 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR, 3047 3048 // ANIMATION_TYPE_REMOVE_SWIPED_OUT 3049 StackStateAnimator.ANIMATION_DURATION_STANDARD, 3050 3051 // ANIMATION_TYPE_TOP_PADDING_CHANGED 3052 StackStateAnimator.ANIMATION_DURATION_STANDARD, 3053 3054 // ANIMATION_TYPE_START_DRAG 3055 StackStateAnimator.ANIMATION_DURATION_STANDARD, 3056 3057 // ANIMATION_TYPE_SNAP_BACK 3058 StackStateAnimator.ANIMATION_DURATION_STANDARD, 3059 3060 // ANIMATION_TYPE_ACTIVATED_CHILD 3061 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED, 3062 3063 // ANIMATION_TYPE_DIMMED 3064 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED, 3065 3066 // ANIMATION_TYPE_CHANGE_POSITION 3067 StackStateAnimator.ANIMATION_DURATION_STANDARD, 3068 3069 // ANIMATION_TYPE_DARK 3070 StackStateAnimator.ANIMATION_DURATION_STANDARD, 3071 3072 // ANIMATION_TYPE_GO_TO_FULL_SHADE 3073 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, 3074 3075 // ANIMATION_TYPE_HIDE_SENSITIVE 3076 StackStateAnimator.ANIMATION_DURATION_STANDARD, 3077 3078 // ANIMATION_TYPE_VIEW_RESIZE 3079 StackStateAnimator.ANIMATION_DURATION_STANDARD, 3080 3081 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED 3082 StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED, 3083 3084 // ANIMATION_TYPE_HEADS_UP_APPEAR 3085 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR, 3086 3087 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR 3088 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR, 3089 3090 // ANIMATION_TYPE_HEADS_UP_OTHER 3091 StackStateAnimator.ANIMATION_DURATION_STANDARD, 3092 3093 // ANIMATION_TYPE_EVERYTHING 3094 StackStateAnimator.ANIMATION_DURATION_STANDARD, 3095 }; 3096 3097 static final int ANIMATION_TYPE_ADD = 0; 3098 static final int ANIMATION_TYPE_REMOVE = 1; 3099 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2; 3100 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3; 3101 static final int ANIMATION_TYPE_START_DRAG = 4; 3102 static final int ANIMATION_TYPE_SNAP_BACK = 5; 3103 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6; 3104 static final int ANIMATION_TYPE_DIMMED = 7; 3105 static final int ANIMATION_TYPE_CHANGE_POSITION = 8; 3106 static final int ANIMATION_TYPE_DARK = 9; 3107 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10; 3108 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11; 3109 static final int ANIMATION_TYPE_VIEW_RESIZE = 12; 3110 static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13; 3111 static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14; 3112 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15; 3113 static final int ANIMATION_TYPE_HEADS_UP_OTHER = 16; 3114 static final int ANIMATION_TYPE_EVERYTHING = 17; 3115 3116 static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1; 3117 static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2; 3118 3119 final long eventStartTime; 3120 final View changingView; 3121 final int animationType; 3122 final AnimationFilter filter; 3123 final long length; 3124 View viewAfterChangingView; 3125 int darkAnimationOriginIndex; 3126 boolean headsUpFromBottom; 3127 AnimationEvent(View view, int type)3128 AnimationEvent(View view, int type) { 3129 this(view, type, LENGTHS[type]); 3130 } 3131 AnimationEvent(View view, int type, long length)3132 AnimationEvent(View view, int type, long length) { 3133 eventStartTime = AnimationUtils.currentAnimationTimeMillis(); 3134 changingView = view; 3135 animationType = type; 3136 filter = FILTERS[type]; 3137 this.length = length; 3138 } 3139 3140 /** 3141 * Combines the length of several animation events into a single value. 3142 * 3143 * @param events The events of the lengths to combine. 3144 * @return The combined length. Depending on the event types, this might be the maximum of 3145 * all events or the length of a specific event. 3146 */ combineLength(ArrayList<AnimationEvent> events)3147 static long combineLength(ArrayList<AnimationEvent> events) { 3148 long length = 0; 3149 int size = events.size(); 3150 for (int i = 0; i < size; i++) { 3151 AnimationEvent event = events.get(i); 3152 length = Math.max(length, event.length); 3153 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) { 3154 return event.length; 3155 } 3156 } 3157 return length; 3158 } 3159 } 3160 3161 } 3162