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