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