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