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.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ObjectAnimator; 22 import android.animation.PropertyValuesHolder; 23 import android.animation.TimeAnimator; 24 import android.animation.ValueAnimator; 25 import android.animation.ValueAnimator.AnimatorUpdateListener; 26 import android.annotation.FloatRange; 27 import android.annotation.Nullable; 28 import android.content.Context; 29 import android.content.res.Configuration; 30 import android.graphics.Canvas; 31 import android.graphics.Color; 32 import android.graphics.Paint; 33 import android.graphics.PointF; 34 import android.graphics.PorterDuff; 35 import android.graphics.PorterDuffXfermode; 36 import android.graphics.Rect; 37 import android.os.Bundle; 38 import android.os.Handler; 39 import android.util.AttributeSet; 40 import android.util.FloatProperty; 41 import android.util.Log; 42 import android.util.Pair; 43 import android.util.Property; 44 import android.view.MotionEvent; 45 import android.view.VelocityTracker; 46 import android.view.View; 47 import android.view.ViewConfiguration; 48 import android.view.ViewGroup; 49 import android.view.ViewTreeObserver; 50 import android.view.WindowInsets; 51 import android.view.accessibility.AccessibilityEvent; 52 import android.view.accessibility.AccessibilityNodeInfo; 53 import android.view.animation.AnimationUtils; 54 import android.view.animation.Interpolator; 55 import android.widget.OverScroller; 56 import android.widget.ScrollView; 57 58 import com.android.internal.logging.MetricsLogger; 59 import com.android.internal.logging.MetricsProto.MetricsEvent; 60 import com.android.systemui.ExpandHelper; 61 import com.android.systemui.Interpolators; 62 import com.android.systemui.R; 63 import com.android.systemui.SwipeHelper; 64 import com.android.systemui.classifier.FalsingManager; 65 import com.android.systemui.statusbar.ActivatableNotificationView; 66 import com.android.systemui.statusbar.DismissView; 67 import com.android.systemui.statusbar.EmptyShadeView; 68 import com.android.systemui.statusbar.ExpandableNotificationRow; 69 import com.android.systemui.statusbar.ExpandableView; 70 import com.android.systemui.statusbar.NotificationGuts; 71 import com.android.systemui.statusbar.NotificationOverflowContainer; 72 import com.android.systemui.statusbar.NotificationSettingsIconRow; 73 import com.android.systemui.statusbar.NotificationSettingsIconRow.SettingsIconRowListener; 74 import com.android.systemui.statusbar.StackScrollerDecorView; 75 import com.android.systemui.statusbar.StatusBarState; 76 import com.android.systemui.statusbar.notification.FakeShadowView; 77 import com.android.systemui.statusbar.notification.NotificationUtils; 78 import com.android.systemui.statusbar.phone.NotificationGroupManager; 79 import com.android.systemui.statusbar.phone.PhoneStatusBar; 80 import com.android.systemui.statusbar.phone.ScrimController; 81 import com.android.systemui.statusbar.policy.HeadsUpManager; 82 import com.android.systemui.statusbar.policy.ScrollAdapter; 83 84 import java.util.ArrayList; 85 import java.util.Collections; 86 import java.util.Comparator; 87 import java.util.HashSet; 88 89 /** 90 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack. 91 */ 92 public class NotificationStackScrollLayout extends ViewGroup 93 implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter, 94 ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener, 95 SettingsIconRowListener, ScrollContainer { 96 97 public static final float BACKGROUND_ALPHA_DIMMED = 0.7f; 98 private static final String TAG = "StackScroller"; 99 private static final boolean DEBUG = false; 100 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f; 101 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f; 102 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f; 103 /** 104 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}. 105 */ 106 private static final int INVALID_POINTER = -1; 107 108 private ExpandHelper mExpandHelper; 109 private NotificationSwipeHelper mSwipeHelper; 110 private boolean mSwipingInProgress; 111 private int mCurrentStackHeight = Integer.MAX_VALUE; 112 private final Paint mBackgroundPaint = new Paint(); 113 114 /** 115 * mCurrentStackHeight is the actual stack height, mLastSetStackHeight is the stack height set 116 * externally from {@link #setStackHeight} 117 */ 118 private float mLastSetStackHeight; 119 private int mOwnScrollY; 120 private int mMaxLayoutHeight; 121 122 private VelocityTracker mVelocityTracker; 123 private OverScroller mScroller; 124 private Runnable mFinishScrollingCallback; 125 private int mTouchSlop; 126 private int mMinimumVelocity; 127 private int mMaximumVelocity; 128 private int mOverflingDistance; 129 private float mMaxOverScroll; 130 private boolean mIsBeingDragged; 131 private int mLastMotionY; 132 private int mDownX; 133 private int mActivePointerId; 134 private boolean mTouchIsClick; 135 private float mInitialTouchX; 136 private float mInitialTouchY; 137 138 private Paint mDebugPaint; 139 private int mContentHeight; 140 private int mCollapsedSize; 141 private int mBottomStackSlowDownHeight; 142 private int mBottomStackPeekSize; 143 private int mPaddingBetweenElements; 144 private int mIncreasedPaddingBetweenElements; 145 private int mTopPadding; 146 private int mBottomInset = 0; 147 148 /** 149 * The algorithm which calculates the properties for our children 150 */ 151 private final StackScrollAlgorithm mStackScrollAlgorithm; 152 153 /** 154 * The current State this Layout is in 155 */ 156 private StackScrollState mCurrentStackScrollState = new StackScrollState(this); 157 private AmbientState mAmbientState = new AmbientState(); 158 private NotificationGroupManager mGroupManager; 159 private HashSet<View> mChildrenToAddAnimated = new HashSet<>(); 160 private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>(); 161 private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>(); 162 private ArrayList<View> mSnappedBackChildren = new ArrayList<>(); 163 private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>(); 164 private ArrayList<View> mChildrenChangingPositions = new ArrayList<>(); 165 private HashSet<View> mFromMoreCardAdditions = new HashSet<>(); 166 private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>(); 167 private ArrayList<View> mSwipedOutViews = new ArrayList<>(); 168 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this); 169 private boolean mAnimationsEnabled; 170 private boolean mChangePositionInProgress; 171 private boolean mChildTransferInProgress; 172 173 /** 174 * The raw amount of the overScroll on the top, which is not rubber-banded. 175 */ 176 private float mOverScrolledTopPixels; 177 178 /** 179 * The raw amount of the overScroll on the bottom, which is not rubber-banded. 180 */ 181 private float mOverScrolledBottomPixels; 182 private OnChildLocationsChangedListener mListener; 183 private OnOverscrollTopChangedListener mOverscrollTopChangedListener; 184 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener; 185 private OnEmptySpaceClickListener mOnEmptySpaceClickListener; 186 private boolean mNeedsAnimation; 187 private boolean mTopPaddingNeedsAnimation; 188 private boolean mDimmedNeedsAnimation; 189 private boolean mHideSensitiveNeedsAnimation; 190 private boolean mDarkNeedsAnimation; 191 private int mDarkAnimationOriginIndex; 192 private boolean mActivateNeedsAnimation; 193 private boolean mGoToFullShadeNeedsAnimation; 194 private boolean mIsExpanded = true; 195 private boolean mChildrenUpdateRequested; 196 private boolean mIsExpansionChanging; 197 private boolean mPanelTracking; 198 private boolean mExpandingNotification; 199 private boolean mExpandedInThisMotion; 200 private boolean mScrollingEnabled; 201 private DismissView mDismissView; 202 private EmptyShadeView mEmptyShadeView; 203 private boolean mDismissAllInProgress; 204 205 /** 206 * Was the scroller scrolled to the top when the down motion was observed? 207 */ 208 private boolean mScrolledToTopOnFirstDown; 209 /** 210 * The minimal amount of over scroll which is needed in order to switch to the quick settings 211 * when over scrolling on a expanded card. 212 */ 213 private float mMinTopOverScrollToEscape; 214 private int mIntrinsicPadding; 215 private float mStackTranslation; 216 private float mTopPaddingOverflow; 217 private boolean mDontReportNextOverScroll; 218 private boolean mDontClampNextScroll; 219 private boolean mRequestViewResizeAnimationOnLayout; 220 private boolean mNeedViewResizeAnimation; 221 private View mExpandedGroupView; 222 private boolean mEverythingNeedsAnimation; 223 224 /** 225 * The maximum scrollPosition which we are allowed to reach when a notification was expanded. 226 * This is needed to avoid scrolling too far after the notification was collapsed in the same 227 * motion. 228 */ 229 private int mMaxScrollAfterExpand; 230 private SwipeHelper.LongPressListener mLongPressListener; 231 232 private NotificationSettingsIconRow mCurrIconRow; 233 private View mTranslatingParentView; 234 private View mGearExposedView; 235 236 /** 237 * Should in this touch motion only be scrolling allowed? It's true when the scroller was 238 * animating. 239 */ 240 private boolean mOnlyScrollingInThisMotion; 241 private boolean mDisallowDismissInThisMotion; 242 private boolean mInterceptDelegateEnabled; 243 private boolean mDelegateToScrollView; 244 private boolean mDisallowScrollingInThisMotion; 245 private long mGoToFullShadeDelay; 246 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater 247 = new ViewTreeObserver.OnPreDrawListener() { 248 @Override 249 public boolean onPreDraw() { 250 updateForcedScroll(); 251 updateChildren(); 252 mChildrenUpdateRequested = false; 253 getViewTreeObserver().removeOnPreDrawListener(this); 254 return true; 255 } 256 }; 257 private PhoneStatusBar mPhoneStatusBar; 258 private int[] mTempInt2 = new int[2]; 259 private boolean mGenerateChildOrderChangedEvent; 260 private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>(); 261 private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>(); 262 private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations 263 = new HashSet<>(); 264 private HeadsUpManager mHeadsUpManager; 265 private boolean mTrackingHeadsUp; 266 private ScrimController mScrimController; 267 private boolean mForceNoOverlappingRendering; 268 private NotificationOverflowContainer mOverflowContainer; 269 private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>(); 270 private FalsingManager mFalsingManager; 271 private boolean mAnimationRunning; 272 private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater 273 = new ViewTreeObserver.OnPreDrawListener() { 274 @Override 275 public boolean onPreDraw() { 276 // if it needs animation 277 if (!mNeedsAnimation && !mChildrenUpdateRequested) { 278 updateBackground(); 279 } 280 return true; 281 } 282 }; 283 private Rect mBackgroundBounds = new Rect(); 284 private Rect mStartAnimationRect = new Rect(); 285 private Rect mEndAnimationRect = new Rect(); 286 private Rect mCurrentBounds = new Rect(-1, -1, -1, -1); 287 private boolean mAnimateNextBackgroundBottom; 288 private boolean mAnimateNextBackgroundTop; 289 private ObjectAnimator mBottomAnimator = null; 290 private ObjectAnimator mTopAnimator = null; 291 private ActivatableNotificationView mFirstVisibleBackgroundChild = null; 292 private ActivatableNotificationView mLastVisibleBackgroundChild = null; 293 private int mBgColor; 294 private float mDimAmount; 295 private ValueAnimator mDimAnimator; 296 private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>(); 297 private Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() { 298 @Override 299 public void onAnimationEnd(Animator animation) { 300 mDimAnimator = null; 301 } 302 }; 303 private ValueAnimator.AnimatorUpdateListener mDimUpdateListener 304 = new ValueAnimator.AnimatorUpdateListener() { 305 306 @Override 307 public void onAnimationUpdate(ValueAnimator animation) { 308 setDimAmount((Float) animation.getAnimatedValue()); 309 } 310 }; 311 protected ViewGroup mQsContainer; 312 private boolean mContinuousShadowUpdate; 313 private ViewTreeObserver.OnPreDrawListener mShadowUpdater 314 = new ViewTreeObserver.OnPreDrawListener() { 315 316 @Override 317 public boolean onPreDraw() { 318 updateViewShadows(); 319 return true; 320 } 321 }; 322 private Comparator<ExpandableView> mViewPositionComparator = new Comparator<ExpandableView>() { 323 @Override 324 public int compare(ExpandableView view, ExpandableView otherView) { 325 float endY = view.getTranslationY() + view.getActualHeight(); 326 float otherEndY = otherView.getTranslationY() + otherView.getActualHeight(); 327 if (endY < otherEndY) { 328 return -1; 329 } else if (endY > otherEndY) { 330 return 1; 331 } else { 332 // The two notifications end at the same location 333 return 0; 334 } 335 } 336 }; 337 private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC); 338 private boolean mPulsing; 339 private boolean mDrawBackgroundAsSrc; 340 private boolean mFadingOut; 341 private boolean mParentFadingOut; 342 private boolean mGroupExpandedForMeasure; 343 private boolean mScrollable; 344 private View mForcedScroll; 345 private float mBackgroundFadeAmount = 1.0f; 346 private static final Property<NotificationStackScrollLayout, Float> BACKGROUND_FADE = 347 new FloatProperty<NotificationStackScrollLayout>("backgroundFade") { 348 @Override 349 public void setValue(NotificationStackScrollLayout object, float value) { 350 object.setBackgroundFadeAmount(value); 351 } 352 353 @Override 354 public Float get(NotificationStackScrollLayout object) { 355 return object.getBackgroundFadeAmount(); 356 } 357 }; 358 359 public NotificationStackScrollLayout(Context context) { 360 this(context, null); 361 } 362 363 public NotificationStackScrollLayout(Context context, AttributeSet attrs) { 364 this(context, attrs, 0); 365 } 366 367 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) { 368 this(context, attrs, defStyleAttr, 0); 369 } 370 371 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr, 372 int defStyleRes) { 373 super(context, attrs, defStyleAttr, defStyleRes); 374 mBgColor = context.getColor(R.color.notification_shade_background_color); 375 int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); 376 int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height); 377 mExpandHelper = new ExpandHelper(getContext(), this, 378 minHeight, maxHeight); 379 mExpandHelper.setEventSource(this); 380 mExpandHelper.setScrollAdapter(this); 381 mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext()); 382 mSwipeHelper.setLongPressListener(mLongPressListener); 383 mStackScrollAlgorithm = new StackScrollAlgorithm(context); 384 initView(context); 385 setWillNotDraw(false); 386 if (DEBUG) { 387 mDebugPaint = new Paint(); 388 mDebugPaint.setColor(0xffff0000); 389 mDebugPaint.setStrokeWidth(2); 390 mDebugPaint.setStyle(Paint.Style.STROKE); 391 } 392 mFalsingManager = FalsingManager.getInstance(context); 393 } 394 395 @Override 396 public void onGearTouched(ExpandableNotificationRow row, int x, int y) { 397 if (mLongPressListener != null) { 398 MetricsLogger.action(mContext, MetricsEvent.ACTION_TOUCH_GEAR, 399 row.getStatusBarNotification().getPackageName()); 400 mLongPressListener.onLongPress(row, x, y); 401 } 402 } 403 404 @Override 405 public void onSettingsIconRowReset(ExpandableNotificationRow row) { 406 if (mTranslatingParentView != null && row == mTranslatingParentView) { 407 mSwipeHelper.setSnappedToGear(false); 408 mGearExposedView = null; 409 mTranslatingParentView = null; 410 } 411 } 412 413 @Override 414 protected void onDraw(Canvas canvas) { 415 if (mCurrentBounds.top < mCurrentBounds.bottom) { 416 canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom, 417 mBackgroundPaint); 418 } 419 if (DEBUG) { 420 int y = mTopPadding; 421 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 422 y = (int) (getLayoutHeight() - mBottomStackPeekSize 423 - mBottomStackSlowDownHeight); 424 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 425 y = (int) (getLayoutHeight() - mBottomStackPeekSize); 426 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 427 y = (int) getLayoutHeight(); 428 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 429 y = getHeight() - getEmptyBottomMargin(); 430 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 431 } 432 } 433 434 private void updateBackgroundDimming() { 435 float alpha = BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount); 436 alpha *= mBackgroundFadeAmount; 437 // We need to manually blend in the background color 438 int scrimColor = mScrimController.getScrimBehindColor(); 439 // SRC_OVER blending Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc 440 float alphaInv = 1 - alpha; 441 int color = Color.argb((int) (alpha * 255 + alphaInv * Color.alpha(scrimColor)), 442 (int) (mBackgroundFadeAmount * Color.red(mBgColor) 443 + alphaInv * Color.red(scrimColor)), 444 (int) (mBackgroundFadeAmount * Color.green(mBgColor) 445 + alphaInv * Color.green(scrimColor)), 446 (int) (mBackgroundFadeAmount * Color.blue(mBgColor) 447 + alphaInv * Color.blue(scrimColor))); 448 mBackgroundPaint.setColor(color); 449 invalidate(); 450 } 451 452 private void initView(Context context) { 453 mScroller = new OverScroller(getContext()); 454 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 455 setClipChildren(false); 456 final ViewConfiguration configuration = ViewConfiguration.get(context); 457 mTouchSlop = configuration.getScaledTouchSlop(); 458 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 459 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 460 mOverflingDistance = configuration.getScaledOverflingDistance(); 461 mCollapsedSize = context.getResources() 462 .getDimensionPixelSize(R.dimen.notification_min_height); 463 mBottomStackPeekSize = context.getResources() 464 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount); 465 mStackScrollAlgorithm.initView(context); 466 mPaddingBetweenElements = Math.max(1, context.getResources() 467 .getDimensionPixelSize(R.dimen.notification_divider_height)); 468 mIncreasedPaddingBetweenElements = context.getResources() 469 .getDimensionPixelSize(R.dimen.notification_divider_height_increased); 470 mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength(); 471 mMinTopOverScrollToEscape = getResources().getDimensionPixelSize( 472 R.dimen.min_top_overscroll_to_qs); 473 } 474 475 public void setDrawBackgroundAsSrc(boolean asSrc) { 476 mDrawBackgroundAsSrc = asSrc; 477 updateSrcDrawing(); 478 } 479 480 private void updateSrcDrawing() { 481 mBackgroundPaint.setXfermode(mDrawBackgroundAsSrc && (!mFadingOut && !mParentFadingOut) 482 ? mSrcMode : null); 483 invalidate(); 484 } 485 486 private void notifyHeightChangeListener(ExpandableView view) { 487 if (mOnHeightChangedListener != null) { 488 mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */); 489 } 490 } 491 492 @Override 493 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 494 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 495 // We need to measure all children even the GONE ones, such that the heights are calculated 496 // correctly as they are used to calculate how many we can fit on the screen. 497 final int size = getChildCount(); 498 for (int i = 0; i < size; i++) { 499 measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec); 500 } 501 } 502 503 @Override 504 protected void onLayout(boolean changed, int l, int t, int r, int b) { 505 // we layout all our children centered on the top 506 float centerX = getWidth() / 2.0f; 507 for (int i = 0; i < getChildCount(); i++) { 508 View child = getChildAt(i); 509 // We need to layout all children even the GONE ones, such that the heights are 510 // calculated correctly as they are used to calculate how many we can fit on the screen 511 float width = child.getMeasuredWidth(); 512 float height = child.getMeasuredHeight(); 513 child.layout((int) (centerX - width / 2.0f), 514 0, 515 (int) (centerX + width / 2.0f), 516 (int) height); 517 } 518 setMaxLayoutHeight(getHeight()); 519 updateContentHeight(); 520 clampScrollPosition(); 521 if (mRequestViewResizeAnimationOnLayout) { 522 requestAnimationOnViewResize(null); 523 mRequestViewResizeAnimationOnLayout = false; 524 } 525 requestChildrenUpdate(); 526 updateFirstAndLastBackgroundViews(); 527 } 528 529 private void requestAnimationOnViewResize(ExpandableNotificationRow row) { 530 if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) { 531 mNeedViewResizeAnimation = true; 532 mNeedsAnimation = true; 533 } 534 } 535 536 public void updateSpeedBumpIndex(int newIndex) { 537 mAmbientState.setSpeedBumpIndex(newIndex); 538 } 539 540 public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) { 541 mListener = listener; 542 } 543 544 /** 545 * Returns the location the given child is currently rendered at. 546 * 547 * @param child the child to get the location for 548 * @return one of {@link StackViewState}'s <code>LOCATION_*</code> constants 549 */ 550 public int getChildLocation(View child) { 551 StackViewState childViewState = mCurrentStackScrollState.getViewStateForView(child); 552 if (childViewState == null) { 553 return StackViewState.LOCATION_UNKNOWN; 554 } 555 if (childViewState.gone) { 556 return StackViewState.LOCATION_GONE; 557 } 558 return childViewState.location; 559 } 560 561 private void setMaxLayoutHeight(int maxLayoutHeight) { 562 mMaxLayoutHeight = maxLayoutHeight; 563 updateAlgorithmHeightAndPadding(); 564 } 565 566 private void updateAlgorithmHeightAndPadding() { 567 mAmbientState.setLayoutHeight(getLayoutHeight()); 568 mAmbientState.setTopPadding(mTopPadding); 569 } 570 571 /** 572 * Updates the children views according to the stack scroll algorithm. Call this whenever 573 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout. 574 */ 575 private void updateChildren() { 576 updateScrollStateForAddedChildren(); 577 mAmbientState.setScrollY(mOwnScrollY); 578 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState); 579 if (!isCurrentlyAnimating() && !mNeedsAnimation) { 580 applyCurrentState(); 581 } else { 582 startAnimationToState(); 583 } 584 } 585 586 private void updateScrollStateForAddedChildren() { 587 if (mChildrenToAddAnimated.isEmpty()) { 588 return; 589 } 590 for (int i = 0; i < getChildCount(); i++) { 591 ExpandableView child = (ExpandableView) getChildAt(i); 592 if (mChildrenToAddAnimated.contains(child)) { 593 int startingPosition = getPositionInLinearLayout(child); 594 int padding = child.getIncreasedPaddingAmount() == 1.0f 595 ? mIncreasedPaddingBetweenElements : 596 mPaddingBetweenElements; 597 int childHeight = getIntrinsicHeight(child) + padding; 598 if (startingPosition < mOwnScrollY) { 599 // This child starts off screen, so let's keep it offscreen to keep the others visible 600 601 mOwnScrollY += childHeight; 602 } 603 } 604 } 605 clampScrollPosition(); 606 } 607 608 private void updateForcedScroll() { 609 if (mForcedScroll != null && (!mForcedScroll.hasFocus() 610 || !mForcedScroll.isAttachedToWindow())) { 611 mForcedScroll = null; 612 } 613 if (mForcedScroll != null) { 614 ExpandableView expandableView = (ExpandableView) mForcedScroll; 615 int positionInLinearLayout = getPositionInLinearLayout(expandableView); 616 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout); 617 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight(); 618 619 targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange())); 620 621 // Only apply the scroll if we're scrolling the view upwards, or the view is so far up 622 // that it is not visible anymore. 623 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) { 624 mOwnScrollY = targetScroll; 625 } 626 } 627 } 628 629 private void requestChildrenUpdate() { 630 if (!mChildrenUpdateRequested) { 631 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater); 632 mChildrenUpdateRequested = true; 633 invalidate(); 634 } 635 } 636 637 private boolean isCurrentlyAnimating() { 638 return mStateAnimator.isRunning(); 639 } 640 641 private void clampScrollPosition() { 642 int scrollRange = getScrollRange(); 643 if (scrollRange < mOwnScrollY) { 644 mOwnScrollY = scrollRange; 645 } 646 } 647 648 public int getTopPadding() { 649 return mTopPadding; 650 } 651 652 private void setTopPadding(int topPadding, boolean animate) { 653 if (mTopPadding != topPadding) { 654 mTopPadding = topPadding; 655 updateAlgorithmHeightAndPadding(); 656 updateContentHeight(); 657 if (animate && mAnimationsEnabled && mIsExpanded) { 658 mTopPaddingNeedsAnimation = true; 659 mNeedsAnimation = true; 660 } 661 requestChildrenUpdate(); 662 notifyHeightChangeListener(null); 663 } 664 } 665 666 /** 667 * Update the height of the stack to a new height. 668 * 669 * @param height the new height of the stack 670 */ 671 public void setStackHeight(float height) { 672 mLastSetStackHeight = height; 673 setIsExpanded(height > 0.0f); 674 int stackHeight; 675 float translationY; 676 float appearEndPosition = getAppearEndPosition(); 677 float appearStartPosition = getAppearStartPosition(); 678 if (height >= appearEndPosition) { 679 translationY = mTopPaddingOverflow; 680 stackHeight = (int) height; 681 } else { 682 float appearFraction = getAppearFraction(height); 683 if (appearFraction >= 0) { 684 translationY = NotificationUtils.interpolate(getExpandTranslationStart(), 0, 685 appearFraction); 686 } else { 687 // This may happen when pushing up a heads up. We linearly push it up from the 688 // start 689 translationY = height - appearStartPosition + getExpandTranslationStart(); 690 } 691 stackHeight = (int) (height - translationY); 692 } 693 if (stackHeight != mCurrentStackHeight) { 694 mCurrentStackHeight = stackHeight; 695 updateAlgorithmHeightAndPadding(); 696 requestChildrenUpdate(); 697 } 698 setStackTranslation(translationY); 699 } 700 701 /** 702 * @return The translation at the beginning when expanding. 703 * Measured relative to the resting position. 704 */ 705 private float getExpandTranslationStart() { 706 int startPosition = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp() 707 ? 0 : -getFirstChildIntrinsicHeight(); 708 return startPosition - mTopPadding; 709 } 710 711 /** 712 * @return the position from where the appear transition starts when expanding. 713 * Measured in absolute height. 714 */ 715 private float getAppearStartPosition() { 716 return mTrackingHeadsUp 717 ? mHeadsUpManager.getTopHeadsUpPinnedHeight() 718 : 0; 719 } 720 721 /** 722 * @return the position from where the appear transition ends when expanding. 723 * Measured in absolute height. 724 */ 725 private float getAppearEndPosition() { 726 int firstItemHeight = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp() 727 ? mHeadsUpManager.getTopHeadsUpPinnedHeight() + mBottomStackPeekSize 728 + mBottomStackSlowDownHeight 729 : getLayoutMinHeight(); 730 return firstItemHeight + mTopPadding + mTopPaddingOverflow; 731 } 732 733 /** 734 * @param height the height of the panel 735 * @return the fraction of the appear animation that has been performed 736 */ 737 public float getAppearFraction(float height) { 738 float appearEndPosition = getAppearEndPosition(); 739 float appearStartPosition = getAppearStartPosition(); 740 return (height - appearStartPosition) 741 / (appearEndPosition - appearStartPosition); 742 } 743 744 public float getStackTranslation() { 745 return mStackTranslation; 746 } 747 748 private void setStackTranslation(float stackTranslation) { 749 if (stackTranslation != mStackTranslation) { 750 mStackTranslation = stackTranslation; 751 mAmbientState.setStackTranslation(stackTranslation); 752 requestChildrenUpdate(); 753 } 754 } 755 756 /** 757 * Get the current height of the view. This is at most the msize of the view given by a the 758 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight} 759 * 760 * @return either the layout height or the externally defined height, whichever is smaller 761 */ 762 private int getLayoutHeight() { 763 return Math.min(mMaxLayoutHeight, mCurrentStackHeight); 764 } 765 766 public int getFirstItemMinHeight() { 767 final ExpandableView firstChild = getFirstChildNotGone(); 768 return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize; 769 } 770 771 public int getBottomStackPeekSize() { 772 return mBottomStackPeekSize; 773 } 774 775 public int getBottomStackSlowDownHeight() { 776 return mBottomStackSlowDownHeight; 777 } 778 779 public void setLongPressListener(SwipeHelper.LongPressListener listener) { 780 mSwipeHelper.setLongPressListener(listener); 781 mLongPressListener = listener; 782 } 783 784 public void setQsContainer(ViewGroup qsContainer) { 785 mQsContainer = qsContainer; 786 } 787 788 @Override 789 public void onChildDismissed(View v) { 790 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 791 if (!row.isDismissed()) { 792 handleChildDismissed(v); 793 } 794 ViewGroup transientContainer = row.getTransientContainer(); 795 if (transientContainer != null) { 796 transientContainer.removeTransientView(v); 797 } 798 } 799 800 private void handleChildDismissed(View v) { 801 if (mDismissAllInProgress) { 802 return; 803 } 804 setSwipingInProgress(false); 805 if (mDragAnimPendingChildren.contains(v)) { 806 // We start the swipe and finish it in the same frame, we don't want any animation 807 // for the drag 808 mDragAnimPendingChildren.remove(v); 809 } 810 mSwipedOutViews.add(v); 811 mAmbientState.onDragFinished(v); 812 updateContinuousShadowDrawing(); 813 if (v instanceof ExpandableNotificationRow) { 814 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 815 if (row.isHeadsUp()) { 816 mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey()); 817 } 818 } 819 performDismiss(v, mGroupManager, false /* fromAccessibility */); 820 821 mFalsingManager.onNotificationDismissed(); 822 if (mFalsingManager.shouldEnforceBouncer()) { 823 mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */, 824 false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */); 825 } 826 } 827 828 public static void performDismiss(View v, NotificationGroupManager groupManager, 829 boolean fromAccessibility) { 830 if (!(v instanceof ExpandableNotificationRow)) { 831 return; 832 } 833 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 834 if (groupManager.isOnlyChildInGroup(row.getStatusBarNotification())) { 835 ExpandableNotificationRow groupSummary = 836 groupManager.getLogicalGroupSummary(row.getStatusBarNotification()); 837 if (groupSummary.isClearable()) { 838 performDismiss(groupSummary, groupManager, fromAccessibility); 839 } 840 } 841 row.setDismissed(true, fromAccessibility); 842 if (row.isClearable()) { 843 row.performDismiss(); 844 } 845 if (DEBUG) Log.v(TAG, "onChildDismissed: " + v); 846 } 847 848 @Override 849 public void onChildSnappedBack(View animView, float targetLeft) { 850 mAmbientState.onDragFinished(animView); 851 updateContinuousShadowDrawing(); 852 if (!mDragAnimPendingChildren.contains(animView)) { 853 if (mAnimationsEnabled) { 854 mSnappedBackChildren.add(animView); 855 mNeedsAnimation = true; 856 } 857 requestChildrenUpdate(); 858 } else { 859 // We start the swipe and snap back in the same frame, we don't want any animation 860 mDragAnimPendingChildren.remove(animView); 861 } 862 if (mCurrIconRow != null && targetLeft == 0) { 863 mCurrIconRow.resetState(); 864 mCurrIconRow = null; 865 } 866 } 867 868 @Override 869 public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { 870 if (!mIsExpanded && isPinnedHeadsUp(animView) && canChildBeDismissed(animView)) { 871 mScrimController.setTopHeadsUpDragAmount(animView, 872 Math.min(Math.abs(swipeProgress / 2f - 1.0f), 1.0f)); 873 } 874 return true; // Don't fade out the notification 875 } 876 877 @Override 878 public void onBeginDrag(View v) { 879 mFalsingManager.onNotificatonStartDismissing(); 880 setSwipingInProgress(true); 881 mAmbientState.onBeginDrag(v); 882 updateContinuousShadowDrawing(); 883 if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) { 884 mDragAnimPendingChildren.add(v); 885 mNeedsAnimation = true; 886 } 887 requestChildrenUpdate(); 888 } 889 890 public static boolean isPinnedHeadsUp(View v) { 891 if (v instanceof ExpandableNotificationRow) { 892 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 893 return row.isHeadsUp() && row.isPinned(); 894 } 895 return false; 896 } 897 898 private boolean isHeadsUp(View v) { 899 if (v instanceof ExpandableNotificationRow) { 900 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 901 return row.isHeadsUp(); 902 } 903 return false; 904 } 905 906 @Override 907 public void onDragCancelled(View v) { 908 mFalsingManager.onNotificatonStopDismissing(); 909 setSwipingInProgress(false); 910 } 911 912 @Override 913 public float getFalsingThresholdFactor() { 914 return mPhoneStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f; 915 } 916 917 @Override 918 public View getChildAtPosition(MotionEvent ev) { 919 View child = getChildAtPosition(ev.getX(), ev.getY()); 920 if (child instanceof ExpandableNotificationRow) { 921 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 922 ExpandableNotificationRow parent = row.getNotificationParent(); 923 if (parent != null && parent.areChildrenExpanded() 924 && (parent.areGutsExposed() 925 || mGearExposedView == parent 926 || (parent.getNotificationChildren().size() == 1 927 && parent.isClearable()))) { 928 // In this case the group is expanded and showing the gear for the 929 // group, further interaction should apply to the group, not any 930 // child notifications so we use the parent of the child. We also do the same 931 // if we only have a single child. 932 child = parent; 933 } 934 } 935 return child; 936 } 937 938 public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) { 939 getLocationOnScreen(mTempInt2); 940 float localTouchY = touchY - mTempInt2[1]; 941 942 ExpandableView closestChild = null; 943 float minDist = Float.MAX_VALUE; 944 945 // find the view closest to the location, accounting for GONE views 946 final int count = getChildCount(); 947 for (int childIdx = 0; childIdx < count; childIdx++) { 948 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx); 949 if (slidingChild.getVisibility() == GONE 950 || slidingChild instanceof StackScrollerDecorView) { 951 continue; 952 } 953 float childTop = slidingChild.getTranslationY(); 954 float top = childTop + slidingChild.getClipTopAmount(); 955 float bottom = childTop + slidingChild.getActualHeight(); 956 957 float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY)); 958 if (dist < minDist) { 959 closestChild = slidingChild; 960 minDist = dist; 961 } 962 } 963 return closestChild; 964 } 965 966 @Override 967 public ExpandableView getChildAtRawPosition(float touchX, float touchY) { 968 getLocationOnScreen(mTempInt2); 969 return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]); 970 } 971 972 @Override 973 public ExpandableView getChildAtPosition(float touchX, float touchY) { 974 // find the view under the pointer, accounting for GONE views 975 final int count = getChildCount(); 976 for (int childIdx = 0; childIdx < count; childIdx++) { 977 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx); 978 if (slidingChild.getVisibility() == GONE 979 || slidingChild instanceof StackScrollerDecorView) { 980 continue; 981 } 982 float childTop = slidingChild.getTranslationY(); 983 float top = childTop + slidingChild.getClipTopAmount(); 984 float bottom = childTop + slidingChild.getActualHeight(); 985 986 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and 987 // camera affordance). 988 int left = 0; 989 int right = getWidth(); 990 991 if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) { 992 if (slidingChild instanceof ExpandableNotificationRow) { 993 ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild; 994 if (!mIsExpanded && row.isHeadsUp() && row.isPinned() 995 && mHeadsUpManager.getTopEntry().entry.row != row 996 && mGroupManager.getGroupSummary( 997 mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification()) 998 != row) { 999 continue; 1000 } 1001 return row.getViewAtPosition(touchY - childTop); 1002 } 1003 return slidingChild; 1004 } 1005 } 1006 return null; 1007 } 1008 1009 @Override 1010 public boolean canChildBeExpanded(View v) { 1011 return v instanceof ExpandableNotificationRow 1012 && ((ExpandableNotificationRow) v).isExpandable() 1013 && !((ExpandableNotificationRow) v).areGutsExposed() 1014 && (mIsExpanded || !((ExpandableNotificationRow) v).isPinned()); 1015 } 1016 1017 /* Only ever called as a consequence of an expansion gesture in the shade. */ 1018 @Override 1019 public void setUserExpandedChild(View v, boolean userExpanded) { 1020 if (v instanceof ExpandableNotificationRow) { 1021 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 1022 row.setUserExpanded(userExpanded, true /* allowChildrenExpansion */); 1023 row.onExpandedByGesture(userExpanded); 1024 } 1025 } 1026 1027 @Override 1028 public void setExpansionCancelled(View v) { 1029 if (v instanceof ExpandableNotificationRow) { 1030 ((ExpandableNotificationRow) v).setGroupExpansionChanging(false); 1031 } 1032 } 1033 1034 @Override 1035 public void setUserLockedChild(View v, boolean userLocked) { 1036 if (v instanceof ExpandableNotificationRow) { 1037 ((ExpandableNotificationRow) v).setUserLocked(userLocked); 1038 } 1039 removeLongPressCallback(); 1040 requestDisallowInterceptTouchEvent(true); 1041 } 1042 1043 @Override 1044 public void expansionStateChanged(boolean isExpanding) { 1045 mExpandingNotification = isExpanding; 1046 if (!mExpandedInThisMotion) { 1047 mMaxScrollAfterExpand = mOwnScrollY; 1048 mExpandedInThisMotion = true; 1049 } 1050 } 1051 1052 @Override 1053 public int getMaxExpandHeight(ExpandableView view) { 1054 int maxContentHeight = view.getMaxContentHeight(); 1055 if (view.isSummaryWithChildren()) { 1056 // Faking a measure with the group expanded to simulate how the group would look if 1057 // it was. Doing a calculation here would be highly non-trivial because of the 1058 // algorithm 1059 mGroupExpandedForMeasure = true; 1060 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 1061 mGroupManager.toggleGroupExpansion(row.getStatusBarNotification()); 1062 row.setForceUnlocked(true); 1063 mAmbientState.setLayoutHeight(mMaxLayoutHeight); 1064 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState); 1065 mAmbientState.setLayoutHeight(getLayoutHeight()); 1066 mGroupManager.toggleGroupExpansion( 1067 row.getStatusBarNotification()); 1068 mGroupExpandedForMeasure = false; 1069 row.setForceUnlocked(false); 1070 int height = mCurrentStackScrollState.getViewStateForView(view).height; 1071 return Math.min(height, maxContentHeight); 1072 } 1073 return maxContentHeight; 1074 } 1075 1076 public void setScrollingEnabled(boolean enable) { 1077 mScrollingEnabled = enable; 1078 } 1079 1080 @Override 1081 public void lockScrollTo(View v) { 1082 if (mForcedScroll == v) { 1083 return; 1084 } 1085 mForcedScroll = v; 1086 scrollTo(v); 1087 } 1088 1089 @Override 1090 public boolean scrollTo(View v) { 1091 ExpandableView expandableView = (ExpandableView) v; 1092 int positionInLinearLayout = getPositionInLinearLayout(v); 1093 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout); 1094 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight(); 1095 1096 // Only apply the scroll if we're scrolling the view upwards, or the view is so far up 1097 // that it is not visible anymore. 1098 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) { 1099 mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY); 1100 mDontReportNextOverScroll = true; 1101 postInvalidateOnAnimation(); 1102 return true; 1103 } 1104 return false; 1105 } 1106 1107 /** 1108 * @return the scroll necessary to make the bottom edge of {@param v} align with the top of 1109 * the IME. 1110 */ 1111 private int targetScrollForView(ExpandableView v, int positionInLinearLayout) { 1112 return positionInLinearLayout + v.getIntrinsicHeight() + 1113 getImeInset() - getHeight() + getTopPadding(); 1114 } 1115 1116 @Override 1117 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 1118 mBottomInset = insets.getSystemWindowInsetBottom(); 1119 1120 int range = getScrollRange(); 1121 if (mOwnScrollY > range) { 1122 // HACK: We're repeatedly getting staggered insets here while the IME is 1123 // animating away. To work around that we'll wait until things have settled. 1124 removeCallbacks(mReclamp); 1125 postDelayed(mReclamp, 50); 1126 } else if (mForcedScroll != null) { 1127 // The scroll was requested before we got the actual inset - in case we need 1128 // to scroll up some more do so now. 1129 scrollTo(mForcedScroll); 1130 } 1131 return insets; 1132 } 1133 1134 private Runnable mReclamp = new Runnable() { 1135 @Override 1136 public void run() { 1137 int range = getScrollRange(); 1138 mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY); 1139 mDontReportNextOverScroll = true; 1140 mDontClampNextScroll = true; 1141 postInvalidateOnAnimation(); 1142 } 1143 }; 1144 1145 public void setExpandingEnabled(boolean enable) { 1146 mExpandHelper.setEnabled(enable); 1147 } 1148 1149 private boolean isScrollingEnabled() { 1150 return mScrollingEnabled; 1151 } 1152 1153 @Override 1154 public boolean canChildBeDismissed(View v) { 1155 return StackScrollAlgorithm.canChildBeDismissed(v); 1156 } 1157 1158 @Override 1159 public boolean isAntiFalsingNeeded() { 1160 return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD; 1161 } 1162 1163 private void setSwipingInProgress(boolean isSwiped) { 1164 mSwipingInProgress = isSwiped; 1165 if(isSwiped) { 1166 requestDisallowInterceptTouchEvent(true); 1167 } 1168 } 1169 1170 @Override 1171 protected void onConfigurationChanged(Configuration newConfig) { 1172 super.onConfigurationChanged(newConfig); 1173 float densityScale = getResources().getDisplayMetrics().density; 1174 mSwipeHelper.setDensityScale(densityScale); 1175 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); 1176 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); 1177 initView(getContext()); 1178 } 1179 1180 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) { 1181 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration, 1182 true /* isDismissAll */); 1183 } 1184 1185 public void snapViewIfNeeded(ExpandableNotificationRow child) { 1186 boolean animate = mIsExpanded || isPinnedHeadsUp(child); 1187 // If the child is showing the gear to go to settings, snap to that 1188 float targetLeft = child.getSettingsRow().isVisible() ? child.getTranslation() : 0; 1189 mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft); 1190 } 1191 1192 @Override 1193 public boolean onTouchEvent(MotionEvent ev) { 1194 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL 1195 || ev.getActionMasked()== MotionEvent.ACTION_UP; 1196 handleEmptySpaceClick(ev); 1197 boolean expandWantsIt = false; 1198 if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) { 1199 if (isCancelOrUp) { 1200 mExpandHelper.onlyObserveMovements(false); 1201 } 1202 boolean wasExpandingBefore = mExpandingNotification; 1203 expandWantsIt = mExpandHelper.onTouchEvent(ev); 1204 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore 1205 && !mDisallowScrollingInThisMotion) { 1206 dispatchDownEventToScroller(ev); 1207 } 1208 } 1209 boolean scrollerWantsIt = false; 1210 if (mIsExpanded && !mSwipingInProgress && !mExpandingNotification 1211 && !mDisallowScrollingInThisMotion) { 1212 scrollerWantsIt = onScrollTouch(ev); 1213 } 1214 boolean horizontalSwipeWantsIt = false; 1215 if (!mIsBeingDragged 1216 && !mExpandingNotification 1217 && !mExpandedInThisMotion 1218 && !mOnlyScrollingInThisMotion 1219 && !mDisallowDismissInThisMotion) { 1220 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev); 1221 } 1222 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev); 1223 } 1224 1225 private void dispatchDownEventToScroller(MotionEvent ev) { 1226 MotionEvent downEvent = MotionEvent.obtain(ev); 1227 downEvent.setAction(MotionEvent.ACTION_DOWN); 1228 onScrollTouch(downEvent); 1229 downEvent.recycle(); 1230 } 1231 1232 private boolean onScrollTouch(MotionEvent ev) { 1233 if (!isScrollingEnabled()) { 1234 return false; 1235 } 1236 if (ev.getY() < mQsContainer.getBottom()) { 1237 return false; 1238 } 1239 mForcedScroll = null; 1240 initVelocityTrackerIfNotExists(); 1241 mVelocityTracker.addMovement(ev); 1242 1243 final int action = ev.getAction(); 1244 1245 switch (action & MotionEvent.ACTION_MASK) { 1246 case MotionEvent.ACTION_DOWN: { 1247 if (getChildCount() == 0 || !isInContentBounds(ev)) { 1248 return false; 1249 } 1250 boolean isBeingDragged = !mScroller.isFinished(); 1251 setIsBeingDragged(isBeingDragged); 1252 1253 /* 1254 * If being flinged and user touches, stop the fling. isFinished 1255 * will be false if being flinged. 1256 */ 1257 if (!mScroller.isFinished()) { 1258 mScroller.forceFinished(true); 1259 } 1260 1261 // Remember where the motion event started 1262 mLastMotionY = (int) ev.getY(); 1263 mDownX = (int) ev.getX(); 1264 mActivePointerId = ev.getPointerId(0); 1265 break; 1266 } 1267 case MotionEvent.ACTION_MOVE: 1268 final int activePointerIndex = ev.findPointerIndex(mActivePointerId); 1269 if (activePointerIndex == -1) { 1270 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent"); 1271 break; 1272 } 1273 1274 final int y = (int) ev.getY(activePointerIndex); 1275 final int x = (int) ev.getX(activePointerIndex); 1276 int deltaY = mLastMotionY - y; 1277 final int xDiff = Math.abs(x - mDownX); 1278 final int yDiff = Math.abs(deltaY); 1279 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) { 1280 setIsBeingDragged(true); 1281 if (deltaY > 0) { 1282 deltaY -= mTouchSlop; 1283 } else { 1284 deltaY += mTouchSlop; 1285 } 1286 } 1287 if (mIsBeingDragged) { 1288 // Scroll to follow the motion event 1289 mLastMotionY = y; 1290 int range = getScrollRange(); 1291 if (mExpandedInThisMotion) { 1292 range = Math.min(range, mMaxScrollAfterExpand); 1293 } 1294 1295 float scrollAmount; 1296 if (deltaY < 0) { 1297 scrollAmount = overScrollDown(deltaY); 1298 } else { 1299 scrollAmount = overScrollUp(deltaY, range); 1300 } 1301 1302 // Calling overScrollBy will call onOverScrolled, which 1303 // calls onScrollChanged if applicable. 1304 if (scrollAmount != 0.0f) { 1305 // The scrolling motion could not be compensated with the 1306 // existing overScroll, we have to scroll the view 1307 overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY, 1308 0, range, 0, getHeight() / 2, true); 1309 } 1310 } 1311 break; 1312 case MotionEvent.ACTION_UP: 1313 if (mIsBeingDragged) { 1314 final VelocityTracker velocityTracker = mVelocityTracker; 1315 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1316 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); 1317 1318 if (shouldOverScrollFling(initialVelocity)) { 1319 onOverScrollFling(true, initialVelocity); 1320 } else { 1321 if (getChildCount() > 0) { 1322 if ((Math.abs(initialVelocity) > mMinimumVelocity)) { 1323 float currentOverScrollTop = getCurrentOverScrollAmount(true); 1324 if (currentOverScrollTop == 0.0f || initialVelocity > 0) { 1325 fling(-initialVelocity); 1326 } else { 1327 onOverScrollFling(false, initialVelocity); 1328 } 1329 } else { 1330 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, 1331 getScrollRange())) { 1332 postInvalidateOnAnimation(); 1333 } 1334 } 1335 } 1336 } 1337 1338 mActivePointerId = INVALID_POINTER; 1339 endDrag(); 1340 } 1341 1342 break; 1343 case MotionEvent.ACTION_CANCEL: 1344 if (mIsBeingDragged && getChildCount() > 0) { 1345 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { 1346 postInvalidateOnAnimation(); 1347 } 1348 mActivePointerId = INVALID_POINTER; 1349 endDrag(); 1350 } 1351 break; 1352 case MotionEvent.ACTION_POINTER_DOWN: { 1353 final int index = ev.getActionIndex(); 1354 mLastMotionY = (int) ev.getY(index); 1355 mDownX = (int) ev.getX(index); 1356 mActivePointerId = ev.getPointerId(index); 1357 break; 1358 } 1359 case MotionEvent.ACTION_POINTER_UP: 1360 onSecondaryPointerUp(ev); 1361 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId)); 1362 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId)); 1363 break; 1364 } 1365 return true; 1366 } 1367 1368 private void onOverScrollFling(boolean open, int initialVelocity) { 1369 if (mOverscrollTopChangedListener != null) { 1370 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open); 1371 } 1372 mDontReportNextOverScroll = true; 1373 setOverScrollAmount(0.0f, true, false); 1374 } 1375 1376 /** 1377 * Perform a scroll upwards and adapt the overscroll amounts accordingly 1378 * 1379 * @param deltaY The amount to scroll upwards, has to be positive. 1380 * @return The amount of scrolling to be performed by the scroller, 1381 * not handled by the overScroll amount. 1382 */ 1383 private float overScrollUp(int deltaY, int range) { 1384 deltaY = Math.max(deltaY, 0); 1385 float currentTopAmount = getCurrentOverScrollAmount(true); 1386 float newTopAmount = currentTopAmount - deltaY; 1387 if (currentTopAmount > 0) { 1388 setOverScrollAmount(newTopAmount, true /* onTop */, 1389 false /* animate */); 1390 } 1391 // Top overScroll might not grab all scrolling motion, 1392 // we have to scroll as well. 1393 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f; 1394 float newScrollY = mOwnScrollY + scrollAmount; 1395 if (newScrollY > range) { 1396 if (!mExpandedInThisMotion) { 1397 float currentBottomPixels = getCurrentOverScrolledPixels(false); 1398 // We overScroll on the top 1399 setOverScrolledPixels(currentBottomPixels + newScrollY - range, 1400 false /* onTop */, 1401 false /* animate */); 1402 } 1403 mOwnScrollY = range; 1404 scrollAmount = 0.0f; 1405 } 1406 return scrollAmount; 1407 } 1408 1409 /** 1410 * Perform a scroll downward and adapt the overscroll amounts accordingly 1411 * 1412 * @param deltaY The amount to scroll downwards, has to be negative. 1413 * @return The amount of scrolling to be performed by the scroller, 1414 * not handled by the overScroll amount. 1415 */ 1416 private float overScrollDown(int deltaY) { 1417 deltaY = Math.min(deltaY, 0); 1418 float currentBottomAmount = getCurrentOverScrollAmount(false); 1419 float newBottomAmount = currentBottomAmount + deltaY; 1420 if (currentBottomAmount > 0) { 1421 setOverScrollAmount(newBottomAmount, false /* onTop */, 1422 false /* animate */); 1423 } 1424 // Bottom overScroll might not grab all scrolling motion, 1425 // we have to scroll as well. 1426 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f; 1427 float newScrollY = mOwnScrollY + scrollAmount; 1428 if (newScrollY < 0) { 1429 float currentTopPixels = getCurrentOverScrolledPixels(true); 1430 // We overScroll on the top 1431 setOverScrolledPixels(currentTopPixels - newScrollY, 1432 true /* onTop */, 1433 false /* animate */); 1434 mOwnScrollY = 0; 1435 scrollAmount = 0.0f; 1436 } 1437 return scrollAmount; 1438 } 1439 1440 private void onSecondaryPointerUp(MotionEvent ev) { 1441 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> 1442 MotionEvent.ACTION_POINTER_INDEX_SHIFT; 1443 final int pointerId = ev.getPointerId(pointerIndex); 1444 if (pointerId == mActivePointerId) { 1445 // This was our active pointer going up. Choose a new 1446 // active pointer and adjust accordingly. 1447 // TODO: Make this decision more intelligent. 1448 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 1449 mLastMotionY = (int) ev.getY(newPointerIndex); 1450 mActivePointerId = ev.getPointerId(newPointerIndex); 1451 if (mVelocityTracker != null) { 1452 mVelocityTracker.clear(); 1453 } 1454 } 1455 } 1456 1457 private void initVelocityTrackerIfNotExists() { 1458 if (mVelocityTracker == null) { 1459 mVelocityTracker = VelocityTracker.obtain(); 1460 } 1461 } 1462 1463 private void recycleVelocityTracker() { 1464 if (mVelocityTracker != null) { 1465 mVelocityTracker.recycle(); 1466 mVelocityTracker = null; 1467 } 1468 } 1469 1470 private void initOrResetVelocityTracker() { 1471 if (mVelocityTracker == null) { 1472 mVelocityTracker = VelocityTracker.obtain(); 1473 } else { 1474 mVelocityTracker.clear(); 1475 } 1476 } 1477 1478 public void setFinishScrollingCallback(Runnable runnable) { 1479 mFinishScrollingCallback = runnable; 1480 } 1481 1482 @Override 1483 public void computeScroll() { 1484 if (mScroller.computeScrollOffset()) { 1485 // This is called at drawing time by ViewGroup. 1486 int oldX = mScrollX; 1487 int oldY = mOwnScrollY; 1488 int x = mScroller.getCurrX(); 1489 int y = mScroller.getCurrY(); 1490 1491 if (oldX != x || oldY != y) { 1492 int range = getScrollRange(); 1493 if (y < 0 && oldY >= 0 || y > range && oldY <= range) { 1494 float currVelocity = mScroller.getCurrVelocity(); 1495 if (currVelocity >= mMinimumVelocity) { 1496 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance; 1497 } 1498 } 1499 1500 if (mDontClampNextScroll) { 1501 range = Math.max(range, oldY); 1502 } 1503 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range, 1504 0, (int) (mMaxOverScroll), false); 1505 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY); 1506 } 1507 1508 // Keep on drawing until the animation has finished. 1509 postInvalidateOnAnimation(); 1510 } else { 1511 mDontClampNextScroll = false; 1512 if (mFinishScrollingCallback != null) { 1513 mFinishScrollingCallback.run(); 1514 } 1515 } 1516 } 1517 1518 @Override 1519 protected boolean overScrollBy(int deltaX, int deltaY, 1520 int scrollX, int scrollY, 1521 int scrollRangeX, int scrollRangeY, 1522 int maxOverScrollX, int maxOverScrollY, 1523 boolean isTouchEvent) { 1524 1525 int newScrollY = scrollY + deltaY; 1526 1527 final int top = -maxOverScrollY; 1528 final int bottom = maxOverScrollY + scrollRangeY; 1529 1530 boolean clampedY = false; 1531 if (newScrollY > bottom) { 1532 newScrollY = bottom; 1533 clampedY = true; 1534 } else if (newScrollY < top) { 1535 newScrollY = top; 1536 clampedY = true; 1537 } 1538 1539 onOverScrolled(0, newScrollY, false, clampedY); 1540 1541 return clampedY; 1542 } 1543 1544 /** 1545 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded 1546 * overscroll effect based on numPixels. By default this will also cancel animations on the 1547 * same overScroll edge. 1548 * 1549 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to 1550 * the rubber-banding logic. 1551 * @param onTop Should the effect be applied on top of the scroller. 1552 * @param animate Should an animation be performed. 1553 */ 1554 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) { 1555 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true); 1556 } 1557 1558 /** 1559 * Set the effective overScroll amount which will be directly reflected in the layout. 1560 * By default this will also cancel animations on the same overScroll edge. 1561 * 1562 * @param amount The amount to overScroll by. 1563 * @param onTop Should the effect be applied on top of the scroller. 1564 * @param animate Should an animation be performed. 1565 */ 1566 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) { 1567 setOverScrollAmount(amount, onTop, animate, true); 1568 } 1569 1570 /** 1571 * Set the effective overScroll amount which will be directly reflected in the layout. 1572 * 1573 * @param amount The amount to overScroll by. 1574 * @param onTop Should the effect be applied on top of the scroller. 1575 * @param animate Should an animation be performed. 1576 * @param cancelAnimators Should running animations be cancelled. 1577 */ 1578 public void setOverScrollAmount(float amount, boolean onTop, boolean animate, 1579 boolean cancelAnimators) { 1580 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop)); 1581 } 1582 1583 /** 1584 * Set the effective overScroll amount which will be directly reflected in the layout. 1585 * 1586 * @param amount The amount to overScroll by. 1587 * @param onTop Should the effect be applied on top of the scroller. 1588 * @param animate Should an animation be performed. 1589 * @param cancelAnimators Should running animations be cancelled. 1590 * @param isRubberbanded The value which will be passed to 1591 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged} 1592 */ 1593 public void setOverScrollAmount(float amount, boolean onTop, boolean animate, 1594 boolean cancelAnimators, boolean isRubberbanded) { 1595 if (cancelAnimators) { 1596 mStateAnimator.cancelOverScrollAnimators(onTop); 1597 } 1598 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded); 1599 } 1600 1601 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate, 1602 boolean isRubberbanded) { 1603 amount = Math.max(0, amount); 1604 if (animate) { 1605 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded); 1606 } else { 1607 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop); 1608 mAmbientState.setOverScrollAmount(amount, onTop); 1609 if (onTop) { 1610 notifyOverscrollTopListener(amount, isRubberbanded); 1611 } 1612 requestChildrenUpdate(); 1613 } 1614 } 1615 1616 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) { 1617 mExpandHelper.onlyObserveMovements(amount > 1.0f); 1618 if (mDontReportNextOverScroll) { 1619 mDontReportNextOverScroll = false; 1620 return; 1621 } 1622 if (mOverscrollTopChangedListener != null) { 1623 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded); 1624 } 1625 } 1626 1627 public void setOverscrollTopChangedListener( 1628 OnOverscrollTopChangedListener overscrollTopChangedListener) { 1629 mOverscrollTopChangedListener = overscrollTopChangedListener; 1630 } 1631 1632 public float getCurrentOverScrollAmount(boolean top) { 1633 return mAmbientState.getOverScrollAmount(top); 1634 } 1635 1636 public float getCurrentOverScrolledPixels(boolean top) { 1637 return top? mOverScrolledTopPixels : mOverScrolledBottomPixels; 1638 } 1639 1640 private void setOverScrolledPixels(float amount, boolean onTop) { 1641 if (onTop) { 1642 mOverScrolledTopPixels = amount; 1643 } else { 1644 mOverScrolledBottomPixels = amount; 1645 } 1646 } 1647 1648 private void customScrollTo(int y) { 1649 mOwnScrollY = y; 1650 updateChildren(); 1651 } 1652 1653 @Override 1654 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { 1655 // Treat animating scrolls differently; see #computeScroll() for why. 1656 if (!mScroller.isFinished()) { 1657 final int oldX = mScrollX; 1658 final int oldY = mOwnScrollY; 1659 mScrollX = scrollX; 1660 mOwnScrollY = scrollY; 1661 if (clampedY) { 1662 springBack(); 1663 } else { 1664 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY); 1665 invalidateParentIfNeeded(); 1666 updateChildren(); 1667 float overScrollTop = getCurrentOverScrollAmount(true); 1668 if (mOwnScrollY < 0) { 1669 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true)); 1670 } else { 1671 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true)); 1672 } 1673 } 1674 } else { 1675 customScrollTo(scrollY); 1676 scrollTo(scrollX, mScrollY); 1677 } 1678 } 1679 1680 private void springBack() { 1681 int scrollRange = getScrollRange(); 1682 boolean overScrolledTop = mOwnScrollY <= 0; 1683 boolean overScrolledBottom = mOwnScrollY >= scrollRange; 1684 if (overScrolledTop || overScrolledBottom) { 1685 boolean onTop; 1686 float newAmount; 1687 if (overScrolledTop) { 1688 onTop = true; 1689 newAmount = -mOwnScrollY; 1690 mOwnScrollY = 0; 1691 mDontReportNextOverScroll = true; 1692 } else { 1693 onTop = false; 1694 newAmount = mOwnScrollY - scrollRange; 1695 mOwnScrollY = scrollRange; 1696 } 1697 setOverScrollAmount(newAmount, onTop, false); 1698 setOverScrollAmount(0.0f, onTop, true); 1699 mScroller.forceFinished(true); 1700 } 1701 } 1702 1703 private int getScrollRange() { 1704 int contentHeight = getContentHeight(); 1705 int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize 1706 + mBottomStackSlowDownHeight); 1707 int imeInset = getImeInset(); 1708 scrollRange += Math.min(imeInset, Math.max(0, 1709 getContentHeight() - (getHeight() - imeInset))); 1710 return scrollRange; 1711 } 1712 1713 private int getImeInset() { 1714 return Math.max(0, mBottomInset - (getRootView().getHeight() - getHeight())); 1715 } 1716 1717 /** 1718 * @return the first child which has visibility unequal to GONE 1719 */ 1720 public ExpandableView getFirstChildNotGone() { 1721 int childCount = getChildCount(); 1722 for (int i = 0; i < childCount; i++) { 1723 View child = getChildAt(i); 1724 if (child.getVisibility() != View.GONE) { 1725 return (ExpandableView) child; 1726 } 1727 } 1728 return null; 1729 } 1730 1731 /** 1732 * @return the child before the given view which has visibility unequal to GONE 1733 */ 1734 public ExpandableView getViewBeforeView(ExpandableView view) { 1735 ExpandableView previousView = null; 1736 int childCount = getChildCount(); 1737 for (int i = 0; i < childCount; i++) { 1738 View child = getChildAt(i); 1739 if (child == view) { 1740 return previousView; 1741 } 1742 if (child.getVisibility() != View.GONE) { 1743 previousView = (ExpandableView) child; 1744 } 1745 } 1746 return null; 1747 } 1748 1749 /** 1750 * @return The first child which has visibility unequal to GONE which is currently below the 1751 * given translationY or equal to it. 1752 */ 1753 private View getFirstChildBelowTranlsationY(float translationY) { 1754 int childCount = getChildCount(); 1755 for (int i = 0; i < childCount; i++) { 1756 View child = getChildAt(i); 1757 if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) { 1758 return child; 1759 } 1760 } 1761 return null; 1762 } 1763 1764 /** 1765 * @return the last child which has visibility unequal to GONE 1766 */ 1767 public View getLastChildNotGone() { 1768 int childCount = getChildCount(); 1769 for (int i = childCount - 1; i >= 0; i--) { 1770 View child = getChildAt(i); 1771 if (child.getVisibility() != View.GONE) { 1772 return child; 1773 } 1774 } 1775 return null; 1776 } 1777 1778 /** 1779 * @return the number of children which have visibility unequal to GONE 1780 */ 1781 public int getNotGoneChildCount() { 1782 int childCount = getChildCount(); 1783 int count = 0; 1784 for (int i = 0; i < childCount; i++) { 1785 ExpandableView child = (ExpandableView) getChildAt(i); 1786 if (child.getVisibility() != View.GONE && !child.willBeGone()) { 1787 count++; 1788 } 1789 } 1790 return count; 1791 } 1792 1793 public int getContentHeight() { 1794 return mContentHeight; 1795 } 1796 1797 private void updateContentHeight() { 1798 int height = 0; 1799 float previousIncreasedAmount = 0.0f; 1800 for (int i = 0; i < getChildCount(); i++) { 1801 ExpandableView expandableView = (ExpandableView) getChildAt(i); 1802 if (expandableView.getVisibility() != View.GONE) { 1803 float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount(); 1804 if (height != 0) { 1805 height += (int) NotificationUtils.interpolate( 1806 mPaddingBetweenElements, 1807 mIncreasedPaddingBetweenElements, 1808 Math.max(previousIncreasedAmount, increasedPaddingAmount)); 1809 } 1810 previousIncreasedAmount = increasedPaddingAmount; 1811 height += expandableView.getIntrinsicHeight(); 1812 } 1813 } 1814 mContentHeight = height + mTopPadding; 1815 updateScrollability(); 1816 } 1817 1818 private void updateScrollability() { 1819 boolean scrollable = getScrollRange() > 0; 1820 if (scrollable != mScrollable) { 1821 mScrollable = scrollable; 1822 setFocusable(scrollable); 1823 } 1824 } 1825 1826 private void updateBackground() { 1827 if (mAmbientState.isDark()) { 1828 return; 1829 } 1830 updateBackgroundBounds(); 1831 if (!mCurrentBounds.equals(mBackgroundBounds)) { 1832 if (mAnimateNextBackgroundTop || mAnimateNextBackgroundBottom || areBoundsAnimating()) { 1833 startBackgroundAnimation(); 1834 } else { 1835 mCurrentBounds.set(mBackgroundBounds); 1836 applyCurrentBackgroundBounds(); 1837 } 1838 } else { 1839 if (mBottomAnimator != null) { 1840 mBottomAnimator.cancel(); 1841 } 1842 if (mTopAnimator != null) { 1843 mTopAnimator.cancel(); 1844 } 1845 } 1846 mAnimateNextBackgroundBottom = false; 1847 mAnimateNextBackgroundTop = false; 1848 } 1849 1850 private boolean areBoundsAnimating() { 1851 return mBottomAnimator != null || mTopAnimator != null; 1852 } 1853 1854 private void startBackgroundAnimation() { 1855 // left and right are always instantly applied 1856 mCurrentBounds.left = mBackgroundBounds.left; 1857 mCurrentBounds.right = mBackgroundBounds.right; 1858 startBottomAnimation(); 1859 startTopAnimation(); 1860 } 1861 1862 private void startTopAnimation() { 1863 int previousEndValue = mEndAnimationRect.top; 1864 int newEndValue = mBackgroundBounds.top; 1865 ObjectAnimator previousAnimator = mTopAnimator; 1866 if (previousAnimator != null && previousEndValue == newEndValue) { 1867 return; 1868 } 1869 if (!mAnimateNextBackgroundTop) { 1870 // just a local update was performed 1871 if (previousAnimator != null) { 1872 // we need to increase all animation keyframes of the previous animator by the 1873 // relative change to the end value 1874 int previousStartValue = mStartAnimationRect.top; 1875 PropertyValuesHolder[] values = previousAnimator.getValues(); 1876 values[0].setIntValues(previousStartValue, newEndValue); 1877 mStartAnimationRect.top = previousStartValue; 1878 mEndAnimationRect.top = newEndValue; 1879 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 1880 return; 1881 } else { 1882 // no new animation needed, let's just apply the value 1883 setBackgroundTop(newEndValue); 1884 return; 1885 } 1886 } 1887 if (previousAnimator != null) { 1888 previousAnimator.cancel(); 1889 } 1890 ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundTop", 1891 mCurrentBounds.top, newEndValue); 1892 Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN; 1893 animator.setInterpolator(interpolator); 1894 animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 1895 // remove the tag when the animation is finished 1896 animator.addListener(new AnimatorListenerAdapter() { 1897 @Override 1898 public void onAnimationEnd(Animator animation) { 1899 mStartAnimationRect.top = -1; 1900 mEndAnimationRect.top = -1; 1901 mTopAnimator = null; 1902 } 1903 }); 1904 animator.start(); 1905 mStartAnimationRect.top = mCurrentBounds.top; 1906 mEndAnimationRect.top = newEndValue; 1907 mTopAnimator = animator; 1908 } 1909 1910 private void startBottomAnimation() { 1911 int previousStartValue = mStartAnimationRect.bottom; 1912 int previousEndValue = mEndAnimationRect.bottom; 1913 int newEndValue = mBackgroundBounds.bottom; 1914 ObjectAnimator previousAnimator = mBottomAnimator; 1915 if (previousAnimator != null && previousEndValue == newEndValue) { 1916 return; 1917 } 1918 if (!mAnimateNextBackgroundBottom) { 1919 // just a local update was performed 1920 if (previousAnimator != null) { 1921 // we need to increase all animation keyframes of the previous animator by the 1922 // relative change to the end value 1923 PropertyValuesHolder[] values = previousAnimator.getValues(); 1924 values[0].setIntValues(previousStartValue, newEndValue); 1925 mStartAnimationRect.bottom = previousStartValue; 1926 mEndAnimationRect.bottom = newEndValue; 1927 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 1928 return; 1929 } else { 1930 // no new animation needed, let's just apply the value 1931 setBackgroundBottom(newEndValue); 1932 return; 1933 } 1934 } 1935 if (previousAnimator != null) { 1936 previousAnimator.cancel(); 1937 } 1938 ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundBottom", 1939 mCurrentBounds.bottom, newEndValue); 1940 Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN; 1941 animator.setInterpolator(interpolator); 1942 animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 1943 // remove the tag when the animation is finished 1944 animator.addListener(new AnimatorListenerAdapter() { 1945 @Override 1946 public void onAnimationEnd(Animator animation) { 1947 mStartAnimationRect.bottom = -1; 1948 mEndAnimationRect.bottom = -1; 1949 mBottomAnimator = null; 1950 } 1951 }); 1952 animator.start(); 1953 mStartAnimationRect.bottom = mCurrentBounds.bottom; 1954 mEndAnimationRect.bottom = newEndValue; 1955 mBottomAnimator = animator; 1956 } 1957 1958 private void setBackgroundTop(int top) { 1959 mCurrentBounds.top = top; 1960 applyCurrentBackgroundBounds(); 1961 } 1962 1963 public void setBackgroundBottom(int bottom) { 1964 mCurrentBounds.bottom = bottom; 1965 applyCurrentBackgroundBounds(); 1966 } 1967 1968 private void applyCurrentBackgroundBounds() { 1969 if (!mFadingOut) { 1970 mScrimController.setExcludedBackgroundArea(mCurrentBounds); 1971 } 1972 invalidate(); 1973 } 1974 1975 /** 1976 * Update the background bounds to the new desired bounds 1977 */ 1978 private void updateBackgroundBounds() { 1979 mBackgroundBounds.left = (int) getX(); 1980 mBackgroundBounds.right = (int) (getX() + getWidth()); 1981 if (!mIsExpanded) { 1982 mBackgroundBounds.top = 0; 1983 mBackgroundBounds.bottom = 0; 1984 } 1985 ActivatableNotificationView firstView = mFirstVisibleBackgroundChild; 1986 int top = 0; 1987 if (firstView != null) { 1988 int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(firstView); 1989 if (mAnimateNextBackgroundTop 1990 || mTopAnimator == null && mCurrentBounds.top == finalTranslationY 1991 || mTopAnimator != null && mEndAnimationRect.top == finalTranslationY) { 1992 // we're ending up at the same location as we are now, lets just skip the animation 1993 top = finalTranslationY; 1994 } else { 1995 top = (int) firstView.getTranslationY(); 1996 } 1997 } 1998 ActivatableNotificationView lastView = mLastVisibleBackgroundChild; 1999 int bottom = 0; 2000 if (lastView != null) { 2001 int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(lastView); 2002 int finalHeight = StackStateAnimator.getFinalActualHeight(lastView); 2003 int finalBottom = finalTranslationY + finalHeight; 2004 finalBottom = Math.min(finalBottom, getHeight()); 2005 if (mAnimateNextBackgroundBottom 2006 || mBottomAnimator == null && mCurrentBounds.bottom == finalBottom 2007 || mBottomAnimator != null && mEndAnimationRect.bottom == finalBottom) { 2008 // we're ending up at the same location as we are now, lets just skip the animation 2009 bottom = finalBottom; 2010 } else { 2011 bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight()); 2012 bottom = Math.min(bottom, getHeight()); 2013 } 2014 } else { 2015 top = mTopPadding; 2016 bottom = top; 2017 } 2018 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD) { 2019 top = (int) Math.max(mTopPadding + mStackTranslation, top); 2020 } else { 2021 // otherwise the animation from the shade to the keyguard will jump as it's maxed 2022 top = Math.max(0, top); 2023 } 2024 mBackgroundBounds.top = top; 2025 mBackgroundBounds.bottom = Math.min(getHeight(), Math.max(bottom, top)); 2026 } 2027 2028 private ActivatableNotificationView getFirstPinnedHeadsUp() { 2029 int childCount = getChildCount(); 2030 for (int i = 0; i < childCount; i++) { 2031 View child = getChildAt(i); 2032 if (child.getVisibility() != View.GONE 2033 && child instanceof ExpandableNotificationRow) { 2034 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 2035 if (row.isPinned()) { 2036 return row; 2037 } 2038 } 2039 } 2040 return null; 2041 } 2042 2043 private ActivatableNotificationView getLastChildWithBackground() { 2044 int childCount = getChildCount(); 2045 for (int i = childCount - 1; i >= 0; i--) { 2046 View child = getChildAt(i); 2047 if (child.getVisibility() != View.GONE 2048 && child instanceof ActivatableNotificationView) { 2049 return (ActivatableNotificationView) child; 2050 } 2051 } 2052 return null; 2053 } 2054 2055 private ActivatableNotificationView getFirstChildWithBackground() { 2056 int childCount = getChildCount(); 2057 for (int i = 0; i < childCount; i++) { 2058 View child = getChildAt(i); 2059 if (child.getVisibility() != View.GONE 2060 && child instanceof ActivatableNotificationView) { 2061 return (ActivatableNotificationView) child; 2062 } 2063 } 2064 return null; 2065 } 2066 2067 /** 2068 * Fling the scroll view 2069 * 2070 * @param velocityY The initial velocity in the Y direction. Positive 2071 * numbers mean that the finger/cursor is moving down the screen, 2072 * which means we want to scroll towards the top. 2073 */ 2074 private void fling(int velocityY) { 2075 if (getChildCount() > 0) { 2076 int scrollRange = getScrollRange(); 2077 2078 float topAmount = getCurrentOverScrollAmount(true); 2079 float bottomAmount = getCurrentOverScrollAmount(false); 2080 if (velocityY < 0 && topAmount > 0) { 2081 mOwnScrollY -= (int) topAmount; 2082 mDontReportNextOverScroll = true; 2083 setOverScrollAmount(0, true, false); 2084 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */) 2085 * mOverflingDistance + topAmount; 2086 } else if (velocityY > 0 && bottomAmount > 0) { 2087 mOwnScrollY += bottomAmount; 2088 setOverScrollAmount(0, false, false); 2089 mMaxOverScroll = Math.abs(velocityY) / 1000f 2090 * getRubberBandFactor(false /* onTop */) * mOverflingDistance 2091 + bottomAmount; 2092 } else { 2093 // it will be set once we reach the boundary 2094 mMaxOverScroll = 0.0f; 2095 } 2096 int minScrollY = Math.max(0, scrollRange); 2097 if (mExpandedInThisMotion) { 2098 minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand); 2099 } 2100 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, 2101 minScrollY, 0, mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2); 2102 2103 postInvalidateOnAnimation(); 2104 } 2105 } 2106 2107 /** 2108 * @return Whether a fling performed on the top overscroll edge lead to the expanded 2109 * overScroll view (i.e QS). 2110 */ 2111 private boolean shouldOverScrollFling(int initialVelocity) { 2112 float topOverScroll = getCurrentOverScrollAmount(true); 2113 return mScrolledToTopOnFirstDown 2114 && !mExpandedInThisMotion 2115 && topOverScroll > mMinTopOverScrollToEscape 2116 && initialVelocity > 0; 2117 } 2118 2119 /** 2120 * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into 2121 * account. 2122 * 2123 * @param qsHeight the top padding imposed by the quick settings panel 2124 * @param animate whether to animate the change 2125 * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and 2126 * {@code qsHeight} is the final top padding 2127 */ 2128 public void updateTopPadding(float qsHeight, boolean animate, 2129 boolean ignoreIntrinsicPadding) { 2130 float start = qsHeight; 2131 float stackHeight = getHeight() - start; 2132 int minStackHeight = getLayoutMinHeight(); 2133 if (stackHeight <= minStackHeight) { 2134 float overflow = minStackHeight - stackHeight; 2135 stackHeight = minStackHeight; 2136 start = getHeight() - stackHeight; 2137 mTopPaddingOverflow = overflow; 2138 } else { 2139 mTopPaddingOverflow = 0; 2140 } 2141 setTopPadding(ignoreIntrinsicPadding ? (int) start : clampPadding((int) start), 2142 animate); 2143 setStackHeight(mLastSetStackHeight); 2144 } 2145 2146 public int getLayoutMinHeight() { 2147 int firstChildMinHeight = getFirstChildIntrinsicHeight(); 2148 return Math.min(firstChildMinHeight + mBottomStackPeekSize + mBottomStackSlowDownHeight, 2149 mMaxLayoutHeight - mTopPadding); 2150 } 2151 2152 public int getFirstChildIntrinsicHeight() { 2153 final ExpandableView firstChild = getFirstChildNotGone(); 2154 int firstChildMinHeight = firstChild != null 2155 ? firstChild.getIntrinsicHeight() 2156 : mEmptyShadeView != null 2157 ? mEmptyShadeView.getIntrinsicHeight() 2158 : mCollapsedSize; 2159 if (mOwnScrollY > 0) { 2160 firstChildMinHeight = Math.max(firstChildMinHeight - mOwnScrollY, mCollapsedSize); 2161 } 2162 return firstChildMinHeight; 2163 } 2164 2165 public float getTopPaddingOverflow() { 2166 return mTopPaddingOverflow; 2167 } 2168 2169 public int getPeekHeight() { 2170 final ExpandableView firstChild = getFirstChildNotGone(); 2171 final int firstChildMinHeight = firstChild != null ? firstChild.getCollapsedHeight() 2172 : mCollapsedSize; 2173 return mIntrinsicPadding + firstChildMinHeight + mBottomStackPeekSize 2174 + mBottomStackSlowDownHeight; 2175 } 2176 2177 private int clampPadding(int desiredPadding) { 2178 return Math.max(desiredPadding, mIntrinsicPadding); 2179 } 2180 2181 private float getRubberBandFactor(boolean onTop) { 2182 if (!onTop) { 2183 return RUBBER_BAND_FACTOR_NORMAL; 2184 } 2185 if (mExpandedInThisMotion) { 2186 return RUBBER_BAND_FACTOR_AFTER_EXPAND; 2187 } else if (mIsExpansionChanging || mPanelTracking) { 2188 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND; 2189 } else if (mScrolledToTopOnFirstDown) { 2190 return 1.0f; 2191 } 2192 return RUBBER_BAND_FACTOR_NORMAL; 2193 } 2194 2195 /** 2196 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is 2197 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the 2198 * overscroll view (e.g. expand QS). 2199 */ 2200 private boolean isRubberbanded(boolean onTop) { 2201 return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking 2202 || !mScrolledToTopOnFirstDown; 2203 } 2204 2205 private void endDrag() { 2206 setIsBeingDragged(false); 2207 2208 recycleVelocityTracker(); 2209 2210 if (getCurrentOverScrollAmount(true /* onTop */) > 0) { 2211 setOverScrollAmount(0, true /* onTop */, true /* animate */); 2212 } 2213 if (getCurrentOverScrollAmount(false /* onTop */) > 0) { 2214 setOverScrollAmount(0, false /* onTop */, true /* animate */); 2215 } 2216 } 2217 2218 private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) { 2219 ev.offsetLocation(sourceView.getX(), sourceView.getY()); 2220 ev.offsetLocation(-targetView.getX(), -targetView.getY()); 2221 } 2222 2223 @Override 2224 public boolean onInterceptTouchEvent(MotionEvent ev) { 2225 initDownStates(ev); 2226 handleEmptySpaceClick(ev); 2227 boolean expandWantsIt = false; 2228 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) { 2229 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev); 2230 } 2231 boolean scrollWantsIt = false; 2232 if (!mSwipingInProgress && !mExpandingNotification) { 2233 scrollWantsIt = onInterceptTouchEventScroll(ev); 2234 } 2235 boolean swipeWantsIt = false; 2236 if (!mIsBeingDragged 2237 && !mExpandingNotification 2238 && !mExpandedInThisMotion 2239 && !mOnlyScrollingInThisMotion 2240 && !mDisallowDismissInThisMotion) { 2241 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev); 2242 } 2243 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev); 2244 } 2245 2246 private void handleEmptySpaceClick(MotionEvent ev) { 2247 switch (ev.getActionMasked()) { 2248 case MotionEvent.ACTION_MOVE: 2249 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop 2250 || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) { 2251 mTouchIsClick = false; 2252 } 2253 break; 2254 case MotionEvent.ACTION_UP: 2255 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD && mTouchIsClick && 2256 isBelowLastNotification(mInitialTouchX, mInitialTouchY)) { 2257 mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY); 2258 } 2259 break; 2260 } 2261 } 2262 2263 private void initDownStates(MotionEvent ev) { 2264 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 2265 mExpandedInThisMotion = false; 2266 mOnlyScrollingInThisMotion = !mScroller.isFinished(); 2267 mDisallowScrollingInThisMotion = false; 2268 mDisallowDismissInThisMotion = false; 2269 mTouchIsClick = true; 2270 mInitialTouchX = ev.getX(); 2271 mInitialTouchY = ev.getY(); 2272 } 2273 } 2274 2275 public void setChildTransferInProgress(boolean childTransferInProgress) { 2276 mChildTransferInProgress = childTransferInProgress; 2277 } 2278 2279 @Override 2280 public void onViewRemoved(View child) { 2281 super.onViewRemoved(child); 2282 // we only call our internal methods if this is actually a removal and not just a 2283 // notification which becomes a child notification 2284 if (!mChildTransferInProgress) { 2285 onViewRemovedInternal(child, this); 2286 } 2287 } 2288 2289 @Override 2290 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 2291 super.requestDisallowInterceptTouchEvent(disallowIntercept); 2292 if (disallowIntercept) { 2293 mSwipeHelper.removeLongPressCallback(); 2294 } 2295 } 2296 2297 private void onViewRemovedInternal(View child, ViewGroup container) { 2298 if (mChangePositionInProgress) { 2299 // This is only a position change, don't do anything special 2300 return; 2301 } 2302 ExpandableView expandableView = (ExpandableView) child; 2303 expandableView.setOnHeightChangedListener(null); 2304 mCurrentStackScrollState.removeViewStateForView(child); 2305 updateScrollStateForRemovedChild(expandableView); 2306 boolean animationGenerated = generateRemoveAnimation(child); 2307 if (animationGenerated) { 2308 if (!mSwipedOutViews.contains(child)) { 2309 container.getOverlay().add(child); 2310 } else if (Math.abs(expandableView.getTranslation()) != expandableView.getWidth()) { 2311 container.addTransientView(child, 0); 2312 expandableView.setTransientContainer(container); 2313 } 2314 } else { 2315 mSwipedOutViews.remove(child); 2316 } 2317 updateAnimationState(false, child); 2318 2319 // Make sure the clipRect we might have set is removed 2320 expandableView.setClipTopAmount(0); 2321 2322 focusNextViewIfFocused(child); 2323 } 2324 2325 private void focusNextViewIfFocused(View view) { 2326 if (view instanceof ExpandableNotificationRow) { 2327 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 2328 if (row.shouldRefocusOnDismiss()) { 2329 View nextView = row.getChildAfterViewWhenDismissed(); 2330 if (nextView == null) { 2331 View groupParentWhenDismissed = row.getGroupParentWhenDismissed(); 2332 nextView = getFirstChildBelowTranlsationY(groupParentWhenDismissed != null 2333 ? groupParentWhenDismissed.getTranslationY() 2334 : view.getTranslationY()); 2335 } 2336 if (nextView != null) { 2337 nextView.requestAccessibilityFocus(); 2338 } 2339 } 2340 } 2341 2342 } 2343 2344 private boolean isChildInGroup(View child) { 2345 return child instanceof ExpandableNotificationRow 2346 && mGroupManager.isChildInGroupWithSummary( 2347 ((ExpandableNotificationRow) child).getStatusBarNotification()); 2348 } 2349 2350 /** 2351 * Generate a remove animation for a child view. 2352 * 2353 * @param child The view to generate the remove animation for. 2354 * @return Whether an animation was generated. 2355 */ 2356 private boolean generateRemoveAnimation(View child) { 2357 if (removeRemovedChildFromHeadsUpChangeAnimations(child)) { 2358 mAddedHeadsUpChildren.remove(child); 2359 return false; 2360 } 2361 if (isClickedHeadsUp(child)) { 2362 // An animation is already running, add it to the Overlay 2363 mClearOverlayViewsWhenFinished.add(child); 2364 return true; 2365 } 2366 if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) { 2367 if (!mChildrenToAddAnimated.contains(child)) { 2368 // Generate Animations 2369 mChildrenToRemoveAnimated.add(child); 2370 mNeedsAnimation = true; 2371 return true; 2372 } else { 2373 mChildrenToAddAnimated.remove(child); 2374 mFromMoreCardAdditions.remove(child); 2375 return false; 2376 } 2377 } 2378 return false; 2379 } 2380 2381 private boolean isClickedHeadsUp(View child) { 2382 return HeadsUpManager.isClickedHeadsUpNotification(child); 2383 } 2384 2385 /** 2386 * Remove a removed child view from the heads up animations if it was just added there 2387 * 2388 * @return whether any child was removed from the list to animate 2389 */ 2390 private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) { 2391 boolean hasAddEvent = false; 2392 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) { 2393 ExpandableNotificationRow row = eventPair.first; 2394 boolean isHeadsUp = eventPair.second; 2395 if (child == row) { 2396 mTmpList.add(eventPair); 2397 hasAddEvent |= isHeadsUp; 2398 } 2399 } 2400 if (hasAddEvent) { 2401 // This child was just added lets remove all events. 2402 mHeadsUpChangeAnimations.removeAll(mTmpList); 2403 ((ExpandableNotificationRow ) child).setHeadsupDisappearRunning(false); 2404 } 2405 mTmpList.clear(); 2406 return hasAddEvent; 2407 } 2408 2409 /** 2410 * @param child the child to query 2411 * @return whether a view is not a top level child but a child notification and that group is 2412 * not expanded 2413 */ 2414 private boolean isChildInInvisibleGroup(View child) { 2415 if (child instanceof ExpandableNotificationRow) { 2416 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 2417 ExpandableNotificationRow groupSummary = 2418 mGroupManager.getGroupSummary(row.getStatusBarNotification()); 2419 if (groupSummary != null && groupSummary != row) { 2420 return row.getVisibility() == View.INVISIBLE; 2421 } 2422 } 2423 return false; 2424 } 2425 2426 /** 2427 * Updates the scroll position when a child was removed 2428 * 2429 * @param removedChild the removed child 2430 */ 2431 private void updateScrollStateForRemovedChild(ExpandableView removedChild) { 2432 int startingPosition = getPositionInLinearLayout(removedChild); 2433 int padding = (int) NotificationUtils.interpolate( 2434 mPaddingBetweenElements, 2435 mIncreasedPaddingBetweenElements, 2436 removedChild.getIncreasedPaddingAmount()); 2437 int childHeight = getIntrinsicHeight(removedChild) + padding; 2438 int endPosition = startingPosition + childHeight; 2439 if (endPosition <= mOwnScrollY) { 2440 // This child is fully scrolled of the top, so we have to deduct its height from the 2441 // scrollPosition 2442 mOwnScrollY -= childHeight; 2443 } else if (startingPosition < mOwnScrollY) { 2444 // This child is currently being scrolled into, set the scroll position to the start of 2445 // this child 2446 mOwnScrollY = startingPosition; 2447 } 2448 } 2449 2450 private int getIntrinsicHeight(View view) { 2451 if (view instanceof ExpandableView) { 2452 ExpandableView expandableView = (ExpandableView) view; 2453 return expandableView.getIntrinsicHeight(); 2454 } 2455 return view.getHeight(); 2456 } 2457 2458 private int getPositionInLinearLayout(View requestedView) { 2459 ExpandableNotificationRow childInGroup = null; 2460 ExpandableNotificationRow requestedRow = null; 2461 if (isChildInGroup(requestedView)) { 2462 // We're asking for a child in a group. Calculate the position of the parent first, 2463 // then within the parent. 2464 childInGroup = (ExpandableNotificationRow) requestedView; 2465 requestedView = requestedRow = childInGroup.getNotificationParent(); 2466 } 2467 int position = 0; 2468 float previousIncreasedAmount = 0.0f; 2469 for (int i = 0; i < getChildCount(); i++) { 2470 ExpandableView child = (ExpandableView) getChildAt(i); 2471 boolean notGone = child.getVisibility() != View.GONE; 2472 if (notGone) { 2473 float increasedPaddingAmount = child.getIncreasedPaddingAmount(); 2474 if (position != 0) { 2475 position += (int) NotificationUtils.interpolate( 2476 mPaddingBetweenElements, 2477 mIncreasedPaddingBetweenElements, 2478 Math.max(previousIncreasedAmount, increasedPaddingAmount)); 2479 } 2480 previousIncreasedAmount = increasedPaddingAmount; 2481 } 2482 if (child == requestedView) { 2483 if (requestedRow != null) { 2484 position += requestedRow.getPositionOfChild(childInGroup); 2485 } 2486 return position; 2487 } 2488 if (notGone) { 2489 position += getIntrinsicHeight(child); 2490 } 2491 } 2492 return 0; 2493 } 2494 2495 @Override 2496 public void onViewAdded(View child) { 2497 super.onViewAdded(child); 2498 onViewAddedInternal(child); 2499 } 2500 2501 private void updateFirstAndLastBackgroundViews() { 2502 ActivatableNotificationView firstChild = getFirstChildWithBackground(); 2503 ActivatableNotificationView lastChild = getLastChildWithBackground(); 2504 if (mAnimationsEnabled && mIsExpanded) { 2505 mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild; 2506 mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild; 2507 } else { 2508 mAnimateNextBackgroundTop = false; 2509 mAnimateNextBackgroundBottom = false; 2510 } 2511 mFirstVisibleBackgroundChild = firstChild; 2512 mLastVisibleBackgroundChild = lastChild; 2513 } 2514 2515 private void onViewAddedInternal(View child) { 2516 updateHideSensitiveForChild(child); 2517 ((ExpandableView) child).setOnHeightChangedListener(this); 2518 generateAddAnimation(child, false /* fromMoreCard */); 2519 updateAnimationState(child); 2520 updateChronometerForChild(child); 2521 } 2522 2523 private void updateHideSensitiveForChild(View child) { 2524 if (child instanceof ExpandableView) { 2525 ExpandableView expandableView = (ExpandableView) child; 2526 expandableView.setHideSensitiveForIntrinsicHeight(mAmbientState.isHideSensitive()); 2527 } 2528 } 2529 2530 public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) { 2531 onViewRemovedInternal(row, childrenContainer); 2532 } 2533 2534 public void notifyGroupChildAdded(View row) { 2535 onViewAddedInternal(row); 2536 } 2537 2538 public void setAnimationsEnabled(boolean animationsEnabled) { 2539 mAnimationsEnabled = animationsEnabled; 2540 updateNotificationAnimationStates(); 2541 } 2542 2543 private void updateNotificationAnimationStates() { 2544 boolean running = mAnimationsEnabled || mPulsing; 2545 int childCount = getChildCount(); 2546 for (int i = 0; i < childCount; i++) { 2547 View child = getChildAt(i); 2548 running &= mIsExpanded || isPinnedHeadsUp(child); 2549 updateAnimationState(running, child); 2550 } 2551 } 2552 2553 private void updateAnimationState(View child) { 2554 updateAnimationState((mAnimationsEnabled || mPulsing) 2555 && (mIsExpanded || isPinnedHeadsUp(child)), child); 2556 } 2557 2558 2559 private void updateAnimationState(boolean running, View child) { 2560 if (child instanceof ExpandableNotificationRow) { 2561 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 2562 row.setIconAnimationRunning(running); 2563 } 2564 } 2565 2566 public boolean isAddOrRemoveAnimationPending() { 2567 return mNeedsAnimation 2568 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty()); 2569 } 2570 /** 2571 * Generate an animation for an added child view. 2572 * 2573 * @param child The view to be added. 2574 * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen. 2575 */ 2576 public void generateAddAnimation(View child, boolean fromMoreCard) { 2577 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) { 2578 // Generate Animations 2579 mChildrenToAddAnimated.add(child); 2580 if (fromMoreCard) { 2581 mFromMoreCardAdditions.add(child); 2582 } 2583 mNeedsAnimation = true; 2584 } 2585 if (isHeadsUp(child) && !mChangePositionInProgress) { 2586 mAddedHeadsUpChildren.add(child); 2587 mChildrenToAddAnimated.remove(child); 2588 } 2589 } 2590 2591 /** 2592 * Change the position of child to a new location 2593 * 2594 * @param child the view to change the position for 2595 * @param newIndex the new index 2596 */ 2597 public void changeViewPosition(View child, int newIndex) { 2598 int currentIndex = indexOfChild(child); 2599 if (child != null && child.getParent() == this && currentIndex != newIndex) { 2600 mChangePositionInProgress = true; 2601 ((ExpandableView)child).setChangingPosition(true); 2602 removeView(child); 2603 addView(child, newIndex); 2604 ((ExpandableView)child).setChangingPosition(false); 2605 mChangePositionInProgress = false; 2606 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) { 2607 mChildrenChangingPositions.add(child); 2608 mNeedsAnimation = true; 2609 } 2610 } 2611 } 2612 2613 private void startAnimationToState() { 2614 if (mNeedsAnimation) { 2615 generateChildHierarchyEvents(); 2616 mNeedsAnimation = false; 2617 } 2618 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) { 2619 setAnimationRunning(true); 2620 mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState, 2621 mGoToFullShadeDelay); 2622 mAnimationEvents.clear(); 2623 updateBackground(); 2624 updateViewShadows(); 2625 } else { 2626 applyCurrentState(); 2627 } 2628 mGoToFullShadeDelay = 0; 2629 } 2630 2631 private void generateChildHierarchyEvents() { 2632 generateHeadsUpAnimationEvents(); 2633 generateChildRemovalEvents(); 2634 generateChildAdditionEvents(); 2635 generatePositionChangeEvents(); 2636 generateSnapBackEvents(); 2637 generateDragEvents(); 2638 generateTopPaddingEvent(); 2639 generateActivateEvent(); 2640 generateDimmedEvent(); 2641 generateHideSensitiveEvent(); 2642 generateDarkEvent(); 2643 generateGoToFullShadeEvent(); 2644 generateViewResizeEvent(); 2645 generateGroupExpansionEvent(); 2646 generateAnimateEverythingEvent(); 2647 mNeedsAnimation = false; 2648 } 2649 2650 private void generateHeadsUpAnimationEvents() { 2651 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) { 2652 ExpandableNotificationRow row = eventPair.first; 2653 boolean isHeadsUp = eventPair.second; 2654 int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER; 2655 boolean onBottom = false; 2656 boolean pinnedAndClosed = row.isPinned() && !mIsExpanded; 2657 if (!mIsExpanded && !isHeadsUp) { 2658 type = row.wasJustClicked() 2659 ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK 2660 : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR; 2661 if (row.isChildInGroup()) { 2662 // We can otherwise get stuck in there if it was just isolated 2663 row.setHeadsupDisappearRunning(false); 2664 } 2665 } else { 2666 StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row); 2667 if (viewState == null) { 2668 // A view state was never generated for this view, so we don't need to animate 2669 // this. This may happen with notification children. 2670 continue; 2671 } 2672 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) { 2673 if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) { 2674 // Our custom add animation 2675 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR; 2676 } else { 2677 // Normal add animation 2678 type = AnimationEvent.ANIMATION_TYPE_ADD; 2679 } 2680 onBottom = !pinnedAndClosed; 2681 } 2682 } 2683 AnimationEvent event = new AnimationEvent(row, type); 2684 event.headsUpFromBottom = onBottom; 2685 mAnimationEvents.add(event); 2686 } 2687 mHeadsUpChangeAnimations.clear(); 2688 mAddedHeadsUpChildren.clear(); 2689 } 2690 2691 private boolean shouldHunAppearFromBottom(StackViewState viewState) { 2692 if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) { 2693 return false; 2694 } 2695 return true; 2696 } 2697 2698 private void generateGroupExpansionEvent() { 2699 // Generate a group expansion/collapsing event if there is such a group at all 2700 if (mExpandedGroupView != null) { 2701 mAnimationEvents.add(new AnimationEvent(mExpandedGroupView, 2702 AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED)); 2703 mExpandedGroupView = null; 2704 } 2705 } 2706 2707 private void generateViewResizeEvent() { 2708 if (mNeedViewResizeAnimation) { 2709 mAnimationEvents.add( 2710 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE)); 2711 } 2712 mNeedViewResizeAnimation = false; 2713 } 2714 2715 private void generateSnapBackEvents() { 2716 for (View child : mSnappedBackChildren) { 2717 mAnimationEvents.add(new AnimationEvent(child, 2718 AnimationEvent.ANIMATION_TYPE_SNAP_BACK)); 2719 } 2720 mSnappedBackChildren.clear(); 2721 } 2722 2723 private void generateDragEvents() { 2724 for (View child : mDragAnimPendingChildren) { 2725 mAnimationEvents.add(new AnimationEvent(child, 2726 AnimationEvent.ANIMATION_TYPE_START_DRAG)); 2727 } 2728 mDragAnimPendingChildren.clear(); 2729 } 2730 2731 private void generateChildRemovalEvents() { 2732 for (View child : mChildrenToRemoveAnimated) { 2733 boolean childWasSwipedOut = mSwipedOutViews.contains(child); 2734 int animationType = childWasSwipedOut 2735 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT 2736 : AnimationEvent.ANIMATION_TYPE_REMOVE; 2737 AnimationEvent event = new AnimationEvent(child, animationType); 2738 2739 // we need to know the view after this one 2740 event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY()); 2741 mAnimationEvents.add(event); 2742 mSwipedOutViews.remove(child); 2743 } 2744 mChildrenToRemoveAnimated.clear(); 2745 } 2746 2747 private void generatePositionChangeEvents() { 2748 for (View child : mChildrenChangingPositions) { 2749 mAnimationEvents.add(new AnimationEvent(child, 2750 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)); 2751 } 2752 mChildrenChangingPositions.clear(); 2753 if (mGenerateChildOrderChangedEvent) { 2754 mAnimationEvents.add(new AnimationEvent(null, 2755 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)); 2756 mGenerateChildOrderChangedEvent = false; 2757 } 2758 } 2759 2760 private void generateChildAdditionEvents() { 2761 for (View child : mChildrenToAddAnimated) { 2762 if (mFromMoreCardAdditions.contains(child)) { 2763 mAnimationEvents.add(new AnimationEvent(child, 2764 AnimationEvent.ANIMATION_TYPE_ADD, 2765 StackStateAnimator.ANIMATION_DURATION_STANDARD)); 2766 } else { 2767 mAnimationEvents.add(new AnimationEvent(child, 2768 AnimationEvent.ANIMATION_TYPE_ADD)); 2769 } 2770 } 2771 mChildrenToAddAnimated.clear(); 2772 mFromMoreCardAdditions.clear(); 2773 } 2774 2775 private void generateTopPaddingEvent() { 2776 if (mTopPaddingNeedsAnimation) { 2777 mAnimationEvents.add( 2778 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED)); 2779 } 2780 mTopPaddingNeedsAnimation = false; 2781 } 2782 2783 private void generateActivateEvent() { 2784 if (mActivateNeedsAnimation) { 2785 mAnimationEvents.add( 2786 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD)); 2787 } 2788 mActivateNeedsAnimation = false; 2789 } 2790 2791 private void generateAnimateEverythingEvent() { 2792 if (mEverythingNeedsAnimation) { 2793 mAnimationEvents.add( 2794 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING)); 2795 } 2796 mEverythingNeedsAnimation = false; 2797 } 2798 2799 private void generateDimmedEvent() { 2800 if (mDimmedNeedsAnimation) { 2801 mAnimationEvents.add( 2802 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED)); 2803 } 2804 mDimmedNeedsAnimation = false; 2805 } 2806 2807 private void generateHideSensitiveEvent() { 2808 if (mHideSensitiveNeedsAnimation) { 2809 mAnimationEvents.add( 2810 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE)); 2811 } 2812 mHideSensitiveNeedsAnimation = false; 2813 } 2814 2815 private void generateDarkEvent() { 2816 if (mDarkNeedsAnimation) { 2817 AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK); 2818 ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex; 2819 mAnimationEvents.add(ev); 2820 startBackgroundFadeIn(); 2821 } 2822 mDarkNeedsAnimation = false; 2823 } 2824 2825 private void generateGoToFullShadeEvent() { 2826 if (mGoToFullShadeNeedsAnimation) { 2827 mAnimationEvents.add( 2828 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE)); 2829 } 2830 mGoToFullShadeNeedsAnimation = false; 2831 } 2832 2833 private boolean onInterceptTouchEventScroll(MotionEvent ev) { 2834 if (!isScrollingEnabled()) { 2835 return false; 2836 } 2837 /* 2838 * This method JUST determines whether we want to intercept the motion. 2839 * If we return true, onMotionEvent will be called and we do the actual 2840 * scrolling there. 2841 */ 2842 2843 /* 2844 * Shortcut the most recurring case: the user is in the dragging 2845 * state and is moving their finger. We want to intercept this 2846 * motion. 2847 */ 2848 final int action = ev.getAction(); 2849 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { 2850 return true; 2851 } 2852 2853 switch (action & MotionEvent.ACTION_MASK) { 2854 case MotionEvent.ACTION_MOVE: { 2855 /* 2856 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 2857 * whether the user has moved far enough from the original down touch. 2858 */ 2859 2860 /* 2861 * Locally do absolute value. mLastMotionY is set to the y value 2862 * of the down event. 2863 */ 2864 final int activePointerId = mActivePointerId; 2865 if (activePointerId == INVALID_POINTER) { 2866 // If we don't have a valid id, the touch down wasn't on content. 2867 break; 2868 } 2869 2870 final int pointerIndex = ev.findPointerIndex(activePointerId); 2871 if (pointerIndex == -1) { 2872 Log.e(TAG, "Invalid pointerId=" + activePointerId 2873 + " in onInterceptTouchEvent"); 2874 break; 2875 } 2876 2877 final int y = (int) ev.getY(pointerIndex); 2878 final int x = (int) ev.getX(pointerIndex); 2879 final int yDiff = Math.abs(y - mLastMotionY); 2880 final int xDiff = Math.abs(x - mDownX); 2881 if (yDiff > mTouchSlop && yDiff > xDiff) { 2882 setIsBeingDragged(true); 2883 mLastMotionY = y; 2884 mDownX = x; 2885 initVelocityTrackerIfNotExists(); 2886 mVelocityTracker.addMovement(ev); 2887 } 2888 break; 2889 } 2890 2891 case MotionEvent.ACTION_DOWN: { 2892 final int y = (int) ev.getY(); 2893 mScrolledToTopOnFirstDown = isScrolledToTop(); 2894 if (getChildAtPosition(ev.getX(), y) == null) { 2895 setIsBeingDragged(false); 2896 recycleVelocityTracker(); 2897 break; 2898 } 2899 2900 /* 2901 * Remember location of down touch. 2902 * ACTION_DOWN always refers to pointer index 0. 2903 */ 2904 mLastMotionY = y; 2905 mDownX = (int) ev.getX(); 2906 mActivePointerId = ev.getPointerId(0); 2907 2908 initOrResetVelocityTracker(); 2909 mVelocityTracker.addMovement(ev); 2910 /* 2911 * If being flinged and user touches the screen, initiate drag; 2912 * otherwise don't. mScroller.isFinished should be false when 2913 * being flinged. 2914 */ 2915 boolean isBeingDragged = !mScroller.isFinished(); 2916 setIsBeingDragged(isBeingDragged); 2917 break; 2918 } 2919 2920 case MotionEvent.ACTION_CANCEL: 2921 case MotionEvent.ACTION_UP: 2922 /* Release the drag */ 2923 setIsBeingDragged(false); 2924 mActivePointerId = INVALID_POINTER; 2925 recycleVelocityTracker(); 2926 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { 2927 postInvalidateOnAnimation(); 2928 } 2929 break; 2930 case MotionEvent.ACTION_POINTER_UP: 2931 onSecondaryPointerUp(ev); 2932 break; 2933 } 2934 2935 /* 2936 * The only time we want to intercept motion events is if we are in the 2937 * drag mode. 2938 */ 2939 return mIsBeingDragged; 2940 } 2941 2942 /** 2943 * @return Whether the specified motion event is actually happening over the content. 2944 */ 2945 private boolean isInContentBounds(MotionEvent event) { 2946 return isInContentBounds(event.getY()); 2947 } 2948 2949 /** 2950 * @return Whether a y coordinate is inside the content. 2951 */ 2952 public boolean isInContentBounds(float y) { 2953 return y < getHeight() - getEmptyBottomMargin(); 2954 } 2955 2956 private void setIsBeingDragged(boolean isDragged) { 2957 mIsBeingDragged = isDragged; 2958 if (isDragged) { 2959 requestDisallowInterceptTouchEvent(true); 2960 removeLongPressCallback(); 2961 } 2962 } 2963 2964 @Override 2965 public void onWindowFocusChanged(boolean hasWindowFocus) { 2966 super.onWindowFocusChanged(hasWindowFocus); 2967 if (!hasWindowFocus) { 2968 removeLongPressCallback(); 2969 } 2970 } 2971 2972 @Override 2973 public void clearChildFocus(View child) { 2974 super.clearChildFocus(child); 2975 if (mForcedScroll == child) { 2976 mForcedScroll = null; 2977 } 2978 } 2979 2980 @Override 2981 public void requestDisallowLongPress() { 2982 removeLongPressCallback(); 2983 } 2984 2985 @Override 2986 public void requestDisallowDismiss() { 2987 mDisallowDismissInThisMotion = true; 2988 } 2989 2990 public void removeLongPressCallback() { 2991 mSwipeHelper.removeLongPressCallback(); 2992 } 2993 2994 @Override 2995 public boolean isScrolledToTop() { 2996 return mOwnScrollY == 0; 2997 } 2998 2999 @Override 3000 public boolean isScrolledToBottom() { 3001 return mOwnScrollY >= getScrollRange(); 3002 } 3003 3004 @Override 3005 public View getHostView() { 3006 return this; 3007 } 3008 3009 public int getEmptyBottomMargin() { 3010 int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize 3011 - mBottomStackSlowDownHeight; 3012 return Math.max(emptyMargin, 0); 3013 } 3014 3015 public float getKeyguardBottomStackSize() { 3016 return mBottomStackPeekSize + getResources().getDimensionPixelSize( 3017 R.dimen.bottom_stack_slow_down_length); 3018 } 3019 3020 public void onExpansionStarted() { 3021 mIsExpansionChanging = true; 3022 } 3023 3024 public void onExpansionStopped() { 3025 mIsExpansionChanging = false; 3026 if (!mIsExpanded) { 3027 mOwnScrollY = 0; 3028 mPhoneStatusBar.resetUserExpandedStates(); 3029 3030 // lets make sure nothing is in the overlay / transient anymore 3031 clearTemporaryViews(this); 3032 for (int i = 0; i < getChildCount(); i++) { 3033 ExpandableView child = (ExpandableView) getChildAt(i); 3034 if (child instanceof ExpandableNotificationRow) { 3035 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 3036 clearTemporaryViews(row.getChildrenContainer()); 3037 } 3038 } 3039 } 3040 } 3041 3042 private void clearTemporaryViews(ViewGroup viewGroup) { 3043 while (viewGroup != null && viewGroup.getTransientViewCount() != 0) { 3044 viewGroup.removeTransientView(viewGroup.getTransientView(0)); 3045 } 3046 if (viewGroup != null) { 3047 viewGroup.getOverlay().clear(); 3048 } 3049 } 3050 3051 public void onPanelTrackingStarted() { 3052 mPanelTracking = true; 3053 } 3054 public void onPanelTrackingStopped() { 3055 mPanelTracking = false; 3056 } 3057 3058 public void resetScrollPosition() { 3059 mScroller.abortAnimation(); 3060 mOwnScrollY = 0; 3061 } 3062 3063 private void setIsExpanded(boolean isExpanded) { 3064 boolean changed = isExpanded != mIsExpanded; 3065 mIsExpanded = isExpanded; 3066 mStackScrollAlgorithm.setIsExpanded(isExpanded); 3067 if (changed) { 3068 if (!mIsExpanded) { 3069 mGroupManager.collapseAllGroups(); 3070 } 3071 updateNotificationAnimationStates(); 3072 updateChronometers(); 3073 } 3074 } 3075 3076 private void updateChronometers() { 3077 int childCount = getChildCount(); 3078 for (int i = 0; i < childCount; i++) { 3079 updateChronometerForChild(getChildAt(i)); 3080 } 3081 } 3082 3083 private void updateChronometerForChild(View child) { 3084 if (child instanceof ExpandableNotificationRow) { 3085 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 3086 row.setChronometerRunning(mIsExpanded); 3087 } 3088 } 3089 3090 @Override 3091 public void onHeightChanged(ExpandableView view, boolean needsAnimation) { 3092 updateContentHeight(); 3093 updateScrollPositionOnExpandInBottom(view); 3094 clampScrollPosition(); 3095 notifyHeightChangeListener(view); 3096 if (needsAnimation) { 3097 ExpandableNotificationRow row = view instanceof ExpandableNotificationRow 3098 ? (ExpandableNotificationRow) view 3099 : null; 3100 requestAnimationOnViewResize(row); 3101 } 3102 requestChildrenUpdate(); 3103 } 3104 3105 @Override 3106 public void onReset(ExpandableView view) { 3107 if (mIsExpanded && mAnimationsEnabled) { 3108 mRequestViewResizeAnimationOnLayout = true; 3109 } 3110 updateAnimationState(view); 3111 updateChronometerForChild(view); 3112 } 3113 3114 private void updateScrollPositionOnExpandInBottom(ExpandableView view) { 3115 if (view instanceof ExpandableNotificationRow) { 3116 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 3117 if (row.isUserLocked() && row != getFirstChildNotGone()) { 3118 if (row.isSummaryWithChildren()) { 3119 return; 3120 } 3121 // We are actually expanding this view 3122 float endPosition = row.getTranslationY() + row.getActualHeight(); 3123 if (row.isChildInGroup()) { 3124 endPosition += row.getNotificationParent().getTranslationY(); 3125 } 3126 int stackEnd = getStackEndPosition(); 3127 if (endPosition > stackEnd) { 3128 mOwnScrollY += endPosition - stackEnd; 3129 mDisallowScrollingInThisMotion = true; 3130 } 3131 } 3132 } 3133 } 3134 3135 private int getStackEndPosition() { 3136 return mMaxLayoutHeight - mBottomStackPeekSize - mBottomStackSlowDownHeight 3137 + mPaddingBetweenElements + (int) mStackTranslation; 3138 } 3139 3140 public void setOnHeightChangedListener( 3141 ExpandableView.OnHeightChangedListener mOnHeightChangedListener) { 3142 this.mOnHeightChangedListener = mOnHeightChangedListener; 3143 } 3144 3145 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) { 3146 mOnEmptySpaceClickListener = listener; 3147 } 3148 3149 public void onChildAnimationFinished() { 3150 setAnimationRunning(false); 3151 requestChildrenUpdate(); 3152 runAnimationFinishedRunnables(); 3153 clearViewOverlays(); 3154 clearHeadsUpDisappearRunning(); 3155 } 3156 3157 private void clearHeadsUpDisappearRunning() { 3158 for (int i = 0; i < getChildCount(); i++) { 3159 View view = getChildAt(i); 3160 if (view instanceof ExpandableNotificationRow) { 3161 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 3162 row.setHeadsupDisappearRunning(false); 3163 if (row.isSummaryWithChildren()) { 3164 for (ExpandableNotificationRow child : row.getNotificationChildren()) { 3165 child.setHeadsupDisappearRunning(false); 3166 } 3167 } 3168 } 3169 } 3170 } 3171 3172 private void clearViewOverlays() { 3173 for (View view : mClearOverlayViewsWhenFinished) { 3174 StackStateAnimator.removeFromOverlay(view); 3175 } 3176 } 3177 3178 private void runAnimationFinishedRunnables() { 3179 for (Runnable runnable : mAnimationFinishedRunnables) { 3180 runnable.run(); 3181 } 3182 mAnimationFinishedRunnables.clear(); 3183 } 3184 3185 /** 3186 * See {@link AmbientState#setDimmed}. 3187 */ 3188 public void setDimmed(boolean dimmed, boolean animate) { 3189 mAmbientState.setDimmed(dimmed); 3190 if (animate && mAnimationsEnabled) { 3191 mDimmedNeedsAnimation = true; 3192 mNeedsAnimation = true; 3193 animateDimmed(dimmed); 3194 } else { 3195 setDimAmount(dimmed ? 1.0f : 0.0f); 3196 } 3197 requestChildrenUpdate(); 3198 } 3199 3200 private void setDimAmount(float dimAmount) { 3201 mDimAmount = dimAmount; 3202 updateBackgroundDimming(); 3203 } 3204 3205 private void animateDimmed(boolean dimmed) { 3206 if (mDimAnimator != null) { 3207 mDimAnimator.cancel(); 3208 } 3209 float target = dimmed ? 1.0f : 0.0f; 3210 if (target == mDimAmount) { 3211 return; 3212 } 3213 mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target); 3214 mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED); 3215 mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 3216 mDimAnimator.addListener(mDimEndListener); 3217 mDimAnimator.addUpdateListener(mDimUpdateListener); 3218 mDimAnimator.start(); 3219 } 3220 3221 public void setHideSensitive(boolean hideSensitive, boolean animate) { 3222 if (hideSensitive != mAmbientState.isHideSensitive()) { 3223 int childCount = getChildCount(); 3224 for (int i = 0; i < childCount; i++) { 3225 ExpandableView v = (ExpandableView) getChildAt(i); 3226 v.setHideSensitiveForIntrinsicHeight(hideSensitive); 3227 } 3228 mAmbientState.setHideSensitive(hideSensitive); 3229 if (animate && mAnimationsEnabled) { 3230 mHideSensitiveNeedsAnimation = true; 3231 mNeedsAnimation = true; 3232 } 3233 requestChildrenUpdate(); 3234 } 3235 } 3236 3237 /** 3238 * See {@link AmbientState#setActivatedChild}. 3239 */ 3240 public void setActivatedChild(ActivatableNotificationView activatedChild) { 3241 mAmbientState.setActivatedChild(activatedChild); 3242 if (mAnimationsEnabled) { 3243 mActivateNeedsAnimation = true; 3244 mNeedsAnimation = true; 3245 } 3246 requestChildrenUpdate(); 3247 } 3248 3249 public ActivatableNotificationView getActivatedChild() { 3250 return mAmbientState.getActivatedChild(); 3251 } 3252 3253 private void applyCurrentState() { 3254 mCurrentStackScrollState.apply(); 3255 if (mListener != null) { 3256 mListener.onChildLocationsChanged(this); 3257 } 3258 runAnimationFinishedRunnables(); 3259 setAnimationRunning(false); 3260 updateBackground(); 3261 updateViewShadows(); 3262 } 3263 3264 private void updateViewShadows() { 3265 // we need to work around an issue where the shadow would not cast between siblings when 3266 // their z difference is between 0 and 0.1 3267 3268 // Lefts first sort by Z difference 3269 for (int i = 0; i < getChildCount(); i++) { 3270 ExpandableView child = (ExpandableView) getChildAt(i); 3271 if (child.getVisibility() != GONE) { 3272 mTmpSortedChildren.add(child); 3273 } 3274 } 3275 Collections.sort(mTmpSortedChildren, mViewPositionComparator); 3276 3277 // Now lets update the shadow for the views 3278 ExpandableView previous = null; 3279 for (int i = 0; i < mTmpSortedChildren.size(); i++) { 3280 ExpandableView expandableView = mTmpSortedChildren.get(i); 3281 float translationZ = expandableView.getTranslationZ(); 3282 float otherZ = previous == null ? translationZ : previous.getTranslationZ(); 3283 float diff = otherZ - translationZ; 3284 if (diff <= 0.0f || diff >= FakeShadowView.SHADOW_SIBLING_TRESHOLD) { 3285 // There is no fake shadow to be drawn 3286 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0); 3287 } else { 3288 float yLocation = previous.getTranslationY() + previous.getActualHeight() - 3289 expandableView.getTranslationY() - previous.getExtraBottomPadding(); 3290 expandableView.setFakeShadowIntensity( 3291 diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD, 3292 previous.getOutlineAlpha(), (int) yLocation, 3293 previous.getOutlineTranslation()); 3294 } 3295 previous = expandableView; 3296 } 3297 3298 mTmpSortedChildren.clear(); 3299 } 3300 3301 public void goToFullShade(long delay) { 3302 mDismissView.setInvisible(); 3303 mEmptyShadeView.setInvisible(); 3304 mGoToFullShadeNeedsAnimation = true; 3305 mGoToFullShadeDelay = delay; 3306 mNeedsAnimation = true; 3307 requestChildrenUpdate(); 3308 } 3309 3310 public void cancelExpandHelper() { 3311 mExpandHelper.cancel(); 3312 } 3313 3314 public void setIntrinsicPadding(int intrinsicPadding) { 3315 mIntrinsicPadding = intrinsicPadding; 3316 } 3317 3318 public int getIntrinsicPadding() { 3319 return mIntrinsicPadding; 3320 } 3321 3322 /** 3323 * @return the y position of the first notification 3324 */ 3325 public float getNotificationsTopY() { 3326 return mTopPadding + getStackTranslation(); 3327 } 3328 3329 @Override 3330 public boolean shouldDelayChildPressedState() { 3331 return true; 3332 } 3333 3334 /** 3335 * See {@link AmbientState#setDark}. 3336 */ 3337 public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) { 3338 mAmbientState.setDark(dark); 3339 if (animate && mAnimationsEnabled) { 3340 mDarkNeedsAnimation = true; 3341 mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation); 3342 mNeedsAnimation = true; 3343 setBackgroundFadeAmount(0.0f); 3344 } else if (!dark) { 3345 setBackgroundFadeAmount(1.0f); 3346 } 3347 requestChildrenUpdate(); 3348 if (dark) { 3349 setWillNotDraw(!DEBUG); 3350 mScrimController.setExcludedBackgroundArea(null); 3351 } else { 3352 updateBackground(); 3353 setWillNotDraw(false); 3354 } 3355 } 3356 3357 private void setBackgroundFadeAmount(float fadeAmount) { 3358 mBackgroundFadeAmount = fadeAmount; 3359 updateBackgroundDimming(); 3360 } 3361 3362 public float getBackgroundFadeAmount() { 3363 return mBackgroundFadeAmount; 3364 } 3365 3366 private void startBackgroundFadeIn() { 3367 ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(this, BACKGROUND_FADE, 0f, 1f); 3368 int maxLength; 3369 if (mDarkAnimationOriginIndex == AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE 3370 || mDarkAnimationOriginIndex == AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) { 3371 maxLength = getNotGoneChildCount() - 1; 3372 } else { 3373 maxLength = Math.max(mDarkAnimationOriginIndex, 3374 getNotGoneChildCount() - mDarkAnimationOriginIndex - 1); 3375 } 3376 maxLength = Math.max(0, maxLength); 3377 long delay = maxLength * StackStateAnimator.ANIMATION_DELAY_PER_ELEMENT_DARK; 3378 fadeAnimator.setStartDelay(delay); 3379 fadeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 3380 fadeAnimator.setInterpolator(Interpolators.ALPHA_IN); 3381 fadeAnimator.start(); 3382 } 3383 3384 private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) { 3385 if (screenLocation == null || screenLocation.y < mTopPadding + mTopPaddingOverflow) { 3386 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE; 3387 } 3388 if (screenLocation.y > getBottomMostNotificationBottom()) { 3389 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW; 3390 } 3391 View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y); 3392 if (child != null) { 3393 return getNotGoneIndex(child); 3394 } else { 3395 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE; 3396 } 3397 } 3398 3399 private int getNotGoneIndex(View child) { 3400 int count = getChildCount(); 3401 int notGoneIndex = 0; 3402 for (int i = 0; i < count; i++) { 3403 View v = getChildAt(i); 3404 if (child == v) { 3405 return notGoneIndex; 3406 } 3407 if (v.getVisibility() != View.GONE) { 3408 notGoneIndex++; 3409 } 3410 } 3411 return -1; 3412 } 3413 3414 public void setDismissView(DismissView dismissView) { 3415 int index = -1; 3416 if (mDismissView != null) { 3417 index = indexOfChild(mDismissView); 3418 removeView(mDismissView); 3419 } 3420 mDismissView = dismissView; 3421 addView(mDismissView, index); 3422 } 3423 3424 public void setEmptyShadeView(EmptyShadeView emptyShadeView) { 3425 int index = -1; 3426 if (mEmptyShadeView != null) { 3427 index = indexOfChild(mEmptyShadeView); 3428 removeView(mEmptyShadeView); 3429 } 3430 mEmptyShadeView = emptyShadeView; 3431 addView(mEmptyShadeView, index); 3432 } 3433 3434 public void updateEmptyShadeView(boolean visible) { 3435 int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility(); 3436 int newVisibility = visible ? VISIBLE : GONE; 3437 if (oldVisibility != newVisibility) { 3438 if (newVisibility != GONE) { 3439 if (mEmptyShadeView.willBeGone()) { 3440 mEmptyShadeView.cancelAnimation(); 3441 } else { 3442 mEmptyShadeView.setInvisible(); 3443 } 3444 mEmptyShadeView.setVisibility(newVisibility); 3445 mEmptyShadeView.setWillBeGone(false); 3446 updateContentHeight(); 3447 notifyHeightChangeListener(mEmptyShadeView); 3448 } else { 3449 Runnable onFinishedRunnable = new Runnable() { 3450 @Override 3451 public void run() { 3452 mEmptyShadeView.setVisibility(GONE); 3453 mEmptyShadeView.setWillBeGone(false); 3454 updateContentHeight(); 3455 notifyHeightChangeListener(mEmptyShadeView); 3456 } 3457 }; 3458 if (mAnimationsEnabled && mIsExpanded) { 3459 mEmptyShadeView.setWillBeGone(true); 3460 mEmptyShadeView.performVisibilityAnimation(false, onFinishedRunnable); 3461 } else { 3462 mEmptyShadeView.setInvisible(); 3463 onFinishedRunnable.run(); 3464 } 3465 } 3466 } 3467 } 3468 3469 public void setOverflowContainer(NotificationOverflowContainer overFlowContainer) { 3470 int index = -1; 3471 if (mOverflowContainer != null) { 3472 index = indexOfChild(mOverflowContainer); 3473 removeView(mOverflowContainer); 3474 } 3475 mOverflowContainer = overFlowContainer; 3476 addView(mOverflowContainer, index); 3477 } 3478 3479 public void updateOverflowContainerVisibility(boolean visible) { 3480 int oldVisibility = mOverflowContainer.willBeGone() ? GONE 3481 : mOverflowContainer.getVisibility(); 3482 final int newVisibility = visible ? VISIBLE : GONE; 3483 if (oldVisibility != newVisibility) { 3484 Runnable onFinishedRunnable = new Runnable() { 3485 @Override 3486 public void run() { 3487 mOverflowContainer.setVisibility(newVisibility); 3488 mOverflowContainer.setWillBeGone(false); 3489 updateContentHeight(); 3490 notifyHeightChangeListener(mOverflowContainer); 3491 } 3492 }; 3493 if (!mAnimationsEnabled || !mIsExpanded) { 3494 mOverflowContainer.cancelAppearDrawing(); 3495 onFinishedRunnable.run(); 3496 } else if (newVisibility != GONE) { 3497 mOverflowContainer.performAddAnimation(0, 3498 StackStateAnimator.ANIMATION_DURATION_STANDARD); 3499 mOverflowContainer.setVisibility(newVisibility); 3500 mOverflowContainer.setWillBeGone(false); 3501 updateContentHeight(); 3502 notifyHeightChangeListener(mOverflowContainer); 3503 } else { 3504 mOverflowContainer.performRemoveAnimation( 3505 StackStateAnimator.ANIMATION_DURATION_STANDARD, 3506 0.0f, 3507 onFinishedRunnable); 3508 mOverflowContainer.setWillBeGone(true); 3509 } 3510 } 3511 } 3512 3513 public void updateDismissView(boolean visible) { 3514 int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility(); 3515 int newVisibility = visible ? VISIBLE : GONE; 3516 if (oldVisibility != newVisibility) { 3517 if (newVisibility != GONE) { 3518 if (mDismissView.willBeGone()) { 3519 mDismissView.cancelAnimation(); 3520 } else { 3521 mDismissView.setInvisible(); 3522 } 3523 mDismissView.setVisibility(newVisibility); 3524 mDismissView.setWillBeGone(false); 3525 updateContentHeight(); 3526 notifyHeightChangeListener(mDismissView); 3527 } else { 3528 Runnable dimissHideFinishRunnable = new Runnable() { 3529 @Override 3530 public void run() { 3531 mDismissView.setVisibility(GONE); 3532 mDismissView.setWillBeGone(false); 3533 updateContentHeight(); 3534 notifyHeightChangeListener(mDismissView); 3535 } 3536 }; 3537 if (mDismissView.isButtonVisible() && mIsExpanded && mAnimationsEnabled) { 3538 mDismissView.setWillBeGone(true); 3539 mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable); 3540 } else { 3541 dimissHideFinishRunnable.run(); 3542 } 3543 } 3544 } 3545 } 3546 3547 public void setDismissAllInProgress(boolean dismissAllInProgress) { 3548 mDismissAllInProgress = dismissAllInProgress; 3549 mAmbientState.setDismissAllInProgress(dismissAllInProgress); 3550 handleDismissAllClipping(); 3551 } 3552 3553 private void handleDismissAllClipping() { 3554 final int count = getChildCount(); 3555 boolean previousChildWillBeDismissed = false; 3556 for (int i = 0; i < count; i++) { 3557 ExpandableView child = (ExpandableView) getChildAt(i); 3558 if (child.getVisibility() == GONE) { 3559 continue; 3560 } 3561 if (mDismissAllInProgress && previousChildWillBeDismissed) { 3562 child.setMinClipTopAmount(child.getClipTopAmount()); 3563 } else { 3564 child.setMinClipTopAmount(0); 3565 } 3566 previousChildWillBeDismissed = canChildBeDismissed(child); 3567 } 3568 } 3569 3570 public boolean isDismissViewNotGone() { 3571 return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone(); 3572 } 3573 3574 public boolean isDismissViewVisible() { 3575 return mDismissView.isVisible(); 3576 } 3577 3578 public int getDismissViewHeight() { 3579 return mDismissView.getHeight() + mPaddingBetweenElements; 3580 } 3581 3582 public int getEmptyShadeViewHeight() { 3583 return mEmptyShadeView.getHeight(); 3584 } 3585 3586 public float getBottomMostNotificationBottom() { 3587 final int count = getChildCount(); 3588 float max = 0; 3589 for (int childIdx = 0; childIdx < count; childIdx++) { 3590 ExpandableView child = (ExpandableView) getChildAt(childIdx); 3591 if (child.getVisibility() == GONE) { 3592 continue; 3593 } 3594 float bottom = child.getTranslationY() + child.getActualHeight(); 3595 if (bottom > max) { 3596 max = bottom; 3597 } 3598 } 3599 return max + getStackTranslation(); 3600 } 3601 3602 public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) { 3603 this.mPhoneStatusBar = phoneStatusBar; 3604 } 3605 3606 public void setGroupManager(NotificationGroupManager groupManager) { 3607 this.mGroupManager = groupManager; 3608 } 3609 3610 public void onGoToKeyguard() { 3611 requestAnimateEverything(); 3612 } 3613 3614 private void requestAnimateEverything() { 3615 if (mIsExpanded && mAnimationsEnabled) { 3616 mEverythingNeedsAnimation = true; 3617 mNeedsAnimation = true; 3618 requestChildrenUpdate(); 3619 } 3620 } 3621 3622 public boolean isBelowLastNotification(float touchX, float touchY) { 3623 int childCount = getChildCount(); 3624 for (int i = childCount - 1; i >= 0; i--) { 3625 ExpandableView child = (ExpandableView) getChildAt(i); 3626 if (child.getVisibility() != View.GONE) { 3627 float childTop = child.getY(); 3628 if (childTop > touchY) { 3629 // we are above a notification entirely let's abort 3630 return false; 3631 } 3632 boolean belowChild = touchY > childTop + child.getActualHeight(); 3633 if (child == mDismissView) { 3634 if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(), 3635 touchY - childTop)) { 3636 // We clicked on the dismiss button 3637 return false; 3638 } 3639 } else if (child == mEmptyShadeView) { 3640 // We arrived at the empty shade view, for which we accept all clicks 3641 return true; 3642 } else if (!belowChild){ 3643 // We are on a child 3644 return false; 3645 } 3646 } 3647 } 3648 return touchY > mTopPadding + mStackTranslation; 3649 } 3650 3651 @Override 3652 public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) { 3653 boolean animated = !mGroupExpandedForMeasure && mAnimationsEnabled 3654 && (mIsExpanded || changedRow.isPinned()); 3655 if (animated) { 3656 mExpandedGroupView = changedRow; 3657 mNeedsAnimation = true; 3658 } 3659 changedRow.setChildrenExpanded(expanded, animated); 3660 if (!mGroupExpandedForMeasure) { 3661 onHeightChanged(changedRow, false /* needsAnimation */); 3662 } 3663 runAfterAnimationFinished(new Runnable() { 3664 @Override 3665 public void run() { 3666 changedRow.onFinishedExpansionChange(); 3667 } 3668 }); 3669 } 3670 3671 @Override 3672 public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) { 3673 mPhoneStatusBar.requestNotificationUpdate(); 3674 } 3675 3676 /** @hide */ 3677 @Override 3678 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 3679 super.onInitializeAccessibilityEventInternal(event); 3680 event.setScrollable(mScrollable); 3681 event.setScrollX(mScrollX); 3682 event.setScrollY(mOwnScrollY); 3683 event.setMaxScrollX(mScrollX); 3684 event.setMaxScrollY(getScrollRange()); 3685 } 3686 3687 @Override 3688 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 3689 super.onInitializeAccessibilityNodeInfoInternal(info); 3690 final int scrollRange = getScrollRange(); 3691 if (scrollRange > 0) { 3692 info.setScrollable(true); 3693 if (mScrollY > 0) { 3694 info.addAction( 3695 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD); 3696 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP); 3697 } 3698 if (mScrollY < scrollRange) { 3699 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD); 3700 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_DOWN); 3701 } 3702 } 3703 // Talkback only listenes to scroll events of certain classes, let's make us a scrollview 3704 info.setClassName(ScrollView.class.getName()); 3705 } 3706 3707 /** @hide */ 3708 @Override 3709 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 3710 if (super.performAccessibilityActionInternal(action, arguments)) { 3711 return true; 3712 } 3713 if (!isEnabled()) { 3714 return false; 3715 } 3716 int direction = -1; 3717 switch (action) { 3718 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: 3719 // fall through 3720 case android.R.id.accessibilityActionScrollDown: 3721 direction = 1; 3722 // fall through 3723 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: 3724 // fall through 3725 case android.R.id.accessibilityActionScrollUp: 3726 final int viewportHeight = getHeight() - mPaddingBottom - mTopPadding - mPaddingTop 3727 - mBottomStackPeekSize - mBottomStackSlowDownHeight; 3728 final int targetScrollY = Math.max(0, 3729 Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange())); 3730 if (targetScrollY != mOwnScrollY) { 3731 mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScrollY - mOwnScrollY); 3732 postInvalidateOnAnimation(); 3733 return true; 3734 } 3735 break; 3736 } 3737 return false; 3738 } 3739 3740 @Override 3741 public void onGroupsChanged() { 3742 mPhoneStatusBar.requestNotificationUpdate(); 3743 } 3744 3745 public void generateChildOrderChangedEvent() { 3746 if (mIsExpanded && mAnimationsEnabled) { 3747 mGenerateChildOrderChangedEvent = true; 3748 mNeedsAnimation = true; 3749 requestChildrenUpdate(); 3750 } 3751 } 3752 3753 public void runAfterAnimationFinished(Runnable runnable) { 3754 mAnimationFinishedRunnables.add(runnable); 3755 } 3756 3757 public void setHeadsUpManager(HeadsUpManager headsUpManager) { 3758 mHeadsUpManager = headsUpManager; 3759 mAmbientState.setHeadsUpManager(headsUpManager); 3760 } 3761 3762 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) { 3763 if (mAnimationsEnabled) { 3764 mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp)); 3765 mNeedsAnimation = true; 3766 if (!mIsExpanded && !isHeadsUp) { 3767 row.setHeadsupDisappearRunning(true); 3768 } 3769 requestChildrenUpdate(); 3770 } 3771 } 3772 3773 public void setShadeExpanded(boolean shadeExpanded) { 3774 mAmbientState.setShadeExpanded(shadeExpanded); 3775 mStateAnimator.setShadeExpanded(shadeExpanded); 3776 } 3777 3778 /** 3779 * Set the boundary for the bottom heads up position. The heads up will always be above this 3780 * position. 3781 * 3782 * @param height the height of the screen 3783 * @param bottomBarHeight the height of the bar on the bottom 3784 */ 3785 public void setHeadsUpBoundaries(int height, int bottomBarHeight) { 3786 mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight); 3787 mStateAnimator.setHeadsUpAppearHeightBottom(height); 3788 requestChildrenUpdate(); 3789 } 3790 3791 public void setTrackingHeadsUp(boolean trackingHeadsUp) { 3792 mTrackingHeadsUp = trackingHeadsUp; 3793 } 3794 3795 public void setScrimController(ScrimController scrimController) { 3796 mScrimController = scrimController; 3797 mScrimController.setScrimBehindChangeRunnable(new Runnable() { 3798 @Override 3799 public void run() { 3800 updateBackgroundDimming(); 3801 } 3802 }); 3803 } 3804 3805 public void forceNoOverlappingRendering(boolean force) { 3806 mForceNoOverlappingRendering = force; 3807 } 3808 3809 @Override 3810 public boolean hasOverlappingRendering() { 3811 return !mForceNoOverlappingRendering && super.hasOverlappingRendering(); 3812 } 3813 3814 public void setAnimationRunning(boolean animationRunning) { 3815 if (animationRunning != mAnimationRunning) { 3816 if (animationRunning) { 3817 getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater); 3818 } else { 3819 getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater); 3820 } 3821 mAnimationRunning = animationRunning; 3822 updateContinuousShadowDrawing(); 3823 } 3824 } 3825 3826 public boolean isExpanded() { 3827 return mIsExpanded; 3828 } 3829 3830 public void setPulsing(boolean pulsing) { 3831 mPulsing = pulsing; 3832 updateNotificationAnimationStates(); 3833 } 3834 3835 public void setFadingOut(boolean fadingOut) { 3836 if (fadingOut != mFadingOut) { 3837 mFadingOut = fadingOut; 3838 updateFadingState(); 3839 } 3840 } 3841 3842 public void setParentFadingOut(boolean fadingOut) { 3843 if (fadingOut != mParentFadingOut) { 3844 mParentFadingOut = fadingOut; 3845 updateFadingState(); 3846 } 3847 } 3848 3849 private void updateFadingState() { 3850 if (mFadingOut || mParentFadingOut || mAmbientState.isDark()) { 3851 mScrimController.setExcludedBackgroundArea(null); 3852 } else { 3853 applyCurrentBackgroundBounds(); 3854 } 3855 updateSrcDrawing(); 3856 } 3857 3858 @Override 3859 public void setAlpha(@FloatRange(from = 0.0, to = 1.0) float alpha) { 3860 super.setAlpha(alpha); 3861 setFadingOut(alpha != 1.0f); 3862 } 3863 3864 /** 3865 * Remove the a given view from the viewstate. This is currently used when the children are 3866 * kept in the parent artificially to have a nicer animation. 3867 * @param view the view to remove 3868 */ 3869 public void removeViewStateForView(View view) { 3870 mCurrentStackScrollState.removeViewStateForView(view); 3871 } 3872 3873 /** 3874 * A listener that is notified when some child locations might have changed. 3875 */ 3876 public interface OnChildLocationsChangedListener { 3877 public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout); 3878 } 3879 3880 /** 3881 * A listener that is notified when the empty space below the notifications is clicked on 3882 */ 3883 public interface OnEmptySpaceClickListener { 3884 public void onEmptySpaceClicked(float x, float y); 3885 } 3886 3887 /** 3888 * A listener that gets notified when the overscroll at the top has changed. 3889 */ 3890 public interface OnOverscrollTopChangedListener { 3891 3892 /** 3893 * Notifies a listener that the overscroll has changed. 3894 * 3895 * @param amount the amount of overscroll, in pixels 3896 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an 3897 * unrubberbanded motion to directly expand overscroll view (e.g expand 3898 * QS) 3899 */ 3900 public void onOverscrollTopChanged(float amount, boolean isRubberbanded); 3901 3902 /** 3903 * Notify a listener that the scroller wants to escape from the scrolling motion and 3904 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS) 3905 * 3906 * @param velocity The velocity that the Scroller had when over flinging 3907 * @param open Should the fling open or close the overscroll view. 3908 */ 3909 public void flingTopOverscroll(float velocity, boolean open); 3910 } 3911 3912 private class NotificationSwipeHelper extends SwipeHelper { 3913 private static final long SHOW_GEAR_DELAY = 60; 3914 private static final long COVER_GEAR_DELAY = 4000; 3915 private CheckForDrag mCheckForDrag; 3916 private Runnable mFalsingCheck; 3917 private Handler mHandler; 3918 private boolean mGearSnappedTo; 3919 private boolean mGearSnappedOnLeft; 3920 3921 public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) { 3922 super(swipeDirection, callback, context); 3923 mHandler = new Handler(); 3924 mFalsingCheck = new Runnable() { 3925 @Override 3926 public void run() { 3927 resetExposedGearView(true /* animate */, true /* force */); 3928 } 3929 }; 3930 } 3931 3932 @Override 3933 public void onDownUpdate(View currView) { 3934 // Set the active view 3935 mTranslatingParentView = currView; 3936 3937 // Reset check for drag gesture 3938 cancelCheckForDrag(); 3939 if (mCurrIconRow != null) { 3940 mCurrIconRow.setSnapping(false); 3941 } 3942 mCheckForDrag = null; 3943 mCurrIconRow = null; 3944 mHandler.removeCallbacks(mFalsingCheck); 3945 3946 // Slide back any notifications that might be showing a gear 3947 resetExposedGearView(true /* animate */, false /* force */); 3948 3949 if (currView instanceof ExpandableNotificationRow) { 3950 // Set the listener for the current row's gear 3951 mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow(); 3952 mCurrIconRow.setGearListener(NotificationStackScrollLayout.this); 3953 } 3954 } 3955 3956 @Override 3957 public void onMoveUpdate(View view, float translation, float delta) { 3958 mHandler.removeCallbacks(mFalsingCheck); 3959 3960 if (mCurrIconRow != null) { 3961 mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping. 3962 3963 // If the gear is visible and the movement is towards it it's not a location change. 3964 boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isIconOnLeft(); 3965 boolean locationChange = isTowardsGear(translation, onLeft) 3966 ? false : mCurrIconRow.isIconLocationChange(translation); 3967 if (locationChange) { 3968 // Don't consider it "snapped" if location has changed. 3969 setSnappedToGear(false); 3970 3971 // Changed directions, make sure we check to fade in icon again. 3972 if (!mHandler.hasCallbacks(mCheckForDrag)) { 3973 // No check scheduled, set null to schedule a new one. 3974 mCheckForDrag = null; 3975 } else { 3976 // Check scheduled, reset alpha and update location; check will fade it in 3977 mCurrIconRow.setGearAlpha(0f); 3978 mCurrIconRow.setIconLocation(translation > 0 /* onLeft */); 3979 } 3980 } 3981 } 3982 3983 final boolean gutsExposed = (view instanceof ExpandableNotificationRow) 3984 && ((ExpandableNotificationRow) view).areGutsExposed(); 3985 3986 if (!isPinnedHeadsUp(view) && !gutsExposed) { 3987 // Only show the gear if we're not a heads up view and guts aren't exposed. 3988 checkForDrag(); 3989 } 3990 } 3991 3992 @Override 3993 public void dismissChild(final View view, float velocity, 3994 boolean useAccelerateInterpolator) { 3995 super.dismissChild(view, velocity, useAccelerateInterpolator); 3996 if (mIsExpanded) { 3997 // We don't want to quick-dismiss when it's a heads up as this might lead to closing 3998 // of the panel early. 3999 handleChildDismissed(view); 4000 } 4001 handleGearCoveredOrDismissed(); 4002 } 4003 4004 @Override 4005 public void snapChild(final View animView, final float targetLeft, float velocity) { 4006 super.snapChild(animView, targetLeft, velocity); 4007 onDragCancelled(animView); 4008 if (targetLeft == 0) { 4009 handleGearCoveredOrDismissed(); 4010 } 4011 } 4012 4013 private void handleGearCoveredOrDismissed() { 4014 cancelCheckForDrag(); 4015 setSnappedToGear(false); 4016 if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) { 4017 mGearExposedView = null; 4018 } 4019 } 4020 4021 @Override 4022 public boolean handleUpEvent(MotionEvent ev, View animView, float velocity, 4023 float translation) { 4024 if (mCurrIconRow == null) { 4025 cancelCheckForDrag(); 4026 return false; // Let SwipeHelper handle it. 4027 } 4028 4029 boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isIconOnLeft()); 4030 boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity(); 4031 4032 if (mGearSnappedTo && mCurrIconRow.isVisible()) { 4033 if (mGearSnappedOnLeft == mCurrIconRow.isIconOnLeft()) { 4034 boolean coveringGear = 4035 Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f; 4036 if (gestureTowardsGear || coveringGear) { 4037 // Gesture is towards or covering the gear 4038 snapChild(animView, 0 /* leftTarget */, velocity); 4039 } else if (isDismissGesture(ev)) { 4040 // Gesture is a dismiss that's not towards the gear 4041 dismissChild(animView, velocity, 4042 !swipedFastEnough() /* useAccelerateInterpolator */); 4043 } else { 4044 // Didn't move enough to dismiss or cover, snap to the gear 4045 snapToGear(animView, velocity); 4046 } 4047 } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView)) 4048 || (gestureTowardsGear && !swipedFarEnough())) { 4049 // The gear has been snapped to previously, however, the gear is now on the 4050 // other side. If gesture is towards gear and not too far snap to the gear. 4051 snapToGear(animView, velocity); 4052 } else { 4053 dismissOrSnapBack(animView, velocity, ev); 4054 } 4055 } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView)) 4056 || gestureTowardsGear) { 4057 // Gear has not been snapped to previously and this is gear revealing gesture 4058 snapToGear(animView, velocity); 4059 } else { 4060 dismissOrSnapBack(animView, velocity, ev); 4061 } 4062 return true; 4063 } 4064 4065 private void dismissOrSnapBack(View animView, float velocity, MotionEvent ev) { 4066 if (isDismissGesture(ev)) { 4067 dismissChild(animView, velocity, 4068 !swipedFastEnough() /* useAccelerateInterpolator */); 4069 } else { 4070 snapChild(animView, 0 /* leftTarget */, velocity); 4071 } 4072 } 4073 4074 private void snapToGear(View animView, float velocity) { 4075 final float snapBackThreshold = getSpaceForGear(animView); 4076 final float target = mCurrIconRow.isIconOnLeft() ? snapBackThreshold 4077 : -snapBackThreshold; 4078 mGearExposedView = mTranslatingParentView; 4079 if (animView instanceof ExpandableNotificationRow) { 4080 MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR, 4081 ((ExpandableNotificationRow) animView).getStatusBarNotification() 4082 .getPackageName()); 4083 } 4084 if (mCurrIconRow != null) { 4085 mCurrIconRow.setSnapping(true); 4086 setSnappedToGear(true); 4087 } 4088 onDragCancelled(animView); 4089 4090 // If we're on the lockscreen we want to false this. 4091 if (mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD) { 4092 mHandler.removeCallbacks(mFalsingCheck); 4093 mHandler.postDelayed(mFalsingCheck, COVER_GEAR_DELAY); 4094 } 4095 super.snapChild(animView, target, velocity); 4096 } 4097 4098 private boolean swipedEnoughToShowGear(View animView) { 4099 if (mTranslatingParentView == null) { 4100 return false; 4101 } 4102 // If the notification can't be dismissed then how far it can move is 4103 // restricted -- reduce the distance it needs to move in this case. 4104 final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f; 4105 final float snapBackThreshold = getSpaceForGear(animView) * multiplier; 4106 final float translation = getTranslation(animView); 4107 final boolean fromLeft = translation > 0; 4108 final float absTrans = Math.abs(translation); 4109 final float notiThreshold = getSize(mTranslatingParentView) * 0.4f; 4110 4111 return mCurrIconRow.isVisible() && (mCurrIconRow.isIconOnLeft() 4112 ? (translation > snapBackThreshold && translation <= notiThreshold) 4113 : (translation < -snapBackThreshold && translation >= -notiThreshold)); 4114 } 4115 4116 @Override 4117 public Animator getViewTranslationAnimator(View v, float target, 4118 AnimatorUpdateListener listener) { 4119 if (v instanceof ExpandableNotificationRow) { 4120 return ((ExpandableNotificationRow) v).getTranslateViewAnimator(target, listener); 4121 } else { 4122 return super.getViewTranslationAnimator(v, target, listener); 4123 } 4124 } 4125 4126 @Override 4127 public void setTranslation(View v, float translate) { 4128 ((ExpandableView) v).setTranslation(translate); 4129 } 4130 4131 @Override 4132 public float getTranslation(View v) { 4133 return ((ExpandableView) v).getTranslation(); 4134 } 4135 4136 public void closeControlsIfOutsideTouch(MotionEvent ev) { 4137 NotificationGuts guts = mPhoneStatusBar.getExposedGuts(); 4138 View view = null; 4139 int height = 0; 4140 if (guts != null) { 4141 // Checking guts 4142 view = guts; 4143 height = guts.getActualHeight(); 4144 } else if (mCurrIconRow != null && mCurrIconRow.isVisible() 4145 && mTranslatingParentView != null) { 4146 // Checking gear 4147 view = mTranslatingParentView; 4148 height = ((ExpandableView) mTranslatingParentView).getActualHeight(); 4149 } 4150 if (view != null) { 4151 final int rx = (int) ev.getRawX(); 4152 final int ry = (int) ev.getRawY(); 4153 4154 getLocationOnScreen(mTempInt2); 4155 int[] location = new int[2]; 4156 view.getLocationOnScreen(location); 4157 final int x = location[0] - mTempInt2[0]; 4158 final int y = location[1] - mTempInt2[1]; 4159 Rect rect = new Rect(x, y, x + view.getWidth(), y + height); 4160 if (!rect.contains((int) rx, (int) ry)) { 4161 // Touch was outside visible guts / gear notification, close what's visible 4162 mPhoneStatusBar.dismissPopups(-1, -1, true /* resetGear */, true /* animate */); 4163 } 4164 } 4165 } 4166 4167 /** 4168 * Returns whether the gesture is towards the gear location or not. 4169 */ 4170 private boolean isTowardsGear(float velocity, boolean onLeft) { 4171 if (mCurrIconRow == null) { 4172 return false; 4173 } 4174 return mCurrIconRow.isVisible() 4175 && ((onLeft && velocity <= 0) || (!onLeft && velocity >= 0)); 4176 } 4177 4178 /** 4179 * Indicates the the gear has been snapped to. 4180 */ 4181 private void setSnappedToGear(boolean snapped) { 4182 mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isIconOnLeft() : false; 4183 mGearSnappedTo = snapped && mCurrIconRow != null; 4184 } 4185 4186 /** 4187 * Returns the horizontal space in pixels required to display the gear behind a 4188 * notification. 4189 */ 4190 private float getSpaceForGear(View view) { 4191 if (view instanceof ExpandableNotificationRow) { 4192 return ((ExpandableNotificationRow) view).getSpaceForGear(); 4193 } 4194 return 0; 4195 } 4196 4197 private void checkForDrag() { 4198 if (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag)) { 4199 mCheckForDrag = new CheckForDrag(); 4200 mHandler.postDelayed(mCheckForDrag, SHOW_GEAR_DELAY); 4201 } 4202 } 4203 4204 private void cancelCheckForDrag() { 4205 if (mCurrIconRow != null) { 4206 mCurrIconRow.cancelFadeAnimator(); 4207 } 4208 mHandler.removeCallbacks(mCheckForDrag); 4209 } 4210 4211 private final class CheckForDrag implements Runnable { 4212 @Override 4213 public void run() { 4214 if (mTranslatingParentView == null) { 4215 return; 4216 } 4217 final float translation = getTranslation(mTranslatingParentView); 4218 final float absTransX = Math.abs(translation); 4219 final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView); 4220 final float notiThreshold = getSize(mTranslatingParentView) * 0.4f; 4221 if ((mCurrIconRow != null && (!mCurrIconRow.isVisible() 4222 || mCurrIconRow.isIconLocationChange(translation))) 4223 && absTransX >= bounceBackToGearWidth * 0.4 4224 && absTransX < notiThreshold) { 4225 // Fade in the gear 4226 mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation, 4227 notiThreshold); 4228 } 4229 } 4230 } 4231 4232 public void resetExposedGearView(boolean animate, boolean force) { 4233 if (mGearExposedView == null 4234 || (!force && mGearExposedView == mTranslatingParentView)) { 4235 // If no gear is showing or it's showing for this view we do nothing. 4236 return; 4237 } 4238 final View prevGearExposedView = mGearExposedView; 4239 if (animate) { 4240 Animator anim = getViewTranslationAnimator(prevGearExposedView, 4241 0 /* leftTarget */, null /* updateListener */); 4242 if (anim != null) { 4243 anim.start(); 4244 } 4245 } else if (mGearExposedView instanceof ExpandableNotificationRow) { 4246 ((ExpandableNotificationRow) mGearExposedView).resetTranslation(); 4247 } 4248 mGearExposedView = null; 4249 mGearSnappedTo = false; 4250 } 4251 } 4252 4253 private void updateContinuousShadowDrawing() { 4254 boolean continuousShadowUpdate = mAnimationRunning 4255 || !mAmbientState.getDraggedViews().isEmpty(); 4256 if (continuousShadowUpdate != mContinuousShadowUpdate) { 4257 if (continuousShadowUpdate) { 4258 getViewTreeObserver().addOnPreDrawListener(mShadowUpdater); 4259 } else { 4260 getViewTreeObserver().removeOnPreDrawListener(mShadowUpdater); 4261 } 4262 mContinuousShadowUpdate = continuousShadowUpdate; 4263 } 4264 } 4265 4266 public void resetExposedGearView(boolean animate, boolean force) { 4267 mSwipeHelper.resetExposedGearView(animate, force); 4268 } 4269 4270 public void closeControlsIfOutsideTouch(MotionEvent ev) { 4271 mSwipeHelper.closeControlsIfOutsideTouch(ev); 4272 } 4273 4274 static class AnimationEvent { 4275 4276 static AnimationFilter[] FILTERS = new AnimationFilter[] { 4277 4278 // ANIMATION_TYPE_ADD 4279 new AnimationFilter() 4280 .animateShadowAlpha() 4281 .animateHeight() 4282 .animateTopInset() 4283 .animateY() 4284 .animateZ() 4285 .hasDelays(), 4286 4287 // ANIMATION_TYPE_REMOVE 4288 new AnimationFilter() 4289 .animateShadowAlpha() 4290 .animateHeight() 4291 .animateTopInset() 4292 .animateY() 4293 .animateZ() 4294 .hasDelays(), 4295 4296 // ANIMATION_TYPE_REMOVE_SWIPED_OUT 4297 new AnimationFilter() 4298 .animateShadowAlpha() 4299 .animateHeight() 4300 .animateTopInset() 4301 .animateY() 4302 .animateZ() 4303 .hasDelays(), 4304 4305 // ANIMATION_TYPE_TOP_PADDING_CHANGED 4306 new AnimationFilter() 4307 .animateShadowAlpha() 4308 .animateHeight() 4309 .animateTopInset() 4310 .animateY() 4311 .animateDimmed() 4312 .animateZ(), 4313 4314 // ANIMATION_TYPE_START_DRAG 4315 new AnimationFilter() 4316 .animateShadowAlpha(), 4317 4318 // ANIMATION_TYPE_SNAP_BACK 4319 new AnimationFilter() 4320 .animateShadowAlpha() 4321 .animateHeight(), 4322 4323 // ANIMATION_TYPE_ACTIVATED_CHILD 4324 new AnimationFilter() 4325 .animateZ(), 4326 4327 // ANIMATION_TYPE_DIMMED 4328 new AnimationFilter() 4329 .animateDimmed(), 4330 4331 // ANIMATION_TYPE_CHANGE_POSITION 4332 new AnimationFilter() 4333 .animateAlpha() // maybe the children change positions 4334 .animateShadowAlpha() 4335 .animateHeight() 4336 .animateTopInset() 4337 .animateY() 4338 .animateZ(), 4339 4340 // ANIMATION_TYPE_DARK 4341 new AnimationFilter() 4342 .animateDark() 4343 .hasDelays(), 4344 4345 // ANIMATION_TYPE_GO_TO_FULL_SHADE 4346 new AnimationFilter() 4347 .animateShadowAlpha() 4348 .animateHeight() 4349 .animateTopInset() 4350 .animateY() 4351 .animateDimmed() 4352 .animateZ() 4353 .hasDelays(), 4354 4355 // ANIMATION_TYPE_HIDE_SENSITIVE 4356 new AnimationFilter() 4357 .animateHideSensitive(), 4358 4359 // ANIMATION_TYPE_VIEW_RESIZE 4360 new AnimationFilter() 4361 .animateShadowAlpha() 4362 .animateHeight() 4363 .animateTopInset() 4364 .animateY() 4365 .animateZ(), 4366 4367 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED 4368 new AnimationFilter() 4369 .animateAlpha() 4370 .animateShadowAlpha() 4371 .animateHeight() 4372 .animateTopInset() 4373 .animateY() 4374 .animateZ(), 4375 4376 // ANIMATION_TYPE_HEADS_UP_APPEAR 4377 new AnimationFilter() 4378 .animateShadowAlpha() 4379 .animateHeight() 4380 .animateTopInset() 4381 .animateY() 4382 .animateZ(), 4383 4384 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR 4385 new AnimationFilter() 4386 .animateShadowAlpha() 4387 .animateHeight() 4388 .animateTopInset() 4389 .animateY() 4390 .animateZ(), 4391 4392 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK 4393 new AnimationFilter() 4394 .animateShadowAlpha() 4395 .animateHeight() 4396 .animateTopInset() 4397 .animateY() 4398 .animateZ() 4399 .hasDelays(), 4400 4401 // ANIMATION_TYPE_HEADS_UP_OTHER 4402 new AnimationFilter() 4403 .animateShadowAlpha() 4404 .animateHeight() 4405 .animateTopInset() 4406 .animateY() 4407 .animateZ(), 4408 4409 // ANIMATION_TYPE_EVERYTHING 4410 new AnimationFilter() 4411 .animateAlpha() 4412 .animateShadowAlpha() 4413 .animateDark() 4414 .animateDimmed() 4415 .animateHideSensitive() 4416 .animateHeight() 4417 .animateTopInset() 4418 .animateY() 4419 .animateZ(), 4420 }; 4421 4422 static int[] LENGTHS = new int[] { 4423 4424 // ANIMATION_TYPE_ADD 4425 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR, 4426 4427 // ANIMATION_TYPE_REMOVE 4428 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR, 4429 4430 // ANIMATION_TYPE_REMOVE_SWIPED_OUT 4431 StackStateAnimator.ANIMATION_DURATION_STANDARD, 4432 4433 // ANIMATION_TYPE_TOP_PADDING_CHANGED 4434 StackStateAnimator.ANIMATION_DURATION_STANDARD, 4435 4436 // ANIMATION_TYPE_START_DRAG 4437 StackStateAnimator.ANIMATION_DURATION_STANDARD, 4438 4439 // ANIMATION_TYPE_SNAP_BACK 4440 StackStateAnimator.ANIMATION_DURATION_STANDARD, 4441 4442 // ANIMATION_TYPE_ACTIVATED_CHILD 4443 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED, 4444 4445 // ANIMATION_TYPE_DIMMED 4446 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED, 4447 4448 // ANIMATION_TYPE_CHANGE_POSITION 4449 StackStateAnimator.ANIMATION_DURATION_STANDARD, 4450 4451 // ANIMATION_TYPE_DARK 4452 StackStateAnimator.ANIMATION_DURATION_STANDARD, 4453 4454 // ANIMATION_TYPE_GO_TO_FULL_SHADE 4455 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, 4456 4457 // ANIMATION_TYPE_HIDE_SENSITIVE 4458 StackStateAnimator.ANIMATION_DURATION_STANDARD, 4459 4460 // ANIMATION_TYPE_VIEW_RESIZE 4461 StackStateAnimator.ANIMATION_DURATION_STANDARD, 4462 4463 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED 4464 StackStateAnimator.ANIMATION_DURATION_STANDARD, 4465 4466 // ANIMATION_TYPE_HEADS_UP_APPEAR 4467 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR, 4468 4469 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR 4470 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR, 4471 4472 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK 4473 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR, 4474 4475 // ANIMATION_TYPE_HEADS_UP_OTHER 4476 StackStateAnimator.ANIMATION_DURATION_STANDARD, 4477 4478 // ANIMATION_TYPE_EVERYTHING 4479 StackStateAnimator.ANIMATION_DURATION_STANDARD, 4480 }; 4481 4482 static final int ANIMATION_TYPE_ADD = 0; 4483 static final int ANIMATION_TYPE_REMOVE = 1; 4484 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2; 4485 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3; 4486 static final int ANIMATION_TYPE_START_DRAG = 4; 4487 static final int ANIMATION_TYPE_SNAP_BACK = 5; 4488 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6; 4489 static final int ANIMATION_TYPE_DIMMED = 7; 4490 static final int ANIMATION_TYPE_CHANGE_POSITION = 8; 4491 static final int ANIMATION_TYPE_DARK = 9; 4492 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10; 4493 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11; 4494 static final int ANIMATION_TYPE_VIEW_RESIZE = 12; 4495 static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13; 4496 static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14; 4497 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15; 4498 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 16; 4499 static final int ANIMATION_TYPE_HEADS_UP_OTHER = 17; 4500 static final int ANIMATION_TYPE_EVERYTHING = 18; 4501 4502 static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1; 4503 static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2; 4504 4505 final long eventStartTime; 4506 final View changingView; 4507 final int animationType; 4508 final AnimationFilter filter; 4509 final long length; 4510 View viewAfterChangingView; 4511 int darkAnimationOriginIndex; 4512 boolean headsUpFromBottom; 4513 4514 AnimationEvent(View view, int type) { 4515 this(view, type, LENGTHS[type]); 4516 } 4517 4518 AnimationEvent(View view, int type, long length) { 4519 eventStartTime = AnimationUtils.currentAnimationTimeMillis(); 4520 changingView = view; 4521 animationType = type; 4522 filter = FILTERS[type]; 4523 this.length = length; 4524 } 4525 4526 /** 4527 * Combines the length of several animation events into a single value. 4528 * 4529 * @param events The events of the lengths to combine. 4530 * @return The combined length. Depending on the event types, this might be the maximum of 4531 * all events or the length of a specific event. 4532 */ 4533 static long combineLength(ArrayList<AnimationEvent> events) { 4534 long length = 0; 4535 int size = events.size(); 4536 for (int i = 0; i < size; i++) { 4537 AnimationEvent event = events.get(i); 4538 length = Math.max(length, event.length); 4539 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) { 4540 return event.length; 4541 } 4542 } 4543 return length; 4544 } 4545 } 4546 4547 } 4548