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