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