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