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.notification.stack; 18 19 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; 20 import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT; 21 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE; 22 import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; 23 import static com.android.systemui.util.Utils.shouldUseSplitNotificationShade; 24 25 import static java.lang.annotation.RetentionPolicy.SOURCE; 26 27 import android.animation.Animator; 28 import android.animation.AnimatorListenerAdapter; 29 import android.animation.TimeAnimator; 30 import android.animation.ValueAnimator; 31 import android.annotation.ColorInt; 32 import android.annotation.IntDef; 33 import android.annotation.NonNull; 34 import android.annotation.Nullable; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.content.res.Configuration; 38 import android.content.res.Resources; 39 import android.graphics.Canvas; 40 import android.graphics.Color; 41 import android.graphics.Outline; 42 import android.graphics.Paint; 43 import android.graphics.Path; 44 import android.graphics.PointF; 45 import android.graphics.Rect; 46 import android.os.Bundle; 47 import android.os.SystemProperties; 48 import android.os.UserHandle; 49 import android.provider.Settings; 50 import android.util.AttributeSet; 51 import android.util.Log; 52 import android.util.MathUtils; 53 import android.util.Pair; 54 import android.view.DisplayCutout; 55 import android.view.InputDevice; 56 import android.view.LayoutInflater; 57 import android.view.MotionEvent; 58 import android.view.VelocityTracker; 59 import android.view.View; 60 import android.view.ViewConfiguration; 61 import android.view.ViewGroup; 62 import android.view.ViewOutlineProvider; 63 import android.view.ViewTreeObserver; 64 import android.view.WindowInsets; 65 import android.view.accessibility.AccessibilityEvent; 66 import android.view.accessibility.AccessibilityNodeInfo; 67 import android.view.animation.AnimationUtils; 68 import android.view.animation.Interpolator; 69 import android.widget.OverScroller; 70 import android.widget.ScrollView; 71 72 import com.android.internal.annotations.VisibleForTesting; 73 import com.android.internal.graphics.ColorUtils; 74 import com.android.internal.jank.InteractionJankMonitor; 75 import com.android.keyguard.KeyguardSliceView; 76 import com.android.settingslib.Utils; 77 import com.android.systemui.Dumpable; 78 import com.android.systemui.ExpandHelper; 79 import com.android.systemui.R; 80 import com.android.systemui.animation.Interpolators; 81 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; 82 import com.android.systemui.statusbar.CommandQueue; 83 import com.android.systemui.statusbar.EmptyShadeView; 84 import com.android.systemui.statusbar.FeatureFlags; 85 import com.android.systemui.statusbar.NotificationRemoteInputManager; 86 import com.android.systemui.statusbar.NotificationShelf; 87 import com.android.systemui.statusbar.NotificationShelfController; 88 import com.android.systemui.statusbar.RemoteInputController; 89 import com.android.systemui.statusbar.StatusBarState; 90 import com.android.systemui.statusbar.notification.ExpandAnimationParameters; 91 import com.android.systemui.statusbar.notification.FakeShadowView; 92 import com.android.systemui.statusbar.notification.NotificationActivityStarter; 93 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController; 94 import com.android.systemui.statusbar.notification.NotificationUtils; 95 import com.android.systemui.statusbar.notification.ShadeViewRefactor; 96 import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent; 97 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 98 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; 99 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; 100 import com.android.systemui.statusbar.notification.logging.NotificationLogger; 101 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; 102 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 103 import com.android.systemui.statusbar.notification.row.ExpandableView; 104 import com.android.systemui.statusbar.notification.row.FooterView; 105 import com.android.systemui.statusbar.notification.row.ForegroundServiceDungeonView; 106 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; 107 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; 108 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; 109 import com.android.systemui.statusbar.phone.ShadeController; 110 import com.android.systemui.statusbar.phone.StatusBar; 111 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; 112 import com.android.systemui.statusbar.policy.HeadsUpUtil; 113 import com.android.systemui.statusbar.policy.ScrollAdapter; 114 import com.android.systemui.util.Assert; 115 import com.android.systemui.util.leak.RotationUtils; 116 117 import java.io.FileDescriptor; 118 import java.io.PrintWriter; 119 import java.lang.annotation.Retention; 120 import java.util.ArrayList; 121 import java.util.Collections; 122 import java.util.Comparator; 123 import java.util.HashSet; 124 import java.util.List; 125 import java.util.function.BiConsumer; 126 import java.util.function.Consumer; 127 128 import javax.inject.Inject; 129 import javax.inject.Named; 130 131 /** 132 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack. 133 */ 134 public class NotificationStackScrollLayout extends ViewGroup implements Dumpable { 135 136 public static final float BACKGROUND_ALPHA_DIMMED = 0.7f; 137 private static final String TAG = "StackScroller"; 138 139 // Usage: 140 // adb shell setprop persist.debug.nssl true && adb reboot 141 private static final boolean DEBUG = SystemProperties.getBoolean("persist.debug.nssl", 142 false /* default */); 143 // TODO(b/187291379) disable again before release 144 private static final boolean DEBUG_REMOVE_ANIMATION = SystemProperties.getBoolean( 145 "persist.debug.nssl.dismiss", false /* default */); 146 147 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f; 148 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f; 149 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f; 150 /** 151 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}. 152 */ 153 private static final int INVALID_POINTER = -1; 154 /** 155 * The distance in pixels between sections when the sections are directly adjacent (no visible 156 * gap is drawn between them). In this case we don't want to round their corners. 157 */ 158 private static final int DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX = 1; 159 private KeyguardBypassEnabledProvider mKeyguardBypassEnabledProvider; 160 161 private ExpandHelper mExpandHelper; 162 private NotificationSwipeHelper mSwipeHelper; 163 private int mCurrentStackHeight = Integer.MAX_VALUE; 164 private final Paint mBackgroundPaint = new Paint(); 165 private final boolean mShouldDrawNotificationBackground; 166 private boolean mHighPriorityBeforeSpeedBump; 167 private boolean mDismissRtl; 168 169 private float mExpandedHeight; 170 private int mOwnScrollY; 171 private int mMaxLayoutHeight; 172 173 private VelocityTracker mVelocityTracker; 174 private OverScroller mScroller; 175 /** Last Y position reported by {@link #mScroller}, used to calculate scroll delta. */ 176 private int mLastScrollerY; 177 /** 178 * True if the max position was set to a known position on the last call to {@link #mScroller}. 179 */ 180 private boolean mIsScrollerBoundSet; 181 private Runnable mFinishScrollingCallback; 182 private int mTouchSlop; 183 private float mSlopMultiplier; 184 private int mMinimumVelocity; 185 private int mMaximumVelocity; 186 private int mOverflingDistance; 187 private float mMaxOverScroll; 188 private boolean mIsBeingDragged; 189 private int mLastMotionY; 190 private int mDownX; 191 private int mActivePointerId = INVALID_POINTER; 192 private boolean mTouchIsClick; 193 private float mInitialTouchX; 194 private float mInitialTouchY; 195 196 private Paint mDebugPaint; 197 private int mContentHeight; 198 private int mIntrinsicContentHeight; 199 private int mCollapsedSize; 200 private int mPaddingBetweenElements; 201 private int mMaxTopPadding; 202 private int mTopPadding; 203 private boolean mAnimateNextTopPaddingChange; 204 private int mBottomMargin; 205 private int mBottomInset = 0; 206 private float mQsExpansionFraction; 207 208 /** 209 * The algorithm which calculates the properties for our children 210 */ 211 private final StackScrollAlgorithm mStackScrollAlgorithm; 212 private final AmbientState mAmbientState; 213 214 private GroupMembershipManager mGroupMembershipManager; 215 private GroupExpansionManager mGroupExpansionManager; 216 private NotificationActivityStarter mNotificationActivityStarter; 217 private HashSet<ExpandableView> mChildrenToAddAnimated = new HashSet<>(); 218 private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>(); 219 private ArrayList<ExpandableView> mChildrenToRemoveAnimated = new ArrayList<>(); 220 private ArrayList<ExpandableView> mChildrenChangingPositions = new ArrayList<>(); 221 private HashSet<View> mFromMoreCardAdditions = new HashSet<>(); 222 private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>(); 223 private ArrayList<View> mSwipedOutViews = new ArrayList<>(); 224 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this); 225 private boolean mAnimationsEnabled; 226 private boolean mChangePositionInProgress; 227 private boolean mChildTransferInProgress; 228 229 private int mSpeedBumpIndex = -1; 230 private boolean mSpeedBumpIndexDirty = true; 231 232 /** 233 * The raw amount of the overScroll on the top, which is not rubber-banded. 234 */ 235 private float mOverScrolledTopPixels; 236 237 /** 238 * The raw amount of the overScroll on the bottom, which is not rubber-banded. 239 */ 240 private float mOverScrolledBottomPixels; 241 private NotificationLogger.OnChildLocationsChangedListener mListener; 242 private OnOverscrollTopChangedListener mOverscrollTopChangedListener; 243 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener; 244 private OnEmptySpaceClickListener mOnEmptySpaceClickListener; 245 private boolean mNeedsAnimation; 246 private boolean mTopPaddingNeedsAnimation; 247 private boolean mDimmedNeedsAnimation; 248 private boolean mHideSensitiveNeedsAnimation; 249 private boolean mActivateNeedsAnimation; 250 private boolean mGoToFullShadeNeedsAnimation; 251 private boolean mIsExpanded = true; 252 private boolean mChildrenUpdateRequested; 253 private boolean mIsExpansionChanging; 254 private boolean mPanelTracking; 255 private boolean mExpandingNotification; 256 private boolean mExpandedInThisMotion; 257 private boolean mShouldShowShelfOnly; 258 protected boolean mScrollingEnabled; 259 protected FooterView mFooterView; 260 protected EmptyShadeView mEmptyShadeView; 261 private boolean mDismissAllInProgress; 262 private boolean mFadeNotificationsOnDismiss; 263 private FooterDismissListener mFooterDismissListener; 264 private boolean mFlingAfterUpEvent; 265 266 /** 267 * Was the scroller scrolled to the top when the down motion was observed? 268 */ 269 private boolean mScrolledToTopOnFirstDown; 270 /** 271 * The minimal amount of over scroll which is needed in order to switch to the quick settings 272 * when over scrolling on a expanded card. 273 */ 274 private float mMinTopOverScrollToEscape; 275 private int mIntrinsicPadding; 276 private float mStackTranslation; 277 private float mTopPaddingOverflow; 278 private boolean mDontReportNextOverScroll; 279 private boolean mDontClampNextScroll; 280 private boolean mNeedViewResizeAnimation; 281 private ExpandableView mExpandedGroupView; 282 private boolean mEverythingNeedsAnimation; 283 284 /** 285 * The maximum scrollPosition which we are allowed to reach when a notification was expanded. 286 * This is needed to avoid scrolling too far after the notification was collapsed in the same 287 * motion. 288 */ 289 private int mMaxScrollAfterExpand; 290 boolean mCheckForLeavebehind; 291 292 /** 293 * Should in this touch motion only be scrolling allowed? It's true when the scroller was 294 * animating. 295 */ 296 private boolean mOnlyScrollingInThisMotion; 297 private boolean mDisallowDismissInThisMotion; 298 private boolean mDisallowScrollingInThisMotion; 299 private long mGoToFullShadeDelay; 300 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater 301 = new ViewTreeObserver.OnPreDrawListener() { 302 @Override 303 public boolean onPreDraw() { 304 updateForcedScroll(); 305 updateChildren(); 306 mChildrenUpdateRequested = false; 307 getViewTreeObserver().removeOnPreDrawListener(this); 308 return true; 309 } 310 }; 311 312 private StatusBar mStatusBar; 313 private int[] mTempInt2 = new int[2]; 314 private boolean mGenerateChildOrderChangedEvent; 315 private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>(); 316 private HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>(); 317 private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations 318 = new HashSet<>(); 319 private boolean mTrackingHeadsUp; 320 private boolean mForceNoOverlappingRendering; 321 private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>(); 322 private boolean mAnimationRunning; 323 private ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater 324 = new ViewTreeObserver.OnPreDrawListener() { 325 @Override 326 public boolean onPreDraw() { 327 onPreDrawDuringAnimation(); 328 return true; 329 } 330 }; 331 private NotificationSection[] mSections; 332 private boolean mAnimateNextBackgroundTop; 333 private boolean mAnimateNextBackgroundBottom; 334 private boolean mAnimateNextSectionBoundsChange; 335 private int mBgColor; 336 private float mDimAmount; 337 private ValueAnimator mDimAnimator; 338 private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>(); 339 private final Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() { 340 @Override 341 public void onAnimationEnd(Animator animation) { 342 mDimAnimator = null; 343 } 344 }; 345 private ValueAnimator.AnimatorUpdateListener mDimUpdateListener 346 = new ValueAnimator.AnimatorUpdateListener() { 347 348 @Override 349 public void onAnimationUpdate(ValueAnimator animation) { 350 setDimAmount((Float) animation.getAnimatedValue()); 351 } 352 }; 353 protected ViewGroup mQsContainer; 354 private boolean mContinuousShadowUpdate; 355 private boolean mContinuousBackgroundUpdate; 356 private ViewTreeObserver.OnPreDrawListener mShadowUpdater 357 = () -> { 358 updateViewShadows(); 359 return true; 360 }; 361 private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> { 362 updateBackground(); 363 return true; 364 }; 365 private Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> { 366 float endY = view.getTranslationY() + view.getActualHeight(); 367 float otherEndY = otherView.getTranslationY() + otherView.getActualHeight(); 368 if (endY < otherEndY) { 369 return -1; 370 } else if (endY > otherEndY) { 371 return 1; 372 } else { 373 // The two notifications end at the same location 374 return 0; 375 } 376 }; 377 private final ViewOutlineProvider mOutlineProvider = new ViewOutlineProvider() { 378 @Override 379 public void getOutline(View view, Outline outline) { 380 if (mAmbientState.isHiddenAtAll()) { 381 float xProgress = mHideXInterpolator.getInterpolation( 382 (1 - mLinearHideAmount) * mBackgroundXFactor); 383 outline.setRoundRect(mBackgroundAnimationRect, 384 MathUtils.lerp(mCornerRadius / 2.0f, mCornerRadius, 385 xProgress)); 386 outline.setAlpha(1.0f - mAmbientState.getHideAmount()); 387 } else { 388 ViewOutlineProvider.BACKGROUND.getOutline(view, outline); 389 } 390 } 391 }; 392 private boolean mPulsing; 393 private boolean mScrollable; 394 private View mForcedScroll; 395 396 /** 397 * @see #setHideAmount(float, float) 398 */ 399 private float mInterpolatedHideAmount = 0f; 400 401 /** 402 * @see #setHideAmount(float, float) 403 */ 404 private float mLinearHideAmount = 0f; 405 406 /** 407 * How fast the background scales in the X direction as a factor of the Y expansion. 408 */ 409 private float mBackgroundXFactor = 1f; 410 411 private boolean mQsExpanded; 412 private boolean mForwardScrollable; 413 private boolean mBackwardScrollable; 414 private NotificationShelf mShelf; 415 private int mMaxDisplayedNotifications = -1; 416 private int mStatusBarHeight; 417 private int mMinInteractionHeight; 418 private final Rect mClipRect = new Rect(); 419 private boolean mIsClipped; 420 private Rect mRequestedClipBounds; 421 private boolean mInHeadsUpPinnedMode; 422 private boolean mHeadsUpAnimatingAway; 423 private int mStatusBarState; 424 private int mCachedBackgroundColor; 425 private boolean mHeadsUpGoingAwayAnimationsAllowed = true; 426 private Runnable mReflingAndAnimateScroll = () -> { 427 animateScroll(); 428 }; 429 private int mCornerRadius; 430 private int mMinimumPaddings; 431 private int mQsTilePadding; 432 private boolean mSkinnyNotifsInLandscape; 433 private int mSidePaddings; 434 private final Rect mBackgroundAnimationRect = new Rect(); 435 private ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>(); 436 private int mHeadsUpInset; 437 438 /** 439 * The position of the scroll boundary relative to this view. This is where the notifications 440 * stop scrolling and will start to clip instead. 441 */ 442 private int mQsScrollBoundaryPosition; 443 private HeadsUpAppearanceController mHeadsUpAppearanceController; 444 private final Rect mTmpRect = new Rect(); 445 private DismissListener mDismissListener; 446 private DismissAllAnimationListener mDismissAllAnimationListener; 447 private NotificationRemoteInputManager mRemoteInputManager; 448 private ShadeController mShadeController; 449 private Consumer<Boolean> mOnStackYChanged; 450 451 protected boolean mClearAllEnabled; 452 453 private Interpolator mHideXInterpolator = Interpolators.FAST_OUT_SLOW_IN; 454 455 private final NotificationSectionsManager mSectionsManager; 456 private ForegroundServiceDungeonView mFgsSectionView; 457 private boolean mAnimateBottomOnLayout; 458 private float mLastSentAppear; 459 private float mLastSentExpandedHeight; 460 private boolean mWillExpand; 461 private int mGapHeight; 462 463 /** 464 * The extra inset during the full shade transition 465 */ 466 private float mExtraTopInsetForFullShadeTransition; 467 468 private int mWaterfallTopInset; 469 private NotificationStackScrollLayoutController mController; 470 471 private boolean mKeyguardMediaControllorVisible; 472 473 /** 474 * The clip path used to clip the view in a rounded way. 475 */ 476 private final Path mRoundedClipPath = new Path(); 477 478 /** 479 * The clip Path used to clip the launching notification. This may be different 480 * from the normal path, as the views launch animation could start clipped. 481 */ 482 private final Path mLaunchedNotificationClipPath = new Path(); 483 484 /** 485 * Should we use rounded rect clipping right now 486 */ 487 private boolean mShouldUseRoundedRectClipping = false; 488 489 private int mRoundedRectClippingLeft; 490 private int mRoundedRectClippingTop; 491 private int mRoundedRectClippingBottom; 492 private int mRoundedRectClippingRight; 493 private float[] mBgCornerRadii = new float[8]; 494 495 /** 496 * Whether stackY should be animated in case the view is getting shorter than the scroll 497 * position and this scrolling will lead to the top scroll inset getting smaller. 498 */ 499 private boolean mAnimateStackYForContentHeightChange = false; 500 501 /** 502 * Are we launching a notification right now 503 */ 504 private boolean mLaunchingNotification; 505 506 /** 507 * Does the launching notification need to be clipped 508 */ 509 private boolean mLaunchingNotificationNeedsToBeClipped; 510 511 /** 512 * The current launch animation params when launching a notification 513 */ 514 private ExpandAnimationParameters mLaunchAnimationParams; 515 516 /** 517 * Corner radii of the launched notification if it's clipped 518 */ 519 private float[] mLaunchedNotificationRadii = new float[8]; 520 521 /** 522 * The notification that is being launched currently. 523 */ 524 private ExpandableNotificationRow mExpandingNotificationRow; 525 526 /** 527 * Do notifications dismiss with normal transitioning 528 */ 529 private boolean mDismissUsingRowTranslationX = true; 530 private NotificationEntry mTopHeadsUpEntry; 531 private long mNumHeadsUp; 532 private NotificationStackScrollLayoutController.TouchHandler mTouchHandler; 533 private final FeatureFlags mFeatureFlags; 534 private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; 535 private boolean mShouldUseSplitNotificationShade; 536 537 private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener = 538 new ExpandableView.OnHeightChangedListener() { 539 @Override 540 public void onHeightChanged(ExpandableView view, boolean needsAnimation) { 541 onChildHeightChanged(view, needsAnimation); 542 } 543 544 @Override 545 public void onReset(ExpandableView view) { 546 onChildHeightReset(view); 547 } 548 }; 549 550 private Consumer<Integer> mScrollListener; 551 private final ScrollAdapter mScrollAdapter = new ScrollAdapter() { 552 @Override 553 public boolean isScrolledToTop() { 554 return mOwnScrollY == 0; 555 } 556 557 @Override 558 public boolean isScrolledToBottom() { 559 return mOwnScrollY >= getScrollRange(); 560 } 561 562 @Override 563 public View getHostView() { 564 return NotificationStackScrollLayout.this; 565 } 566 }; 567 568 @Inject NotificationStackScrollLayout( @amedVIEW_CONTEXT) Context context, AttributeSet attrs, NotificationSectionsManager notificationSectionsManager, GroupMembershipManager groupMembershipManager, GroupExpansionManager groupExpansionManager, AmbientState ambientState, FeatureFlags featureFlags, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController)569 public NotificationStackScrollLayout( 570 @Named(VIEW_CONTEXT) Context context, 571 AttributeSet attrs, 572 NotificationSectionsManager notificationSectionsManager, 573 GroupMembershipManager groupMembershipManager, 574 GroupExpansionManager groupExpansionManager, 575 AmbientState ambientState, 576 FeatureFlags featureFlags, 577 UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { 578 super(context, attrs, 0, 0); 579 Resources res = getResources(); 580 mSectionsManager = notificationSectionsManager; 581 mFeatureFlags = featureFlags; 582 mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; 583 updateSplitNotificationShade(); 584 mSectionsManager.initialize(this, LayoutInflater.from(context)); 585 mSections = mSectionsManager.createSectionsForBuckets(); 586 587 mAmbientState = ambientState; 588 mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackgroundFloating) 589 .getDefaultColor(); 590 int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height); 591 int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height); 592 mExpandHelper = new ExpandHelper(getContext(), mExpandHelperCallback, 593 minHeight, maxHeight); 594 mExpandHelper.setEventSource(this); 595 mExpandHelper.setScrollAdapter(mScrollAdapter); 596 597 mStackScrollAlgorithm = createStackScrollAlgorithm(context); 598 mShouldDrawNotificationBackground = 599 res.getBoolean(R.bool.config_drawNotificationBackground); 600 setOutlineProvider(mOutlineProvider); 601 602 boolean willDraw = mShouldDrawNotificationBackground || DEBUG; 603 setWillNotDraw(!willDraw); 604 mBackgroundPaint.setAntiAlias(true); 605 if (DEBUG) { 606 mDebugPaint = new Paint(); 607 mDebugPaint.setColor(0xffff0000); 608 mDebugPaint.setStrokeWidth(2); 609 mDebugPaint.setStyle(Paint.Style.STROKE); 610 mDebugPaint.setTextSize(25f); 611 } 612 mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll); 613 mGroupMembershipManager = groupMembershipManager; 614 mGroupExpansionManager = groupExpansionManager; 615 } 616 initializeForegroundServiceSection(ForegroundServiceDungeonView fgsSectionView)617 void initializeForegroundServiceSection(ForegroundServiceDungeonView fgsSectionView) { 618 if (mFgsSectionView != null) { 619 return; 620 } 621 mFgsSectionView = fgsSectionView; 622 addView(mFgsSectionView, -1); 623 } 624 updateDismissRtlSetting(boolean dismissRtl)625 void updateDismissRtlSetting(boolean dismissRtl) { 626 mDismissRtl = dismissRtl; 627 for (int i = 0; i < getChildCount(); i++) { 628 View child = getChildAt(i); 629 if (child instanceof ExpandableNotificationRow) { 630 ((ExpandableNotificationRow) child).setDismissRtl(dismissRtl); 631 } 632 } 633 } 634 635 /** 636 * Set the overexpansion of the panel to be applied to the view. 637 */ setOverExpansion(float margin)638 void setOverExpansion(float margin) { 639 mAmbientState.setOverExpansion(margin); 640 updateStackPosition(); 641 requestChildrenUpdate(); 642 } 643 644 @Override 645 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) onFinishInflate()646 protected void onFinishInflate() { 647 super.onFinishInflate(); 648 649 inflateEmptyShadeView(); 650 inflateFooterView(); 651 } 652 653 /** 654 * @return the height at which we will wake up when pulsing 655 */ getWakeUpHeight()656 public float getWakeUpHeight() { 657 ExpandableView firstChild = getFirstChildWithBackground(); 658 if (firstChild != null) { 659 if (mKeyguardBypassEnabledProvider.getBypassEnabled()) { 660 return firstChild.getHeadsUpHeightWithoutHeader(); 661 } else { 662 return firstChild.getCollapsedHeight(); 663 } 664 } 665 return 0f; 666 } 667 reinflateViews()668 void reinflateViews() { 669 inflateFooterView(); 670 inflateEmptyShadeView(); 671 updateFooter(); 672 mSectionsManager.reinflateViews(LayoutInflater.from(mContext)); 673 } 674 675 @VisibleForTesting 676 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) updateFooter()677 public void updateFooter() { 678 if (mFooterView == null) { 679 return; 680 } 681 // TODO: move this logic to controller, which will invoke updateFooterView directly 682 boolean showDismissView = mClearAllEnabled && 683 mController.hasActiveClearableNotifications(ROWS_ALL); 684 RemoteInputController remoteInputController = mRemoteInputManager.getController(); 685 boolean showFooterView = (showDismissView || getVisibleNotificationCount() > 0) 686 && mStatusBarState != StatusBarState.KEYGUARD 687 && !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying() 688 && (remoteInputController == null || !remoteInputController.isRemoteInputActive()); 689 boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(), 690 Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1; 691 692 updateFooterView(showFooterView, showDismissView, showHistory); 693 } 694 695 /** 696 * Return whether there are any clearable notifications 697 */ 698 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) hasActiveClearableNotifications(@electedRows int selection)699 boolean hasActiveClearableNotifications(@SelectedRows int selection) { 700 return mController.hasActiveClearableNotifications(selection); 701 } 702 703 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) getSwipeActionHelper()704 public NotificationSwipeActionHelper getSwipeActionHelper() { 705 return mSwipeHelper; 706 } 707 updateBgColor()708 void updateBgColor() { 709 mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackgroundFloating) 710 .getDefaultColor(); 711 updateBackgroundDimming(); 712 for (int i = 0; i < getChildCount(); i++) { 713 View child = getChildAt(i); 714 if (child instanceof ActivatableNotificationView) { 715 ((ActivatableNotificationView) child).updateBackgroundColors(); 716 } 717 } 718 } 719 720 @ShadeViewRefactor(RefactorComponent.DECORATOR) onDraw(Canvas canvas)721 protected void onDraw(Canvas canvas) { 722 if (mShouldDrawNotificationBackground 723 && (mSections[0].getCurrentBounds().top 724 < mSections[mSections.length - 1].getCurrentBounds().bottom 725 || mAmbientState.isDozing())) { 726 drawBackground(canvas); 727 } else if (mInHeadsUpPinnedMode || mHeadsUpAnimatingAway) { 728 drawHeadsUpBackground(canvas); 729 } 730 731 if (DEBUG) { 732 int y = mTopPadding; 733 mDebugPaint.setColor(Color.RED); 734 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 735 736 y = getLayoutHeight(); 737 mDebugPaint.setColor(Color.YELLOW); 738 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 739 740 y = getHeight() - getEmptyBottomMargin(); 741 mDebugPaint.setColor(Color.GREEN); 742 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 743 744 y = (int) (mAmbientState.getStackY()); 745 mDebugPaint.setColor(Color.CYAN); 746 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 747 748 y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight()); 749 mDebugPaint.setColor(Color.BLUE); 750 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 751 } 752 } 753 754 @ShadeViewRefactor(RefactorComponent.DECORATOR) drawBackground(Canvas canvas)755 private void drawBackground(Canvas canvas) { 756 int lockScreenLeft = mSidePaddings; 757 int lockScreenRight = getWidth() - mSidePaddings; 758 int lockScreenTop = mSections[0].getCurrentBounds().top; 759 int lockScreenBottom = mSections[mSections.length - 1].getCurrentBounds().bottom; 760 int hiddenLeft = getWidth() / 2; 761 int hiddenTop = mTopPadding; 762 763 float yProgress = 1 - mInterpolatedHideAmount; 764 float xProgress = mHideXInterpolator.getInterpolation( 765 (1 - mLinearHideAmount) * mBackgroundXFactor); 766 767 int left = (int) MathUtils.lerp(hiddenLeft, lockScreenLeft, xProgress); 768 int right = (int) MathUtils.lerp(hiddenLeft, lockScreenRight, xProgress); 769 int top = (int) MathUtils.lerp(hiddenTop, lockScreenTop, yProgress); 770 int bottom = (int) MathUtils.lerp(hiddenTop, lockScreenBottom, yProgress); 771 mBackgroundAnimationRect.set( 772 left, 773 top, 774 right, 775 bottom); 776 777 int backgroundTopAnimationOffset = top - lockScreenTop; 778 // TODO(kprevas): this may not be necessary any more since we don't display the shelf in AOD 779 boolean anySectionHasVisibleChild = false; 780 for (NotificationSection section : mSections) { 781 if (section.needsBackground()) { 782 anySectionHasVisibleChild = true; 783 break; 784 } 785 } 786 boolean shouldDrawBackground; 787 if (mKeyguardBypassEnabledProvider.getBypassEnabled() && onKeyguard()) { 788 shouldDrawBackground = isPulseExpanding(); 789 } else { 790 shouldDrawBackground = !mAmbientState.isDozing() || anySectionHasVisibleChild; 791 } 792 if (shouldDrawBackground) { 793 drawBackgroundRects(canvas, left, right, top, backgroundTopAnimationOffset); 794 } 795 796 updateClipping(); 797 } 798 799 /** 800 * Draws round rects for each background section. 801 * 802 * We want to draw a round rect for each background section as defined by {@link #mSections}. 803 * However, if two sections are directly adjacent with no gap between them (e.g. on the 804 * lockscreen where the shelf can appear directly below the high priority section, or while 805 * scrolling the shade so that the top of the shelf is right at the bottom of the high priority 806 * section), we don't want to round the adjacent corners. 807 * 808 * Since {@link Canvas} doesn't provide a way to draw a half-rounded rect, this means that we 809 * need to coalesce the backgrounds for adjacent sections and draw them as a single round rect. 810 * This method tracks the top of each rect we need to draw, then iterates through the visible 811 * sections. If a section is not adjacent to the previous section, we draw the previous rect 812 * behind the sections we've accumulated up to that point, then start a new rect at the top of 813 * the current section. When we're done iterating we will always have one rect left to draw. 814 */ drawBackgroundRects(Canvas canvas, int left, int right, int top, int animationYOffset)815 private void drawBackgroundRects(Canvas canvas, int left, int right, int top, 816 int animationYOffset) { 817 int backgroundRectTop = top; 818 int lastSectionBottom = 819 mSections[0].getCurrentBounds().bottom + animationYOffset; 820 int currentLeft = left; 821 int currentRight = right; 822 boolean first = true; 823 for (NotificationSection section : mSections) { 824 if (!section.needsBackground()) { 825 continue; 826 } 827 int sectionTop = section.getCurrentBounds().top + animationYOffset; 828 int ownLeft = Math.min(Math.max(left, section.getCurrentBounds().left), right); 829 int ownRight = Math.max(Math.min(right, section.getCurrentBounds().right), ownLeft); 830 // If sections are directly adjacent to each other, we don't want to draw them 831 // as separate roundrects, as the rounded corners right next to each other look 832 // bad. 833 if (sectionTop - lastSectionBottom > DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX 834 || ((currentLeft != ownLeft || currentRight != ownRight) && !first)) { 835 canvas.drawRoundRect(currentLeft, 836 backgroundRectTop, 837 currentRight, 838 lastSectionBottom, 839 mCornerRadius, mCornerRadius, mBackgroundPaint); 840 backgroundRectTop = sectionTop; 841 } 842 currentLeft = ownLeft; 843 currentRight = ownRight; 844 lastSectionBottom = 845 section.getCurrentBounds().bottom + animationYOffset; 846 first = false; 847 } 848 canvas.drawRoundRect(currentLeft, 849 backgroundRectTop, 850 currentRight, 851 lastSectionBottom, 852 mCornerRadius, mCornerRadius, mBackgroundPaint); 853 } 854 drawHeadsUpBackground(Canvas canvas)855 private void drawHeadsUpBackground(Canvas canvas) { 856 int left = mSidePaddings; 857 int right = getWidth() - mSidePaddings; 858 859 float top = getHeight(); 860 float bottom = 0; 861 int childCount = getChildCount(); 862 for (int i = 0; i < childCount; i++) { 863 View child = getChildAt(i); 864 if (child.getVisibility() != View.GONE 865 && child instanceof ExpandableNotificationRow) { 866 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 867 if ((row.isPinned() || row.isHeadsUpAnimatingAway()) && row.getTranslation() < 0 868 && row.getProvider().shouldShowGutsOnSnapOpen()) { 869 top = Math.min(top, row.getTranslationY()); 870 bottom = Math.max(bottom, row.getTranslationY() + row.getActualHeight()); 871 } 872 } 873 } 874 875 if (top < bottom) { 876 canvas.drawRoundRect( 877 left, top, right, bottom, 878 mCornerRadius, mCornerRadius, mBackgroundPaint); 879 } 880 } 881 882 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) updateBackgroundDimming()883 void updateBackgroundDimming() { 884 // No need to update the background color if it's not being drawn. 885 if (!mShouldDrawNotificationBackground) { 886 return; 887 } 888 // Interpolate between semi-transparent notification panel background color 889 // and white AOD separator. 890 float colorInterpolation = MathUtils.smoothStep(0.4f /* start */, 1f /* end */, 891 mLinearHideAmount); 892 int color = ColorUtils.blendARGB(mBgColor, Color.WHITE, colorInterpolation); 893 894 if (mCachedBackgroundColor != color) { 895 mCachedBackgroundColor = color; 896 mBackgroundPaint.setColor(color); 897 invalidate(); 898 } 899 } 900 reinitView()901 private void reinitView() { 902 initView(getContext(), mKeyguardBypassEnabledProvider, mSwipeHelper); 903 } 904 905 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) initView(Context context, KeyguardBypassEnabledProvider keyguardBypassEnabledProvider, NotificationSwipeHelper swipeHelper)906 void initView(Context context, 907 KeyguardBypassEnabledProvider keyguardBypassEnabledProvider, 908 NotificationSwipeHelper swipeHelper) { 909 mScroller = new OverScroller(getContext()); 910 mKeyguardBypassEnabledProvider = keyguardBypassEnabledProvider; 911 mSwipeHelper = swipeHelper; 912 913 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 914 setClipChildren(false); 915 final ViewConfiguration configuration = ViewConfiguration.get(context); 916 mTouchSlop = configuration.getScaledTouchSlop(); 917 mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); 918 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 919 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 920 mOverflingDistance = configuration.getScaledOverflingDistance(); 921 922 Resources res = context.getResources(); 923 mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height); 924 mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height); 925 mStackScrollAlgorithm.initView(context); 926 mAmbientState.reload(context); 927 mPaddingBetweenElements = Math.max(1, 928 res.getDimensionPixelSize(R.dimen.notification_divider_height)); 929 mMinTopOverScrollToEscape = res.getDimensionPixelSize( 930 R.dimen.min_top_overscroll_to_qs); 931 mStatusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height); 932 mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom); 933 mMinimumPaddings = res.getDimensionPixelSize(R.dimen.notification_side_paddings); 934 mQsTilePadding = res.getDimensionPixelOffset(R.dimen.qs_tile_margin_horizontal); 935 mSkinnyNotifsInLandscape = res.getBoolean(R.bool.config_skinnyNotifsInLandscape); 936 mSidePaddings = mMinimumPaddings; // Updated in onMeasure by updateSidePadding() 937 mMinInteractionHeight = res.getDimensionPixelSize( 938 R.dimen.notification_min_interaction_height); 939 mCornerRadius = res.getDimensionPixelSize(R.dimen.notification_corner_radius); 940 mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize( 941 R.dimen.heads_up_status_bar_padding); 942 mQsScrollBoundaryPosition = res.getDimensionPixelSize( 943 com.android.internal.R.dimen.quick_qs_offset_height); 944 } 945 updateSidePadding(int viewWidth)946 void updateSidePadding(int viewWidth) { 947 if (viewWidth == 0 || !mSkinnyNotifsInLandscape) { 948 mSidePaddings = mMinimumPaddings; 949 return; 950 } 951 // Portrait is easy, just use the dimen for paddings 952 if (RotationUtils.getRotation(mContext) == RotationUtils.ROTATION_NONE) { 953 mSidePaddings = mMinimumPaddings; 954 return; 955 } 956 final int innerWidth = viewWidth - mMinimumPaddings * 2; 957 final int qsTileWidth = (innerWidth - mQsTilePadding * 3) / 4; 958 mSidePaddings = mMinimumPaddings + qsTileWidth + mQsTilePadding; 959 } 960 updateCornerRadius()961 void updateCornerRadius() { 962 int newRadius = getResources().getDimensionPixelSize(R.dimen.notification_corner_radius); 963 if (mCornerRadius != newRadius) { 964 mCornerRadius = newRadius; 965 invalidate(); 966 } 967 } 968 969 @ShadeViewRefactor(RefactorComponent.COORDINATOR) notifyHeightChangeListener(ExpandableView view)970 private void notifyHeightChangeListener(ExpandableView view) { 971 notifyHeightChangeListener(view, false /* needsAnimation */); 972 } 973 974 @ShadeViewRefactor(RefactorComponent.COORDINATOR) notifyHeightChangeListener(ExpandableView view, boolean needsAnimation)975 private void notifyHeightChangeListener(ExpandableView view, boolean needsAnimation) { 976 if (mOnHeightChangedListener != null) { 977 mOnHeightChangedListener.onHeightChanged(view, needsAnimation); 978 } 979 } 980 isPulseExpanding()981 public boolean isPulseExpanding() { 982 return mAmbientState.isPulseExpanding(); 983 } 984 getSpeedBumpIndex()985 public int getSpeedBumpIndex() { 986 if (mSpeedBumpIndexDirty) { 987 mSpeedBumpIndexDirty = false; 988 int speedBumpIndex = 0; 989 int currentIndex = 0; 990 final int n = getChildCount(); 991 for (int i = 0; i < n; i++) { 992 View view = getChildAt(i); 993 if (view.getVisibility() == View.GONE 994 || !(view instanceof ExpandableNotificationRow)) { 995 continue; 996 } 997 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 998 currentIndex++; 999 boolean beforeSpeedBump; 1000 if (mHighPriorityBeforeSpeedBump) { 1001 beforeSpeedBump = row.getEntry().getBucket() < BUCKET_SILENT; 1002 } else { 1003 beforeSpeedBump = !row.getEntry().isAmbient(); 1004 } 1005 if (beforeSpeedBump) { 1006 speedBumpIndex = currentIndex; 1007 } 1008 } 1009 1010 mSpeedBumpIndex = speedBumpIndex; 1011 } 1012 return mSpeedBumpIndex; 1013 } 1014 1015 @Override 1016 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1017 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1018 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 1019 1020 int width = MeasureSpec.getSize(widthMeasureSpec); 1021 updateSidePadding(width); 1022 int childWidthSpec = MeasureSpec.makeMeasureSpec(width - mSidePaddings * 2, 1023 MeasureSpec.getMode(widthMeasureSpec)); 1024 // Don't constrain the height of the children so we know how big they'd like to be 1025 int childHeightSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), 1026 MeasureSpec.UNSPECIFIED); 1027 1028 // We need to measure all children even the GONE ones, such that the heights are calculated 1029 // correctly as they are used to calculate how many we can fit on the screen. 1030 final int size = getChildCount(); 1031 for (int i = 0; i < size; i++) { 1032 measureChild(getChildAt(i), childWidthSpec, childHeightSpec); 1033 } 1034 } 1035 1036 @Override 1037 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1038 protected void onLayout(boolean changed, int l, int t, int r, int b) { 1039 // we layout all our children centered on the top 1040 float centerX = getWidth() / 2.0f; 1041 for (int i = 0; i < getChildCount(); i++) { 1042 View child = getChildAt(i); 1043 // We need to layout all children even the GONE ones, such that the heights are 1044 // calculated correctly as they are used to calculate how many we can fit on the screen 1045 float width = child.getMeasuredWidth(); 1046 float height = child.getMeasuredHeight(); 1047 child.layout((int) (centerX - width / 2.0f), 1048 0, 1049 (int) (centerX + width / 2.0f), 1050 (int) height); 1051 } 1052 setMaxLayoutHeight(getHeight()); 1053 updateContentHeight(); 1054 clampScrollPosition(); 1055 requestChildrenUpdate(); 1056 updateFirstAndLastBackgroundViews(); 1057 updateAlgorithmLayoutMinHeight(); 1058 updateOwnTranslationZ(); 1059 1060 // Once the layout has finished, we don't need to animate any scrolling clampings anymore. 1061 mAnimateStackYForContentHeightChange = false; 1062 } 1063 1064 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1065 private void requestAnimationOnViewResize(ExpandableNotificationRow row) { 1066 if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) { 1067 mNeedViewResizeAnimation = true; 1068 mNeedsAnimation = true; 1069 } 1070 } 1071 1072 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1073 public void setChildLocationsChangedListener( 1074 NotificationLogger.OnChildLocationsChangedListener listener) { 1075 mListener = listener; 1076 } 1077 1078 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM) 1079 private void setMaxLayoutHeight(int maxLayoutHeight) { 1080 mMaxLayoutHeight = maxLayoutHeight; 1081 updateAlgorithmHeightAndPadding(); 1082 } 1083 1084 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM) 1085 private void updateAlgorithmHeightAndPadding() { 1086 mAmbientState.setLayoutHeight(getLayoutHeight()); 1087 updateAlgorithmLayoutMinHeight(); 1088 mAmbientState.setTopPadding(mTopPadding); 1089 } 1090 1091 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM) 1092 private void updateAlgorithmLayoutMinHeight() { 1093 mAmbientState.setLayoutMinHeight(mQsExpanded || isHeadsUpTransition() 1094 ? getLayoutMinHeight() : 0); 1095 } 1096 1097 /** 1098 * Updates the children views according to the stack scroll algorithm. Call this whenever 1099 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout. 1100 */ 1101 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1102 private void updateChildren() { 1103 updateScrollStateForAddedChildren(); 1104 mAmbientState.setCurrentScrollVelocity(mScroller.isFinished() 1105 ? 0 1106 : mScroller.getCurrVelocity()); 1107 mStackScrollAlgorithm.resetViewStates(mAmbientState, getSpeedBumpIndex()); 1108 if (!isCurrentlyAnimating() && !mNeedsAnimation) { 1109 applyCurrentState(); 1110 } else { 1111 startAnimationToState(); 1112 } 1113 } 1114 1115 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1116 private void onPreDrawDuringAnimation() { 1117 mShelf.updateAppearance(); 1118 if (!mNeedsAnimation && !mChildrenUpdateRequested) { 1119 updateBackground(); 1120 } 1121 } 1122 1123 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1124 private void updateScrollStateForAddedChildren() { 1125 if (mChildrenToAddAnimated.isEmpty()) { 1126 return; 1127 } 1128 for (int i = 0; i < getChildCount(); i++) { 1129 ExpandableView child = (ExpandableView) getChildAt(i); 1130 if (mChildrenToAddAnimated.contains(child)) { 1131 final int startingPosition = getPositionInLinearLayout(child); 1132 final int childHeight = getIntrinsicHeight(child) + mPaddingBetweenElements; 1133 if (startingPosition < mOwnScrollY) { 1134 // This child starts off screen, so let's keep it offscreen to keep the 1135 // others visible 1136 1137 setOwnScrollY(mOwnScrollY + childHeight); 1138 } 1139 } 1140 } 1141 clampScrollPosition(); 1142 } 1143 1144 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1145 private void updateForcedScroll() { 1146 if (mForcedScroll != null && (!mForcedScroll.hasFocus() 1147 || !mForcedScroll.isAttachedToWindow())) { 1148 mForcedScroll = null; 1149 } 1150 if (mForcedScroll != null) { 1151 ExpandableView expandableView = (ExpandableView) mForcedScroll; 1152 int positionInLinearLayout = getPositionInLinearLayout(expandableView); 1153 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout); 1154 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight(); 1155 targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange())); 1156 // Only apply the scroll if we're scrolling the view upwards, or the view is so 1157 // far up that it is not visible anymore. 1158 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) { 1159 setOwnScrollY(targetScroll); 1160 } 1161 } 1162 } 1163 1164 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1165 void requestChildrenUpdate() { 1166 if (!mChildrenUpdateRequested) { 1167 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater); 1168 mChildrenUpdateRequested = true; 1169 invalidate(); 1170 } 1171 } 1172 1173 /** 1174 * Returns best effort count of visible notifications. 1175 */ 1176 public int getVisibleNotificationCount() { 1177 int count = 0; 1178 for (int i = 0; i < getChildCount(); i++) { 1179 final View child = getChildAt(i); 1180 if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) { 1181 count++; 1182 } 1183 } 1184 return count; 1185 } 1186 1187 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1188 private boolean isCurrentlyAnimating() { 1189 return mStateAnimator.isRunning(); 1190 } 1191 1192 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1193 private void clampScrollPosition() { 1194 int scrollRange = getScrollRange(); 1195 if (scrollRange < mOwnScrollY) { 1196 boolean animateStackY = false; 1197 if (scrollRange < getScrollAmountToScrollBoundary() 1198 && mAnimateStackYForContentHeightChange) { 1199 // if the scroll boundary updates the position of the stack, 1200 animateStackY = true; 1201 } 1202 setOwnScrollY(scrollRange, animateStackY); 1203 } 1204 } 1205 1206 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1207 public int getTopPadding() { 1208 return mTopPadding; 1209 } 1210 1211 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1212 private void setTopPadding(int topPadding, boolean animate) { 1213 if (mTopPadding != topPadding) { 1214 boolean shouldAnimate = animate || mAnimateNextTopPaddingChange; 1215 mTopPadding = topPadding; 1216 updateAlgorithmHeightAndPadding(); 1217 updateContentHeight(); 1218 if (shouldAnimate && mAnimationsEnabled && mIsExpanded) { 1219 mTopPaddingNeedsAnimation = true; 1220 mNeedsAnimation = true; 1221 } 1222 updateStackPosition(); 1223 requestChildrenUpdate(); 1224 notifyHeightChangeListener(null, shouldAnimate); 1225 mAnimateNextTopPaddingChange = false; 1226 } 1227 } 1228 1229 /** 1230 * Apply expansion fraction to the y position and height of the notifications panel. 1231 */ 1232 private void updateStackPosition() { 1233 updateStackPosition(false /* listenerNeedsAnimation */); 1234 } 1235 1236 /** 1237 * Apply expansion fraction to the y position and height of the notifications panel. 1238 * @param listenerNeedsAnimation does the listener need to animate? 1239 */ 1240 private void updateStackPosition(boolean listenerNeedsAnimation) { 1241 // Consider interpolating from an mExpansionStartY for use on lockscreen and AOD 1242 float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition 1243 + mAmbientState.getOverExpansion() 1244 - getCurrentOverScrollAmount(false /* top */); 1245 final float fraction = mAmbientState.getExpansionFraction(); 1246 final float stackY = MathUtils.lerp(0, endTopPosition, fraction); 1247 mAmbientState.setStackY(stackY); 1248 if (mOnStackYChanged != null) { 1249 mOnStackYChanged.accept(listenerNeedsAnimation); 1250 } 1251 if (mQsExpansionFraction <= 0) { 1252 final float stackEndHeight = Math.max(0f, 1253 getHeight() - getEmptyBottomMargin() - mTopPadding); 1254 mAmbientState.setStackEndHeight(stackEndHeight); 1255 mAmbientState.setStackHeight( 1256 MathUtils.lerp(stackEndHeight * StackScrollAlgorithm.START_FRACTION, 1257 stackEndHeight, fraction)); 1258 } 1259 } 1260 1261 /** 1262 * Add a listener when the StackY changes. The argument signifies whether an animation is 1263 * needed. 1264 */ 1265 void setOnStackYChanged(Consumer<Boolean> onStackYChanged) { 1266 mOnStackYChanged = onStackYChanged; 1267 } 1268 1269 /** 1270 * Update the height of the panel. 1271 * 1272 * @param height the expanded height of the panel 1273 */ 1274 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1275 public void setExpandedHeight(float height) { 1276 final float shadeBottom = getHeight() - getEmptyBottomMargin(); 1277 final float expansionFraction = MathUtils.saturate(height / shadeBottom); 1278 mAmbientState.setExpansionFraction(expansionFraction); 1279 updateStackPosition(); 1280 1281 mExpandedHeight = height; 1282 setIsExpanded(height > 0); 1283 int minExpansionHeight = getMinExpansionHeight(); 1284 if (height < minExpansionHeight) { 1285 mClipRect.left = 0; 1286 mClipRect.right = getWidth(); 1287 mClipRect.top = 0; 1288 mClipRect.bottom = (int) height; 1289 height = minExpansionHeight; 1290 setRequestedClipBounds(mClipRect); 1291 } else { 1292 setRequestedClipBounds(null); 1293 } 1294 int stackHeight; 1295 float translationY; 1296 float appearEndPosition = getAppearEndPosition(); 1297 float appearStartPosition = getAppearStartPosition(); 1298 float appearFraction = 1.0f; 1299 boolean appearing = height < appearEndPosition; 1300 mAmbientState.setAppearing(appearing); 1301 if (!appearing) { 1302 translationY = 0; 1303 if (mShouldShowShelfOnly) { 1304 stackHeight = mTopPadding + mShelf.getIntrinsicHeight(); 1305 } else if (mQsExpanded) { 1306 int stackStartPosition = mContentHeight - mTopPadding + mIntrinsicPadding; 1307 int stackEndPosition = mMaxTopPadding + mShelf.getIntrinsicHeight(); 1308 if (stackStartPosition <= stackEndPosition) { 1309 stackHeight = stackEndPosition; 1310 } else { 1311 if (mShouldUseSplitNotificationShade) { 1312 // This prevents notifications from being collapsed when QS is expanded. 1313 stackHeight = (int) height; 1314 } else { 1315 stackHeight = (int) NotificationUtils.interpolate(stackStartPosition, 1316 stackEndPosition, mQsExpansionFraction); 1317 } 1318 } 1319 } else { 1320 stackHeight = (int) height; 1321 } 1322 } else { 1323 appearFraction = calculateAppearFraction(height); 1324 if (appearFraction >= 0) { 1325 translationY = NotificationUtils.interpolate(getExpandTranslationStart(), 0, 1326 appearFraction); 1327 } else { 1328 // This may happen when pushing up a heads up. We linearly push it up from the 1329 // start 1330 translationY = height - appearStartPosition + getExpandTranslationStart(); 1331 } 1332 stackHeight = (int) (height - translationY); 1333 if (isHeadsUpTransition()) { 1334 translationY = MathUtils.lerp(mHeadsUpInset - mTopPadding, 0, appearFraction); 1335 } 1336 } 1337 mAmbientState.setAppearFraction(appearFraction); 1338 if (stackHeight != mCurrentStackHeight) { 1339 mCurrentStackHeight = stackHeight; 1340 updateAlgorithmHeightAndPadding(); 1341 requestChildrenUpdate(); 1342 } 1343 setStackTranslation(translationY); 1344 notifyAppearChangedListeners(); 1345 } 1346 1347 private void notifyAppearChangedListeners() { 1348 float appear; 1349 float expandAmount; 1350 if (mKeyguardBypassEnabledProvider.getBypassEnabled() && onKeyguard()) { 1351 appear = calculateAppearFractionBypass(); 1352 expandAmount = getPulseHeight(); 1353 } else { 1354 appear = MathUtils.saturate(calculateAppearFraction(mExpandedHeight)); 1355 expandAmount = mExpandedHeight; 1356 } 1357 if (appear != mLastSentAppear || expandAmount != mLastSentExpandedHeight) { 1358 mLastSentAppear = appear; 1359 mLastSentExpandedHeight = expandAmount; 1360 for (int i = 0; i < mExpandedHeightListeners.size(); i++) { 1361 BiConsumer<Float, Float> listener = mExpandedHeightListeners.get(i); 1362 listener.accept(expandAmount, appear); 1363 } 1364 } 1365 } 1366 1367 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1368 private void setRequestedClipBounds(Rect clipRect) { 1369 mRequestedClipBounds = clipRect; 1370 updateClipping(); 1371 } 1372 1373 /** 1374 * Return the height of the content ignoring the footer. 1375 */ 1376 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1377 public int getIntrinsicContentHeight() { 1378 return mIntrinsicContentHeight; 1379 } 1380 1381 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1382 public void updateClipping() { 1383 boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode 1384 && !mHeadsUpAnimatingAway; 1385 boolean clipToOutline = false; 1386 if (mIsClipped != clipped) { 1387 mIsClipped = clipped; 1388 } 1389 1390 if (mAmbientState.isHiddenAtAll()) { 1391 clipToOutline = false; 1392 invalidateOutline(); 1393 if (isFullyHidden()) { 1394 setClipBounds(null); 1395 } 1396 } else if (clipped) { 1397 setClipBounds(mRequestedClipBounds); 1398 } else { 1399 setClipBounds(null); 1400 } 1401 1402 setClipToOutline(clipToOutline); 1403 } 1404 1405 /** 1406 * @return The translation at the beginning when expanding. 1407 * Measured relative to the resting position. 1408 */ 1409 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1410 private float getExpandTranslationStart() { 1411 return -mTopPadding + getMinExpansionHeight() - mShelf.getIntrinsicHeight(); 1412 } 1413 1414 /** 1415 * @return the position from where the appear transition starts when expanding. 1416 * Measured in absolute height. 1417 */ 1418 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1419 private float getAppearStartPosition() { 1420 if (isHeadsUpTransition()) { 1421 final NotificationSection firstVisibleSection = getFirstVisibleSection(); 1422 final int pinnedHeight = firstVisibleSection != null 1423 ? firstVisibleSection.getFirstVisibleChild().getPinnedHeadsUpHeight() 1424 : 0; 1425 return mHeadsUpInset + pinnedHeight; 1426 } 1427 return getMinExpansionHeight(); 1428 } 1429 1430 /** 1431 * @return the height of the top heads up notification when pinned. This is different from the 1432 * intrinsic height, which also includes whether the notification is system expanded and 1433 * is mainly used when dragging down from a heads up notification. 1434 */ 1435 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1436 private int getTopHeadsUpPinnedHeight() { 1437 if (mTopHeadsUpEntry == null) { 1438 return 0; 1439 } 1440 ExpandableNotificationRow row = mTopHeadsUpEntry.getRow(); 1441 if (row.isChildInGroup()) { 1442 final NotificationEntry groupSummary = 1443 mGroupMembershipManager.getGroupSummary(row.getEntry()); 1444 if (groupSummary != null) { 1445 row = groupSummary.getRow(); 1446 } 1447 } 1448 return row.getPinnedHeadsUpHeight(); 1449 } 1450 1451 /** 1452 * @return the position from where the appear transition ends when expanding. 1453 * Measured in absolute height. 1454 */ 1455 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1456 private float getAppearEndPosition() { 1457 int appearPosition = 0; 1458 int visibleNotifCount = getVisibleNotificationCount(); 1459 if (mEmptyShadeView.getVisibility() == GONE && visibleNotifCount > 0) { 1460 if (isHeadsUpTransition() 1461 || (mInHeadsUpPinnedMode && !mAmbientState.isDozing())) { 1462 if (mShelf.getVisibility() != GONE && visibleNotifCount > 1) { 1463 appearPosition += mShelf.getIntrinsicHeight() + mPaddingBetweenElements; 1464 } 1465 appearPosition += getTopHeadsUpPinnedHeight() 1466 + getPositionInLinearLayout(mAmbientState.getTrackedHeadsUpRow()); 1467 } else if (mShelf.getVisibility() != GONE) { 1468 appearPosition += mShelf.getIntrinsicHeight(); 1469 } 1470 } else { 1471 appearPosition = mEmptyShadeView.getHeight(); 1472 } 1473 return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding); 1474 } 1475 1476 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1477 private boolean isHeadsUpTransition() { 1478 return mAmbientState.getTrackedHeadsUpRow() != null; 1479 } 1480 1481 /** 1482 * @param height the height of the panel 1483 * @return the fraction of the appear animation that has been performed 1484 */ 1485 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1486 public float calculateAppearFraction(float height) { 1487 float appearEndPosition = getAppearEndPosition(); 1488 float appearStartPosition = getAppearStartPosition(); 1489 return (height - appearStartPosition) 1490 / (appearEndPosition - appearStartPosition); 1491 } 1492 1493 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1494 public float getStackTranslation() { 1495 return mStackTranslation; 1496 } 1497 1498 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1499 private void setStackTranslation(float stackTranslation) { 1500 if (stackTranslation != mStackTranslation) { 1501 mStackTranslation = stackTranslation; 1502 mAmbientState.setStackTranslation(stackTranslation); 1503 requestChildrenUpdate(); 1504 } 1505 } 1506 1507 /** 1508 * Get the current height of the view. This is at most the msize of the view given by a the 1509 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight} 1510 * 1511 * @return either the layout height or the externally defined height, whichever is smaller 1512 */ 1513 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1514 private int getLayoutHeight() { 1515 return Math.min(mMaxLayoutHeight, mCurrentStackHeight); 1516 } 1517 1518 @ShadeViewRefactor(RefactorComponent.ADAPTER) 1519 public void setQsContainer(ViewGroup qsContainer) { 1520 mQsContainer = qsContainer; 1521 } 1522 1523 @ShadeViewRefactor(RefactorComponent.ADAPTER) 1524 public static boolean isPinnedHeadsUp(View v) { 1525 if (v instanceof ExpandableNotificationRow) { 1526 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 1527 return row.isHeadsUp() && row.isPinned(); 1528 } 1529 return false; 1530 } 1531 1532 @ShadeViewRefactor(RefactorComponent.ADAPTER) 1533 private boolean isHeadsUp(View v) { 1534 if (v instanceof ExpandableNotificationRow) { 1535 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 1536 return row.isHeadsUp(); 1537 } 1538 return false; 1539 } 1540 1541 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1542 private ExpandableView getChildAtPosition(float touchX, float touchY) { 1543 return getChildAtPosition( 1544 touchX, touchY, true /* requireMinHeight */, true /* ignoreDecors */); 1545 } 1546 1547 /** 1548 * Get the child at a certain screen location. 1549 * 1550 * @param touchX the x coordinate 1551 * @param touchY the y coordinate 1552 * @param requireMinHeight Whether a minimum height is required for a child to be returned. 1553 * @param ignoreDecors Whether decors can be returned 1554 * @return the child at the given location. 1555 */ 1556 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1557 ExpandableView getChildAtPosition(float touchX, float touchY, 1558 boolean requireMinHeight, boolean ignoreDecors) { 1559 // find the view under the pointer, accounting for GONE views 1560 final int count = getChildCount(); 1561 for (int childIdx = 0; childIdx < count; childIdx++) { 1562 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx); 1563 if (slidingChild.getVisibility() != VISIBLE 1564 || (ignoreDecors && slidingChild instanceof StackScrollerDecorView)) { 1565 continue; 1566 } 1567 float childTop = slidingChild.getTranslationY(); 1568 float top = childTop + slidingChild.getClipTopAmount(); 1569 float bottom = childTop + slidingChild.getActualHeight() 1570 - slidingChild.getClipBottomAmount(); 1571 1572 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and 1573 // camera affordance). 1574 int left = 0; 1575 int right = getWidth(); 1576 1577 if ((bottom - top >= mMinInteractionHeight || !requireMinHeight) 1578 && touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) { 1579 if (slidingChild instanceof ExpandableNotificationRow) { 1580 ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild; 1581 NotificationEntry entry = row.getEntry(); 1582 if (!mIsExpanded && row.isHeadsUp() && row.isPinned() 1583 && mTopHeadsUpEntry.getRow() != row 1584 && mGroupMembershipManager.getGroupSummary(mTopHeadsUpEntry) != entry) { 1585 continue; 1586 } 1587 return row.getViewAtPosition(touchY - childTop); 1588 } 1589 return slidingChild; 1590 } 1591 } 1592 return null; 1593 } 1594 1595 public ExpandableView getChildAtRawPosition(float touchX, float touchY) { 1596 getLocationOnScreen(mTempInt2); 1597 return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]); 1598 } 1599 1600 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1601 public void setScrollingEnabled(boolean enable) { 1602 mScrollingEnabled = enable; 1603 } 1604 1605 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1606 public void lockScrollTo(View v) { 1607 if (mForcedScroll == v) { 1608 return; 1609 } 1610 mForcedScroll = v; 1611 scrollTo(v); 1612 } 1613 1614 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1615 public boolean scrollTo(View v) { 1616 ExpandableView expandableView = (ExpandableView) v; 1617 int positionInLinearLayout = getPositionInLinearLayout(v); 1618 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout); 1619 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight(); 1620 1621 // Only apply the scroll if we're scrolling the view upwards, or the view is so far up 1622 // that it is not visible anymore. 1623 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) { 1624 mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY); 1625 mDontReportNextOverScroll = true; 1626 animateScroll(); 1627 return true; 1628 } 1629 return false; 1630 } 1631 1632 /** 1633 * @return the scroll necessary to make the bottom edge of {@param v} align with the top of 1634 * the IME. 1635 */ 1636 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1637 private int targetScrollForView(ExpandableView v, int positionInLinearLayout) { 1638 return positionInLinearLayout + v.getIntrinsicHeight() + 1639 getImeInset() - getHeight() 1640 + ((!isExpanded() && isPinnedHeadsUp(v)) ? mHeadsUpInset : getTopPadding()); 1641 } 1642 1643 @Override 1644 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1645 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 1646 mBottomInset = insets.getSystemWindowInsetBottom(); 1647 1648 mWaterfallTopInset = 0; 1649 final DisplayCutout cutout = insets.getDisplayCutout(); 1650 if (cutout != null) { 1651 mWaterfallTopInset = cutout.getWaterfallInsets().top; 1652 } 1653 1654 int range = getScrollRange(); 1655 if (mOwnScrollY > range) { 1656 // HACK: We're repeatedly getting staggered insets here while the IME is 1657 // animating away. To work around that we'll wait until things have settled. 1658 removeCallbacks(mReclamp); 1659 postDelayed(mReclamp, 50); 1660 } else if (mForcedScroll != null) { 1661 // The scroll was requested before we got the actual inset - in case we need 1662 // to scroll up some more do so now. 1663 scrollTo(mForcedScroll); 1664 } 1665 return insets; 1666 } 1667 1668 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1669 private Runnable mReclamp = new Runnable() { 1670 @Override 1671 public void run() { 1672 int range = getScrollRange(); 1673 mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY); 1674 mDontReportNextOverScroll = true; 1675 mDontClampNextScroll = true; 1676 animateScroll(); 1677 } 1678 }; 1679 1680 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1681 public void setExpandingEnabled(boolean enable) { 1682 mExpandHelper.setEnabled(enable); 1683 } 1684 1685 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1686 private boolean isScrollingEnabled() { 1687 return mScrollingEnabled; 1688 } 1689 1690 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1691 boolean onKeyguard() { 1692 return mStatusBarState == StatusBarState.KEYGUARD; 1693 } 1694 1695 @Override 1696 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1697 protected void onConfigurationChanged(Configuration newConfig) { 1698 super.onConfigurationChanged(newConfig); 1699 Resources res = getResources(); 1700 updateSplitNotificationShade(); 1701 mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height); 1702 float densityScale = res.getDisplayMetrics().density; 1703 mSwipeHelper.setDensityScale(densityScale); 1704 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); 1705 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); 1706 reinitView(); 1707 } 1708 1709 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1710 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) { 1711 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration, 1712 true /* isDismissAll */); 1713 } 1714 1715 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1716 private void snapViewIfNeeded(NotificationEntry entry) { 1717 ExpandableNotificationRow child = entry.getRow(); 1718 boolean animate = mIsExpanded || isPinnedHeadsUp(child); 1719 // If the child is showing the notification menu snap to that 1720 if (child.getProvider() != null) { 1721 float targetLeft = child.getProvider().isMenuVisible() ? child.getTranslation() : 0; 1722 mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft); 1723 } 1724 } 1725 1726 @ShadeViewRefactor(RefactorComponent.ADAPTER) 1727 public ViewGroup getViewParentForNotification(NotificationEntry entry) { 1728 return this; 1729 } 1730 1731 /** 1732 * Perform a scroll upwards and adapt the overscroll amounts accordingly 1733 * 1734 * @param deltaY The amount to scroll upwards, has to be positive. 1735 * @return The amount of scrolling to be performed by the scroller, 1736 * not handled by the overScroll amount. 1737 */ 1738 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1739 private float overScrollUp(int deltaY, int range) { 1740 deltaY = Math.max(deltaY, 0); 1741 float currentTopAmount = getCurrentOverScrollAmount(true); 1742 float newTopAmount = currentTopAmount - deltaY; 1743 if (currentTopAmount > 0) { 1744 setOverScrollAmount(newTopAmount, true /* onTop */, 1745 false /* animate */); 1746 } 1747 // Top overScroll might not grab all scrolling motion, 1748 // we have to scroll as well. 1749 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f; 1750 float newScrollY = mOwnScrollY + scrollAmount; 1751 if (newScrollY > range) { 1752 if (!mExpandedInThisMotion) { 1753 float currentBottomPixels = getCurrentOverScrolledPixels(false); 1754 // We overScroll on the bottom 1755 setOverScrolledPixels(currentBottomPixels + newScrollY - range, 1756 false /* onTop */, 1757 false /* animate */); 1758 } 1759 setOwnScrollY(range); 1760 scrollAmount = 0.0f; 1761 } 1762 return scrollAmount; 1763 } 1764 1765 /** 1766 * Perform a scroll downward and adapt the overscroll amounts accordingly 1767 * 1768 * @param deltaY The amount to scroll downwards, has to be negative. 1769 * @return The amount of scrolling to be performed by the scroller, 1770 * not handled by the overScroll amount. 1771 */ 1772 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1773 private float overScrollDown(int deltaY) { 1774 deltaY = Math.min(deltaY, 0); 1775 float currentBottomAmount = getCurrentOverScrollAmount(false); 1776 float newBottomAmount = currentBottomAmount + deltaY; 1777 if (currentBottomAmount > 0) { 1778 setOverScrollAmount(newBottomAmount, false /* onTop */, 1779 false /* animate */); 1780 } 1781 // Bottom overScroll might not grab all scrolling motion, 1782 // we have to scroll as well. 1783 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f; 1784 float newScrollY = mOwnScrollY + scrollAmount; 1785 if (newScrollY < 0) { 1786 float currentTopPixels = getCurrentOverScrolledPixels(true); 1787 // We overScroll on the top 1788 setOverScrolledPixels(currentTopPixels - newScrollY, 1789 true /* onTop */, 1790 false /* animate */); 1791 setOwnScrollY(0); 1792 scrollAmount = 0.0f; 1793 } 1794 return scrollAmount; 1795 } 1796 1797 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1798 private void initVelocityTrackerIfNotExists() { 1799 if (mVelocityTracker == null) { 1800 mVelocityTracker = VelocityTracker.obtain(); 1801 } 1802 } 1803 1804 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1805 private void recycleVelocityTracker() { 1806 if (mVelocityTracker != null) { 1807 mVelocityTracker.recycle(); 1808 mVelocityTracker = null; 1809 } 1810 } 1811 1812 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1813 private void initOrResetVelocityTracker() { 1814 if (mVelocityTracker == null) { 1815 mVelocityTracker = VelocityTracker.obtain(); 1816 } else { 1817 mVelocityTracker.clear(); 1818 } 1819 } 1820 1821 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 1822 public void setFinishScrollingCallback(Runnable runnable) { 1823 mFinishScrollingCallback = runnable; 1824 } 1825 1826 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1827 private void animateScroll() { 1828 if (mScroller.computeScrollOffset()) { 1829 int oldY = mOwnScrollY; 1830 int y = mScroller.getCurrY(); 1831 1832 if (oldY != y) { 1833 int range = getScrollRange(); 1834 if (y < 0 && oldY >= 0 || y > range && oldY <= range) { 1835 // This frame takes us into overscroll, so set the max overscroll based on 1836 // the current velocity 1837 setMaxOverScrollFromCurrentVelocity(); 1838 } 1839 1840 if (mDontClampNextScroll) { 1841 range = Math.max(range, oldY); 1842 } 1843 customOverScrollBy(y - oldY, oldY, range, 1844 (int) (mMaxOverScroll)); 1845 } 1846 postOnAnimation(mReflingAndAnimateScroll); 1847 } else { 1848 mDontClampNextScroll = false; 1849 if (mFinishScrollingCallback != null) { 1850 mFinishScrollingCallback.run(); 1851 } 1852 } 1853 } 1854 1855 private void setMaxOverScrollFromCurrentVelocity() { 1856 float currVelocity = mScroller.getCurrVelocity(); 1857 if (currVelocity >= mMinimumVelocity) { 1858 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance; 1859 } 1860 } 1861 1862 /** 1863 * Scrolls by the given delta, overscrolling if needed. If called during a fling and the delta 1864 * would cause us to exceed the provided maximum overscroll, springs back instead. 1865 * 1866 * This method performs the determination of whether we're exceeding the overscroll and clamps 1867 * the scroll amount if so. The actual scrolling/overscrolling happens in 1868 * {@link #onCustomOverScrolled(int, boolean)} 1869 * @param deltaY The (signed) number of pixels to scroll. 1870 * @param scrollY The current scroll position (absolute scrolling only). 1871 * @param scrollRangeY The maximum allowable scroll position (absolute scrolling only). 1872 * @param maxOverScrollY The current (unsigned) limit on number of pixels to overscroll by. 1873 */ 1874 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1875 private void customOverScrollBy(int deltaY, int scrollY, int scrollRangeY, int maxOverScrollY) { 1876 int newScrollY = scrollY + deltaY; 1877 final int top = -maxOverScrollY; 1878 final int bottom = maxOverScrollY + scrollRangeY; 1879 1880 boolean clampedY = false; 1881 if (newScrollY > bottom) { 1882 newScrollY = bottom; 1883 clampedY = true; 1884 } else if (newScrollY < top) { 1885 newScrollY = top; 1886 clampedY = true; 1887 } 1888 1889 onCustomOverScrolled(newScrollY, clampedY); 1890 } 1891 1892 /** 1893 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded 1894 * overscroll effect based on numPixels. By default this will also cancel animations on the 1895 * same overScroll edge. 1896 * 1897 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to 1898 * the rubber-banding logic. 1899 * @param onTop Should the effect be applied on top of the scroller. 1900 * @param animate Should an animation be performed. 1901 */ 1902 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1903 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) { 1904 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true); 1905 } 1906 1907 /** 1908 * Set the effective overScroll amount which will be directly reflected in the layout. 1909 * By default this will also cancel animations on the same overScroll edge. 1910 * 1911 * @param amount The amount to overScroll by. 1912 * @param onTop Should the effect be applied on top of the scroller. 1913 * @param animate Should an animation be performed. 1914 */ 1915 1916 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1917 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) { 1918 setOverScrollAmount(amount, onTop, animate, true); 1919 } 1920 1921 /** 1922 * Set the effective overScroll amount which will be directly reflected in the layout. 1923 * 1924 * @param amount The amount to overScroll by. 1925 * @param onTop Should the effect be applied on top of the scroller. 1926 * @param animate Should an animation be performed. 1927 * @param cancelAnimators Should running animations be cancelled. 1928 */ 1929 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1930 public void setOverScrollAmount(float amount, boolean onTop, boolean animate, 1931 boolean cancelAnimators) { 1932 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop)); 1933 } 1934 1935 /** 1936 * Set the effective overScroll amount which will be directly reflected in the layout. 1937 * 1938 * @param amount The amount to overScroll by. 1939 * @param onTop Should the effect be applied on top of the scroller. 1940 * @param animate Should an animation be performed. 1941 * @param cancelAnimators Should running animations be cancelled. 1942 * @param isRubberbanded The value which will be passed to 1943 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged} 1944 */ 1945 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1946 public void setOverScrollAmount(float amount, boolean onTop, boolean animate, 1947 boolean cancelAnimators, boolean isRubberbanded) { 1948 if (cancelAnimators) { 1949 mStateAnimator.cancelOverScrollAnimators(onTop); 1950 } 1951 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded); 1952 } 1953 1954 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 1955 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate, 1956 boolean isRubberbanded) { 1957 amount = Math.max(0, amount); 1958 if (animate) { 1959 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded); 1960 } else { 1961 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop); 1962 mAmbientState.setOverScrollAmount(amount, onTop); 1963 if (onTop) { 1964 notifyOverscrollTopListener(amount, isRubberbanded); 1965 } 1966 updateStackPosition(); 1967 requestChildrenUpdate(); 1968 } 1969 } 1970 1971 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1972 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) { 1973 mExpandHelper.onlyObserveMovements(amount > 1.0f); 1974 if (mDontReportNextOverScroll) { 1975 mDontReportNextOverScroll = false; 1976 return; 1977 } 1978 if (mOverscrollTopChangedListener != null) { 1979 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded); 1980 } 1981 } 1982 1983 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1984 public void setOverscrollTopChangedListener( 1985 OnOverscrollTopChangedListener overscrollTopChangedListener) { 1986 mOverscrollTopChangedListener = overscrollTopChangedListener; 1987 } 1988 1989 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1990 public float getCurrentOverScrollAmount(boolean top) { 1991 return mAmbientState.getOverScrollAmount(top); 1992 } 1993 1994 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 1995 public float getCurrentOverScrolledPixels(boolean top) { 1996 return top ? mOverScrolledTopPixels : mOverScrolledBottomPixels; 1997 } 1998 1999 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2000 private void setOverScrolledPixels(float amount, boolean onTop) { 2001 if (onTop) { 2002 mOverScrolledTopPixels = amount; 2003 } else { 2004 mOverScrolledBottomPixels = amount; 2005 } 2006 } 2007 2008 /** 2009 * Scrolls to the given position, overscrolling if needed. If called during a fling and the 2010 * position exceeds the provided maximum overscroll, springs back instead. 2011 * 2012 * @param scrollY The target scroll position. 2013 * @param clampedY Whether this value was clamped by the calling method, meaning we've reached 2014 * the overscroll limit. 2015 */ 2016 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2017 private void onCustomOverScrolled(int scrollY, boolean clampedY) { 2018 // Treat animating scrolls differently; see #computeScroll() for why. 2019 if (!mScroller.isFinished()) { 2020 setOwnScrollY(scrollY); 2021 if (clampedY) { 2022 springBack(); 2023 } else { 2024 float overScrollTop = getCurrentOverScrollAmount(true); 2025 if (mOwnScrollY < 0) { 2026 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true)); 2027 } else { 2028 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true)); 2029 } 2030 } 2031 } else { 2032 setOwnScrollY(scrollY); 2033 } 2034 } 2035 2036 /** 2037 * Springs back from an overscroll by stopping the {@link #mScroller} and animating the 2038 * overscroll amount back to zero. 2039 */ 2040 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 2041 private void springBack() { 2042 int scrollRange = getScrollRange(); 2043 boolean overScrolledTop = mOwnScrollY <= 0; 2044 boolean overScrolledBottom = mOwnScrollY >= scrollRange; 2045 if (overScrolledTop || overScrolledBottom) { 2046 boolean onTop; 2047 float newAmount; 2048 if (overScrolledTop) { 2049 onTop = true; 2050 newAmount = -mOwnScrollY; 2051 setOwnScrollY(0); 2052 mDontReportNextOverScroll = true; 2053 } else { 2054 onTop = false; 2055 newAmount = mOwnScrollY - scrollRange; 2056 setOwnScrollY(scrollRange); 2057 } 2058 setOverScrollAmount(newAmount, onTop, false); 2059 setOverScrollAmount(0.0f, onTop, true); 2060 mScroller.forceFinished(true); 2061 } 2062 } 2063 2064 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2065 private int getScrollRange() { 2066 // In current design, it only use the top HUN to treat all of HUNs 2067 // although there are more than one HUNs 2068 int contentHeight = mContentHeight; 2069 if (!isExpanded() && mInHeadsUpPinnedMode) { 2070 contentHeight = mHeadsUpInset + getTopHeadsUpPinnedHeight(); 2071 } 2072 int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight); 2073 int imeInset = getImeInset(); 2074 scrollRange += Math.min(imeInset, Math.max(0, contentHeight - (getHeight() - imeInset))); 2075 if (scrollRange > 0) { 2076 scrollRange = Math.max(getScrollAmountToScrollBoundary(), scrollRange); 2077 } 2078 return scrollRange; 2079 } 2080 2081 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2082 private int getImeInset() { 2083 return Math.max(0, mBottomInset - (getRootView().getHeight() - getHeight())); 2084 } 2085 2086 /** 2087 * @return the first child which has visibility unequal to GONE 2088 */ 2089 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2090 public ExpandableView getFirstChildNotGone() { 2091 int childCount = getChildCount(); 2092 for (int i = 0; i < childCount; i++) { 2093 View child = getChildAt(i); 2094 if (child.getVisibility() != View.GONE && child != mShelf) { 2095 return (ExpandableView) child; 2096 } 2097 } 2098 return null; 2099 } 2100 2101 /** 2102 * @return The first child which has visibility unequal to GONE which is currently below the 2103 * given translationY or equal to it. 2104 */ 2105 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2106 private View getFirstChildBelowTranlsationY(float translationY, boolean ignoreChildren) { 2107 int childCount = getChildCount(); 2108 for (int i = 0; i < childCount; i++) { 2109 View child = getChildAt(i); 2110 if (child.getVisibility() == View.GONE) { 2111 continue; 2112 } 2113 float rowTranslation = child.getTranslationY(); 2114 if (rowTranslation >= translationY) { 2115 return child; 2116 } else if (!ignoreChildren && child instanceof ExpandableNotificationRow) { 2117 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 2118 if (row.isSummaryWithChildren() && row.areChildrenExpanded()) { 2119 List<ExpandableNotificationRow> notificationChildren = 2120 row.getAttachedChildren(); 2121 for (int childIndex = 0; childIndex < notificationChildren.size(); 2122 childIndex++) { 2123 ExpandableNotificationRow rowChild = notificationChildren.get(childIndex); 2124 if (rowChild.getTranslationY() + rowTranslation >= translationY) { 2125 return rowChild; 2126 } 2127 } 2128 } 2129 } 2130 } 2131 return null; 2132 } 2133 2134 /** 2135 * @return the last child which has visibility unequal to GONE 2136 */ 2137 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2138 public ExpandableView getLastChildNotGone() { 2139 int childCount = getChildCount(); 2140 for (int i = childCount - 1; i >= 0; i--) { 2141 View child = getChildAt(i); 2142 if (child.getVisibility() != View.GONE && child != mShelf) { 2143 return (ExpandableView) child; 2144 } 2145 } 2146 return null; 2147 } 2148 2149 private ExpandableNotificationRow getLastRowNotGone() { 2150 int childCount = getChildCount(); 2151 for (int i = childCount - 1; i >= 0; i--) { 2152 View child = getChildAt(i); 2153 if (child instanceof ExpandableNotificationRow && child.getVisibility() != View.GONE) { 2154 return (ExpandableNotificationRow) child; 2155 } 2156 } 2157 return null; 2158 } 2159 2160 /** 2161 * @return the number of children which have visibility unequal to GONE 2162 */ 2163 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2164 public int getNotGoneChildCount() { 2165 int childCount = getChildCount(); 2166 int count = 0; 2167 for (int i = 0; i < childCount; i++) { 2168 ExpandableView child = (ExpandableView) getChildAt(i); 2169 if (child.getVisibility() != View.GONE && !child.willBeGone() && child != mShelf) { 2170 count++; 2171 } 2172 } 2173 return count; 2174 } 2175 2176 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 2177 private void updateContentHeight() { 2178 final float scrimTopPadding = mAmbientState.isOnKeyguard() ? 0 : mMinimumPaddings; 2179 int height = (int) scrimTopPadding; 2180 float previousPaddingRequest = mPaddingBetweenElements; 2181 int numShownItems = 0; 2182 int numShownNotifs = 0; 2183 boolean finish = false; 2184 int maxDisplayedNotifications = mMaxDisplayedNotifications; 2185 ExpandableView previousView = null; 2186 2187 for (int i = 0; i < getChildCount(); i++) { 2188 ExpandableView expandableView = (ExpandableView) getChildAt(i); 2189 boolean footerViewOnLockScreen = expandableView == mFooterView && onKeyguard(); 2190 2191 if (expandableView.getVisibility() != View.GONE 2192 && !expandableView.hasNoContentHeight() && !footerViewOnLockScreen) { 2193 2194 boolean limitReached = maxDisplayedNotifications != -1 2195 && numShownNotifs >= maxDisplayedNotifications; 2196 final float viewHeight; 2197 if (limitReached) { 2198 viewHeight = mShelf.getIntrinsicHeight(); 2199 finish = true; 2200 } else { 2201 viewHeight = expandableView.getIntrinsicHeight(); 2202 } 2203 if (height != 0) { 2204 height += mPaddingBetweenElements; 2205 } 2206 float gapHeight = calculateGapHeight(previousView, expandableView, numShownNotifs); 2207 height += gapHeight; 2208 height += viewHeight; 2209 2210 numShownItems++; 2211 if (viewHeight > 0 || !(expandableView instanceof MediaHeaderView)) { 2212 // Only count the media as a notification if it has a positive height. 2213 numShownNotifs++; 2214 } 2215 previousView = expandableView; 2216 if (finish) { 2217 break; 2218 } 2219 } 2220 } 2221 mIntrinsicContentHeight = height; 2222 2223 // The topPadding can be bigger than the regular padding when qs is expanded, in that 2224 // state the maxPanelHeight and the contentHeight should be bigger 2225 mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomMargin; 2226 updateScrollability(); 2227 clampScrollPosition(); 2228 updateStackPosition(); 2229 mAmbientState.setContentHeight(mContentHeight); 2230 } 2231 2232 /** 2233 * Calculate the gap height between two different views 2234 * 2235 * @param previous the previousView 2236 * @param current the currentView 2237 * @param visibleIndex the visible index in the list 2238 * 2239 * @return the gap height needed before the current view 2240 */ 2241 public float calculateGapHeight( 2242 ExpandableView previous, 2243 ExpandableView current, 2244 int visibleIndex 2245 ) { 2246 return mStackScrollAlgorithm.getGapHeightForChild(mSectionsManager, visibleIndex, current, 2247 previous); 2248 } 2249 2250 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2251 public boolean hasPulsingNotifications() { 2252 return mPulsing; 2253 } 2254 2255 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2256 private void updateScrollability() { 2257 boolean scrollable = !mQsExpanded && getScrollRange() > 0; 2258 if (scrollable != mScrollable) { 2259 mScrollable = scrollable; 2260 setFocusable(scrollable); 2261 updateForwardAndBackwardScrollability(); 2262 } 2263 } 2264 2265 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2266 private void updateForwardAndBackwardScrollability() { 2267 boolean forwardScrollable = mScrollable && !mScrollAdapter.isScrolledToBottom(); 2268 boolean backwardsScrollable = mScrollable && !mScrollAdapter.isScrolledToTop(); 2269 boolean changed = forwardScrollable != mForwardScrollable 2270 || backwardsScrollable != mBackwardScrollable; 2271 mForwardScrollable = forwardScrollable; 2272 mBackwardScrollable = backwardsScrollable; 2273 if (changed) { 2274 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); 2275 } 2276 } 2277 2278 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2279 private void updateBackground() { 2280 // No need to update the background color if it's not being drawn. 2281 if (!mShouldDrawNotificationBackground) { 2282 return; 2283 } 2284 2285 updateBackgroundBounds(); 2286 if (didSectionBoundsChange()) { 2287 boolean animate = mAnimateNextSectionBoundsChange || mAnimateNextBackgroundTop 2288 || mAnimateNextBackgroundBottom || areSectionBoundsAnimating(); 2289 if (!isExpanded()) { 2290 abortBackgroundAnimators(); 2291 animate = false; 2292 } 2293 if (animate) { 2294 startBackgroundAnimation(); 2295 } else { 2296 for (NotificationSection section : mSections) { 2297 section.resetCurrentBounds(); 2298 } 2299 invalidate(); 2300 } 2301 } else { 2302 abortBackgroundAnimators(); 2303 } 2304 mAnimateNextBackgroundTop = false; 2305 mAnimateNextBackgroundBottom = false; 2306 mAnimateNextSectionBoundsChange = false; 2307 } 2308 2309 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 2310 private void abortBackgroundAnimators() { 2311 for (NotificationSection section : mSections) { 2312 section.cancelAnimators(); 2313 } 2314 } 2315 2316 private boolean didSectionBoundsChange() { 2317 for (NotificationSection section : mSections) { 2318 if (section.didBoundsChange()) { 2319 return true; 2320 } 2321 } 2322 return false; 2323 } 2324 2325 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 2326 private boolean areSectionBoundsAnimating() { 2327 for (NotificationSection section : mSections) { 2328 if (section.areBoundsAnimating()) { 2329 return true; 2330 } 2331 } 2332 return false; 2333 } 2334 2335 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 2336 private void startBackgroundAnimation() { 2337 // TODO(kprevas): do we still need separate fields for top/bottom? 2338 // or can each section manage its own animation state? 2339 NotificationSection firstVisibleSection = getFirstVisibleSection(); 2340 NotificationSection lastVisibleSection = getLastVisibleSection(); 2341 for (NotificationSection section : mSections) { 2342 section.startBackgroundAnimation( 2343 section == firstVisibleSection 2344 ? mAnimateNextBackgroundTop 2345 : mAnimateNextSectionBoundsChange, 2346 section == lastVisibleSection 2347 ? mAnimateNextBackgroundBottom 2348 : mAnimateNextSectionBoundsChange); 2349 } 2350 } 2351 2352 /** 2353 * Update the background bounds to the new desired bounds 2354 */ 2355 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2356 private void updateBackgroundBounds() { 2357 int left = mSidePaddings; 2358 int right = getWidth() - mSidePaddings; 2359 for (NotificationSection section : mSections) { 2360 section.getBounds().left = left; 2361 section.getBounds().right = right; 2362 } 2363 2364 if (!mIsExpanded) { 2365 for (NotificationSection section : mSections) { 2366 section.getBounds().top = 0; 2367 section.getBounds().bottom = 0; 2368 } 2369 return; 2370 } 2371 int minTopPosition; 2372 NotificationSection lastSection = getLastVisibleSection(); 2373 boolean onKeyguard = mStatusBarState == StatusBarState.KEYGUARD; 2374 if (!onKeyguard) { 2375 minTopPosition = (int) (mTopPadding + mStackTranslation); 2376 } else if (lastSection == null) { 2377 minTopPosition = mTopPadding; 2378 } else { 2379 // The first sections could be empty while there could still be elements in later 2380 // sections. The position of these first few sections is determined by the position of 2381 // the first visible section. 2382 NotificationSection firstVisibleSection = getFirstVisibleSection(); 2383 firstVisibleSection.updateBounds(0 /* minTopPosition*/, 0 /* minBottomPosition */, 2384 false /* shiftPulsingWithFirst */); 2385 minTopPosition = firstVisibleSection.getBounds().top; 2386 } 2387 boolean shiftPulsingWithFirst = mNumHeadsUp <= 1 2388 && (mAmbientState.isDozing() 2389 || (mKeyguardBypassEnabledProvider.getBypassEnabled() && onKeyguard)); 2390 for (NotificationSection section : mSections) { 2391 int minBottomPosition = minTopPosition; 2392 if (section == lastSection) { 2393 // We need to make sure the section goes all the way to the shelf 2394 minBottomPosition = (int) (ViewState.getFinalTranslationY(mShelf) 2395 + mShelf.getIntrinsicHeight()); 2396 } 2397 minTopPosition = section.updateBounds(minTopPosition, minBottomPosition, 2398 shiftPulsingWithFirst); 2399 shiftPulsingWithFirst = false; 2400 } 2401 } 2402 2403 private NotificationSection getFirstVisibleSection() { 2404 for (NotificationSection section : mSections) { 2405 if (section.getFirstVisibleChild() != null) { 2406 return section; 2407 } 2408 } 2409 return null; 2410 } 2411 2412 private NotificationSection getLastVisibleSection() { 2413 for (int i = mSections.length - 1; i >= 0; i--) { 2414 NotificationSection section = mSections[i]; 2415 if (section.getLastVisibleChild() != null) { 2416 return section; 2417 } 2418 } 2419 return null; 2420 } 2421 2422 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2423 private ExpandableView getLastChildWithBackground() { 2424 int childCount = getChildCount(); 2425 for (int i = childCount - 1; i >= 0; i--) { 2426 ExpandableView child = (ExpandableView) getChildAt(i); 2427 if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView) 2428 && child != mShelf) { 2429 return child; 2430 } 2431 } 2432 return null; 2433 } 2434 2435 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2436 private ExpandableView getFirstChildWithBackground() { 2437 int childCount = getChildCount(); 2438 for (int i = 0; i < childCount; i++) { 2439 ExpandableView child = (ExpandableView) getChildAt(i); 2440 if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView) 2441 && child != mShelf) { 2442 return child; 2443 } 2444 } 2445 return null; 2446 } 2447 2448 //TODO: We shouldn't have to generate this list every time 2449 private List<ExpandableView> getChildrenWithBackground() { 2450 ArrayList<ExpandableView> children = new ArrayList<>(); 2451 int childCount = getChildCount(); 2452 for (int i = 0; i < childCount; i++) { 2453 ExpandableView child = (ExpandableView) getChildAt(i); 2454 if (child.getVisibility() != View.GONE 2455 && !(child instanceof StackScrollerDecorView) 2456 && child != mShelf) { 2457 children.add(child); 2458 } 2459 } 2460 return children; 2461 } 2462 2463 /** 2464 * Fling the scroll view 2465 * 2466 * @param velocityY The initial velocity in the Y direction. Positive 2467 * numbers mean that the finger/cursor is moving down the screen, 2468 * which means we want to scroll towards the top. 2469 */ 2470 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2471 protected void fling(int velocityY) { 2472 if (getChildCount() > 0) { 2473 float topAmount = getCurrentOverScrollAmount(true); 2474 float bottomAmount = getCurrentOverScrollAmount(false); 2475 if (velocityY < 0 && topAmount > 0) { 2476 setOwnScrollY(mOwnScrollY - (int) topAmount); 2477 mDontReportNextOverScroll = true; 2478 setOverScrollAmount(0, true, false); 2479 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */) 2480 * mOverflingDistance + topAmount; 2481 } else if (velocityY > 0 && bottomAmount > 0) { 2482 setOwnScrollY((int) (mOwnScrollY + bottomAmount)); 2483 setOverScrollAmount(0, false, false); 2484 mMaxOverScroll = Math.abs(velocityY) / 1000f 2485 * getRubberBandFactor(false /* onTop */) * mOverflingDistance 2486 + bottomAmount; 2487 } else { 2488 // it will be set once we reach the boundary 2489 mMaxOverScroll = 0.0f; 2490 } 2491 int scrollRange = getScrollRange(); 2492 int minScrollY = Math.max(0, scrollRange); 2493 if (mExpandedInThisMotion) { 2494 minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand); 2495 } 2496 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0, 2497 mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2); 2498 2499 animateScroll(); 2500 } 2501 } 2502 2503 /** 2504 * @return Whether a fling performed on the top overscroll edge lead to the expanded 2505 * overScroll view (i.e QS). 2506 */ 2507 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2508 private boolean shouldOverScrollFling(int initialVelocity) { 2509 float topOverScroll = getCurrentOverScrollAmount(true); 2510 return mScrolledToTopOnFirstDown 2511 && !mExpandedInThisMotion 2512 && (initialVelocity > mMinimumVelocity 2513 || (topOverScroll > mMinTopOverScrollToEscape && initialVelocity > 0)); 2514 } 2515 2516 /** 2517 * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into 2518 * account. 2519 * 2520 * @param qsHeight the top padding imposed by the quick settings panel 2521 * @param animate whether to animate the change 2522 */ 2523 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2524 public void updateTopPadding(float qsHeight, boolean animate) { 2525 int topPadding = (int) qsHeight; 2526 int minStackHeight = getLayoutMinHeight(); 2527 if (topPadding + minStackHeight > getHeight()) { 2528 mTopPaddingOverflow = topPadding + minStackHeight - getHeight(); 2529 } else { 2530 mTopPaddingOverflow = 0; 2531 } 2532 setTopPadding(topPadding, animate && !mKeyguardBypassEnabledProvider.getBypassEnabled()); 2533 setExpandedHeight(mExpandedHeight); 2534 } 2535 2536 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2537 public void setMaxTopPadding(int maxTopPadding) { 2538 mMaxTopPadding = maxTopPadding; 2539 } 2540 2541 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2542 public int getLayoutMinHeight() { 2543 if (isHeadsUpTransition()) { 2544 ExpandableNotificationRow trackedHeadsUpRow = mAmbientState.getTrackedHeadsUpRow(); 2545 if (trackedHeadsUpRow.isAboveShelf()) { 2546 int hunDistance = (int) MathUtils.lerp( 2547 0, 2548 getPositionInLinearLayout(trackedHeadsUpRow), 2549 mAmbientState.getAppearFraction()); 2550 return getTopHeadsUpPinnedHeight() + hunDistance; 2551 } else { 2552 return getTopHeadsUpPinnedHeight(); 2553 } 2554 } 2555 return mShelf.getVisibility() == GONE ? 0 : mShelf.getIntrinsicHeight(); 2556 } 2557 2558 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2559 public float getTopPaddingOverflow() { 2560 return mTopPaddingOverflow; 2561 } 2562 2563 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2564 private int clampPadding(int desiredPadding) { 2565 return Math.max(desiredPadding, mIntrinsicPadding); 2566 } 2567 2568 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2569 private float getRubberBandFactor(boolean onTop) { 2570 if (!onTop) { 2571 return RUBBER_BAND_FACTOR_NORMAL; 2572 } 2573 if (mExpandedInThisMotion) { 2574 return RUBBER_BAND_FACTOR_AFTER_EXPAND; 2575 } else if (mIsExpansionChanging || mPanelTracking) { 2576 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND; 2577 } else if (mScrolledToTopOnFirstDown) { 2578 return 1.0f; 2579 } 2580 return RUBBER_BAND_FACTOR_NORMAL; 2581 } 2582 2583 /** 2584 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is 2585 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the 2586 * overscroll view (e.g. expand QS). 2587 */ 2588 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2589 private boolean isRubberbanded(boolean onTop) { 2590 return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking 2591 || !mScrolledToTopOnFirstDown; 2592 } 2593 2594 2595 2596 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2597 public void setChildTransferInProgress(boolean childTransferInProgress) { 2598 Assert.isMainThread(); 2599 mChildTransferInProgress = childTransferInProgress; 2600 } 2601 2602 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 2603 @Override 2604 public void onViewRemoved(View child) { 2605 super.onViewRemoved(child); 2606 // we only call our internal methods if this is actually a removal and not just a 2607 // notification which becomes a child notification 2608 if (!mChildTransferInProgress) { 2609 onViewRemovedInternal((ExpandableView) child, this); 2610 } 2611 } 2612 2613 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 2614 public void cleanUpViewStateForEntry(NotificationEntry entry) { 2615 View child = entry.getRow(); 2616 if (child == mSwipeHelper.getTranslatingParentView()) { 2617 mSwipeHelper.clearTranslatingParentView(); 2618 } 2619 } 2620 2621 @ShadeViewRefactor(RefactorComponent.COORDINATOR) 2622 private void onViewRemovedInternal(ExpandableView child, ViewGroup container) { 2623 if (mChangePositionInProgress) { 2624 // This is only a position change, don't do anything special 2625 return; 2626 } 2627 child.setOnHeightChangedListener(null); 2628 updateScrollStateForRemovedChild(child); 2629 boolean animationGenerated = generateRemoveAnimation(child); 2630 if (animationGenerated) { 2631 if (!mSwipedOutViews.contains(child) || !isFullySwipedOut(child)) { 2632 container.addTransientView(child, 0); 2633 child.setTransientContainer(container); 2634 } 2635 } else { 2636 mSwipedOutViews.remove(child); 2637 } 2638 updateAnimationState(false, child); 2639 2640 focusNextViewIfFocused(child); 2641 } 2642 2643 /** 2644 * Has this view been fully swiped out such that it's not visible anymore. 2645 */ 2646 public boolean isFullySwipedOut(ExpandableView child) { 2647 return Math.abs(child.getTranslation()) >= Math.abs(getTotalTranslationLength(child)); 2648 } 2649 2650 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) focusNextViewIfFocused(View view)2651 private void focusNextViewIfFocused(View view) { 2652 if (view instanceof ExpandableNotificationRow) { 2653 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 2654 if (row.shouldRefocusOnDismiss()) { 2655 View nextView = row.getChildAfterViewWhenDismissed(); 2656 if (nextView == null) { 2657 View groupParentWhenDismissed = row.getGroupParentWhenDismissed(); 2658 nextView = getFirstChildBelowTranlsationY(groupParentWhenDismissed != null 2659 ? groupParentWhenDismissed.getTranslationY() 2660 : view.getTranslationY(), true /* ignoreChildren */); 2661 } 2662 if (nextView != null) { 2663 nextView.requestAccessibilityFocus(); 2664 } 2665 } 2666 } 2667 2668 } 2669 2670 @ShadeViewRefactor(RefactorComponent.ADAPTER) isChildInGroup(View child)2671 private boolean isChildInGroup(View child) { 2672 return child instanceof ExpandableNotificationRow 2673 && mGroupMembershipManager.isChildInGroup( 2674 ((ExpandableNotificationRow) child).getEntry()); 2675 } 2676 2677 /** 2678 * Generate a remove animation for a child view. 2679 * 2680 * @param child The view to generate the remove animation for. 2681 * @return Whether an animation was generated. 2682 */ 2683 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) generateRemoveAnimation(ExpandableView child)2684 boolean generateRemoveAnimation(ExpandableView child) { 2685 String key = ""; 2686 if (DEBUG_REMOVE_ANIMATION) { 2687 if (child instanceof ExpandableNotificationRow) { 2688 key = ((ExpandableNotificationRow) child).getEntry().getKey(); 2689 } 2690 Log.d(TAG, "generateRemoveAnimation " + key); 2691 } 2692 if (removeRemovedChildFromHeadsUpChangeAnimations(child)) { 2693 if (DEBUG_REMOVE_ANIMATION) { 2694 Log.d(TAG, "removedBecauseOfHeadsUp " + key); 2695 } 2696 mAddedHeadsUpChildren.remove(child); 2697 return false; 2698 } 2699 if (isClickedHeadsUp(child)) { 2700 // An animation is already running, add it transiently 2701 mClearTransientViewsWhenFinished.add(child); 2702 return true; 2703 } 2704 if (DEBUG_REMOVE_ANIMATION) { 2705 Log.d(TAG, "generateRemove " + key 2706 + "\nmIsExpanded " + mIsExpanded 2707 + "\nmAnimationsEnabled " + mAnimationsEnabled 2708 + "\n!invisible group " + !isChildInInvisibleGroup(child)); 2709 } 2710 if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) { 2711 if (!mChildrenToAddAnimated.contains(child)) { 2712 if (DEBUG_REMOVE_ANIMATION) { 2713 Log.d(TAG, "needsAnimation = true " + key); 2714 } 2715 // Generate Animations 2716 mChildrenToRemoveAnimated.add(child); 2717 mNeedsAnimation = true; 2718 return true; 2719 } else { 2720 mChildrenToAddAnimated.remove(child); 2721 mFromMoreCardAdditions.remove(child); 2722 return false; 2723 } 2724 } 2725 return false; 2726 } 2727 2728 @ShadeViewRefactor(RefactorComponent.ADAPTER) isClickedHeadsUp(View child)2729 private boolean isClickedHeadsUp(View child) { 2730 return HeadsUpUtil.isClickedHeadsUpNotification(child); 2731 } 2732 2733 /** 2734 * Remove a removed child view from the heads up animations if it was just added there 2735 * 2736 * @return whether any child was removed from the list to animate and the view was just added 2737 */ 2738 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) removeRemovedChildFromHeadsUpChangeAnimations(View child)2739 private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) { 2740 boolean hasAddEvent = false; 2741 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) { 2742 ExpandableNotificationRow row = eventPair.first; 2743 boolean isHeadsUp = eventPair.second; 2744 if (child == row) { 2745 mTmpList.add(eventPair); 2746 hasAddEvent |= isHeadsUp; 2747 } 2748 } 2749 if (hasAddEvent) { 2750 // This child was just added lets remove all events. 2751 mHeadsUpChangeAnimations.removeAll(mTmpList); 2752 ((ExpandableNotificationRow) child).setHeadsUpAnimatingAway(false); 2753 } 2754 mTmpList.clear(); 2755 return hasAddEvent && mAddedHeadsUpChildren.contains(child); 2756 } 2757 2758 // TODO (b/162832756): remove since this won't happen in new pipeline (we prune groups in 2759 // ShadeListBuilder) 2760 /** 2761 * @param child the child to query 2762 * @return whether a view is not a top level child but a child notification and that group is 2763 * not expanded 2764 */ 2765 @ShadeViewRefactor(RefactorComponent.ADAPTER) isChildInInvisibleGroup(View child)2766 private boolean isChildInInvisibleGroup(View child) { 2767 if (child instanceof ExpandableNotificationRow) { 2768 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 2769 NotificationEntry groupSummary = 2770 mGroupMembershipManager.getGroupSummary(row.getEntry()); 2771 if (groupSummary != null && groupSummary.getRow() != row) { 2772 return row.getVisibility() == View.INVISIBLE; 2773 } 2774 } 2775 return false; 2776 } 2777 2778 /** 2779 * Updates the scroll position when a child was removed 2780 * 2781 * @param removedChild the removed child 2782 */ 2783 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) updateScrollStateForRemovedChild(ExpandableView removedChild)2784 private void updateScrollStateForRemovedChild(ExpandableView removedChild) { 2785 final int startingPosition = getPositionInLinearLayout(removedChild); 2786 final int childHeight = getIntrinsicHeight(removedChild) + mPaddingBetweenElements; 2787 final int endPosition = startingPosition + childHeight; 2788 final int scrollBoundaryStart = getScrollAmountToScrollBoundary(); 2789 mAnimateStackYForContentHeightChange = true; 2790 // This is reset onLayout 2791 if (endPosition <= mOwnScrollY - scrollBoundaryStart) { 2792 // This child is fully scrolled of the top, so we have to deduct its height from the 2793 // scrollPosition 2794 setOwnScrollY(mOwnScrollY - childHeight); 2795 } else if (startingPosition < mOwnScrollY - scrollBoundaryStart) { 2796 // This child is currently being scrolled into, set the scroll position to the 2797 // start of this child 2798 setOwnScrollY(startingPosition + scrollBoundaryStart); 2799 } 2800 } 2801 2802 /** 2803 * @return the amount of scrolling needed to start clipping notifications. 2804 */ getScrollAmountToScrollBoundary()2805 private int getScrollAmountToScrollBoundary() { 2806 if (mShouldUseSplitNotificationShade) { 2807 return mSidePaddings; 2808 } 2809 return mTopPadding - mQsScrollBoundaryPosition; 2810 } 2811 2812 @ShadeViewRefactor(RefactorComponent.COORDINATOR) getIntrinsicHeight(View view)2813 private int getIntrinsicHeight(View view) { 2814 if (view instanceof ExpandableView) { 2815 ExpandableView expandableView = (ExpandableView) view; 2816 return expandableView.getIntrinsicHeight(); 2817 } 2818 return view.getHeight(); 2819 } 2820 2821 @ShadeViewRefactor(RefactorComponent.COORDINATOR) getPositionInLinearLayout(View requestedView)2822 public int getPositionInLinearLayout(View requestedView) { 2823 ExpandableNotificationRow childInGroup = null; 2824 ExpandableNotificationRow requestedRow = null; 2825 if (isChildInGroup(requestedView)) { 2826 // We're asking for a child in a group. Calculate the position of the parent first, 2827 // then within the parent. 2828 childInGroup = (ExpandableNotificationRow) requestedView; 2829 requestedView = requestedRow = childInGroup.getNotificationParent(); 2830 } 2831 int position = 0; 2832 for (int i = 0; i < getChildCount(); i++) { 2833 ExpandableView child = (ExpandableView) getChildAt(i); 2834 boolean notGone = child.getVisibility() != View.GONE; 2835 if (notGone && !child.hasNoContentHeight()) { 2836 if (position != 0) { 2837 position += mPaddingBetweenElements; 2838 } 2839 } 2840 if (child == requestedView) { 2841 if (requestedRow != null) { 2842 position += requestedRow.getPositionOfChild(childInGroup); 2843 } 2844 return position; 2845 } 2846 if (notGone) { 2847 position += getIntrinsicHeight(child); 2848 } 2849 } 2850 return 0; 2851 } 2852 2853 @Override 2854 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) onViewAdded(View child)2855 public void onViewAdded(View child) { 2856 super.onViewAdded(child); 2857 if (child instanceof ExpandableView) { 2858 onViewAddedInternal((ExpandableView) child); 2859 } 2860 } 2861 2862 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) updateFirstAndLastBackgroundViews()2863 private void updateFirstAndLastBackgroundViews() { 2864 NotificationSection firstSection = getFirstVisibleSection(); 2865 NotificationSection lastSection = getLastVisibleSection(); 2866 ExpandableView previousFirstChild = 2867 firstSection == null ? null : firstSection.getFirstVisibleChild(); 2868 ExpandableView previousLastChild = 2869 lastSection == null ? null : lastSection.getLastVisibleChild(); 2870 2871 ExpandableView firstChild = getFirstChildWithBackground(); 2872 ExpandableView lastChild = getLastChildWithBackground(); 2873 boolean sectionViewsChanged = mSectionsManager.updateFirstAndLastViewsForAllSections( 2874 mSections, getChildrenWithBackground()); 2875 2876 if (mAnimationsEnabled && mIsExpanded) { 2877 mAnimateNextBackgroundTop = firstChild != previousFirstChild; 2878 mAnimateNextBackgroundBottom = lastChild != previousLastChild || mAnimateBottomOnLayout; 2879 mAnimateNextSectionBoundsChange = sectionViewsChanged; 2880 } else { 2881 mAnimateNextBackgroundTop = false; 2882 mAnimateNextBackgroundBottom = false; 2883 mAnimateNextSectionBoundsChange = false; 2884 } 2885 mAmbientState.setLastVisibleBackgroundChild(lastChild); 2886 // TODO: Refactor SectionManager and put the RoundnessManager there. 2887 mController.getNoticationRoundessManager().updateRoundedChildren(mSections); 2888 mAnimateBottomOnLayout = false; 2889 invalidate(); 2890 } 2891 2892 @ShadeViewRefactor(RefactorComponent.COORDINATOR) onViewAddedInternal(ExpandableView child)2893 private void onViewAddedInternal(ExpandableView child) { 2894 updateHideSensitiveForChild(child); 2895 child.setOnHeightChangedListener(mOnChildHeightChangedListener); 2896 generateAddAnimation(child, false /* fromMoreCard */); 2897 updateAnimationState(child); 2898 updateChronometerForChild(child); 2899 if (child instanceof ExpandableNotificationRow) { 2900 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 2901 row.setDismissRtl(mDismissRtl); 2902 row.setDismissUsingRowTranslationX(mDismissUsingRowTranslationX); 2903 2904 } 2905 } 2906 2907 @ShadeViewRefactor(RefactorComponent.COORDINATOR) updateHideSensitiveForChild(ExpandableView child)2908 private void updateHideSensitiveForChild(ExpandableView child) { 2909 child.setHideSensitiveForIntrinsicHeight(mAmbientState.isHideSensitive()); 2910 } 2911 2912 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer)2913 public void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer) { 2914 onViewRemovedInternal(row, childrenContainer); 2915 } 2916 notifyGroupChildAdded(ExpandableView row)2917 public void notifyGroupChildAdded(ExpandableView row) { 2918 onViewAddedInternal(row); 2919 } 2920 2921 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) setAnimationsEnabled(boolean animationsEnabled)2922 public void setAnimationsEnabled(boolean animationsEnabled) { 2923 mAnimationsEnabled = animationsEnabled; 2924 updateNotificationAnimationStates(); 2925 if (!animationsEnabled) { 2926 mSwipedOutViews.clear(); 2927 mChildrenToRemoveAnimated.clear(); 2928 clearTemporaryViewsInGroup(this); 2929 } 2930 } 2931 2932 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) updateNotificationAnimationStates()2933 private void updateNotificationAnimationStates() { 2934 boolean running = mAnimationsEnabled || hasPulsingNotifications(); 2935 mShelf.setAnimationsEnabled(running); 2936 int childCount = getChildCount(); 2937 for (int i = 0; i < childCount; i++) { 2938 View child = getChildAt(i); 2939 running &= mIsExpanded || isPinnedHeadsUp(child); 2940 updateAnimationState(running, child); 2941 } 2942 } 2943 2944 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) updateAnimationState(View child)2945 void updateAnimationState(View child) { 2946 updateAnimationState((mAnimationsEnabled || hasPulsingNotifications()) 2947 && (mIsExpanded || isPinnedHeadsUp(child)), child); 2948 } 2949 2950 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setExpandingNotification(ExpandableNotificationRow row)2951 void setExpandingNotification(ExpandableNotificationRow row) { 2952 if (mExpandingNotificationRow != null && row == null) { 2953 // Let's unset the clip path being set during launch 2954 mExpandingNotificationRow.setExpandingClipPath(null); 2955 ExpandableNotificationRow parent = mExpandingNotificationRow.getNotificationParent(); 2956 if (parent != null) { 2957 parent.setExpandingClipPath(null); 2958 } 2959 } 2960 mExpandingNotificationRow = row; 2961 updateLaunchedNotificationClipPath(); 2962 requestChildrenUpdate(); 2963 } 2964 containsView(View v)2965 public boolean containsView(View v) { 2966 return v.getParent() == this; 2967 } 2968 2969 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) applyExpandAnimationParams(ExpandAnimationParameters params)2970 public void applyExpandAnimationParams(ExpandAnimationParameters params) { 2971 // Modify the clipping for launching notifications 2972 mLaunchAnimationParams = params; 2973 setLaunchingNotification(params != null); 2974 updateLaunchedNotificationClipPath(); 2975 requestChildrenUpdate(); 2976 } 2977 2978 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) updateAnimationState(boolean running, View child)2979 private void updateAnimationState(boolean running, View child) { 2980 if (child instanceof ExpandableNotificationRow) { 2981 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 2982 row.setIconAnimationRunning(running); 2983 } 2984 } 2985 2986 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) isAddOrRemoveAnimationPending()2987 boolean isAddOrRemoveAnimationPending() { 2988 return mNeedsAnimation 2989 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty()); 2990 } 2991 2992 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) generateAddAnimation(ExpandableView child, boolean fromMoreCard)2993 public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) { 2994 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress && !isFullyHidden()) { 2995 // Generate Animations 2996 mChildrenToAddAnimated.add(child); 2997 if (fromMoreCard) { 2998 mFromMoreCardAdditions.add(child); 2999 } 3000 mNeedsAnimation = true; 3001 } 3002 if (isHeadsUp(child) && mAnimationsEnabled && !mChangePositionInProgress 3003 && !isFullyHidden()) { 3004 mAddedHeadsUpChildren.add(child); 3005 mChildrenToAddAnimated.remove(child); 3006 } 3007 } 3008 3009 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) changeViewPosition(ExpandableView child, int newIndex)3010 public void changeViewPosition(ExpandableView child, int newIndex) { 3011 Assert.isMainThread(); 3012 if (mChangePositionInProgress) { 3013 throw new IllegalStateException("Reentrant call to changeViewPosition"); 3014 } 3015 3016 int currentIndex = indexOfChild(child); 3017 3018 if (currentIndex == -1) { 3019 boolean isTransient = false; 3020 if (child instanceof ExpandableNotificationRow 3021 && child.getTransientContainer() != null) { 3022 isTransient = true; 3023 } 3024 Log.e(TAG, "Attempting to re-position " 3025 + (isTransient ? "transient" : "") 3026 + " view {" 3027 + child 3028 + "}"); 3029 return; 3030 } 3031 3032 if (child != null && child.getParent() == this && currentIndex != newIndex) { 3033 mChangePositionInProgress = true; 3034 child.setChangingPosition(true); 3035 removeView(child); 3036 addView(child, newIndex); 3037 child.setChangingPosition(false); 3038 mChangePositionInProgress = false; 3039 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) { 3040 mChildrenChangingPositions.add(child); 3041 mNeedsAnimation = true; 3042 } 3043 } 3044 } 3045 3046 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) startAnimationToState()3047 private void startAnimationToState() { 3048 if (mNeedsAnimation) { 3049 generateAllAnimationEvents(); 3050 mNeedsAnimation = false; 3051 } 3052 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) { 3053 setAnimationRunning(true); 3054 mStateAnimator.startAnimationForEvents(mAnimationEvents, mGoToFullShadeDelay); 3055 mAnimationEvents.clear(); 3056 updateBackground(); 3057 updateViewShadows(); 3058 } else { 3059 applyCurrentState(); 3060 } 3061 mGoToFullShadeDelay = 0; 3062 } 3063 3064 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) generateAllAnimationEvents()3065 private void generateAllAnimationEvents() { 3066 generateHeadsUpAnimationEvents(); 3067 generateChildRemovalEvents(); 3068 generateChildAdditionEvents(); 3069 generatePositionChangeEvents(); 3070 generateTopPaddingEvent(); 3071 generateActivateEvent(); 3072 generateDimmedEvent(); 3073 generateHideSensitiveEvent(); 3074 generateGoToFullShadeEvent(); 3075 generateViewResizeEvent(); 3076 generateGroupExpansionEvent(); 3077 generateAnimateEverythingEvent(); 3078 } 3079 3080 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) generateHeadsUpAnimationEvents()3081 private void generateHeadsUpAnimationEvents() { 3082 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) { 3083 ExpandableNotificationRow row = eventPair.first; 3084 boolean isHeadsUp = eventPair.second; 3085 if (isHeadsUp != row.isHeadsUp()) { 3086 // For cases where we have a heads up showing and appearing again we shouldn't 3087 // do the animations at all. 3088 continue; 3089 } 3090 int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER; 3091 boolean onBottom = false; 3092 boolean pinnedAndClosed = row.isPinned() && !mIsExpanded; 3093 boolean performDisappearAnimation = !mIsExpanded 3094 // Only animate if we still have pinned heads up, otherwise we just have the 3095 // regular collapse animation of the lock screen 3096 || (mKeyguardBypassEnabledProvider.getBypassEnabled() && onKeyguard() 3097 && mInHeadsUpPinnedMode); 3098 if (performDisappearAnimation && !isHeadsUp) { 3099 type = row.wasJustClicked() 3100 ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK 3101 : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR; 3102 if (row.isChildInGroup()) { 3103 // We can otherwise get stuck in there if it was just isolated 3104 row.setHeadsUpAnimatingAway(false); 3105 continue; 3106 } 3107 } else { 3108 ExpandableViewState viewState = row.getViewState(); 3109 if (viewState == null) { 3110 // A view state was never generated for this view, so we don't need to animate 3111 // this. This may happen with notification children. 3112 continue; 3113 } 3114 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) { 3115 if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) { 3116 // Our custom add animation 3117 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR; 3118 } else { 3119 // Normal add animation 3120 type = AnimationEvent.ANIMATION_TYPE_ADD; 3121 } 3122 onBottom = !pinnedAndClosed; 3123 } 3124 } 3125 AnimationEvent event = new AnimationEvent(row, type); 3126 event.headsUpFromBottom = onBottom; 3127 mAnimationEvents.add(event); 3128 } 3129 mHeadsUpChangeAnimations.clear(); 3130 mAddedHeadsUpChildren.clear(); 3131 } 3132 3133 @ShadeViewRefactor(RefactorComponent.COORDINATOR) shouldHunAppearFromBottom(ExpandableViewState viewState)3134 private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) { 3135 if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) { 3136 return false; 3137 } 3138 return true; 3139 } 3140 3141 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) generateGroupExpansionEvent()3142 private void generateGroupExpansionEvent() { 3143 // Generate a group expansion/collapsing event if there is such a group at all 3144 if (mExpandedGroupView != null) { 3145 mAnimationEvents.add(new AnimationEvent(mExpandedGroupView, 3146 AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED)); 3147 mExpandedGroupView = null; 3148 } 3149 } 3150 3151 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) generateViewResizeEvent()3152 private void generateViewResizeEvent() { 3153 if (mNeedViewResizeAnimation) { 3154 boolean hasDisappearAnimation = false; 3155 for (AnimationEvent animationEvent : mAnimationEvents) { 3156 final int type = animationEvent.animationType; 3157 if (type == AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK 3158 || type == AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) { 3159 hasDisappearAnimation = true; 3160 break; 3161 } 3162 } 3163 3164 if (!hasDisappearAnimation) { 3165 mAnimationEvents.add( 3166 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE)); 3167 } 3168 } 3169 mNeedViewResizeAnimation = false; 3170 } 3171 3172 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) generateChildRemovalEvents()3173 private void generateChildRemovalEvents() { 3174 for (ExpandableView child : mChildrenToRemoveAnimated) { 3175 boolean childWasSwipedOut = mSwipedOutViews.contains(child); 3176 3177 // we need to know the view after this one 3178 float removedTranslation = child.getTranslationY(); 3179 boolean ignoreChildren = true; 3180 if (child instanceof ExpandableNotificationRow) { 3181 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 3182 if (row.isRemoved() && row.wasChildInGroupWhenRemoved()) { 3183 removedTranslation = row.getTranslationWhenRemoved(); 3184 ignoreChildren = false; 3185 } 3186 childWasSwipedOut |= isFullySwipedOut(row); 3187 } else if (child instanceof MediaHeaderView) { 3188 childWasSwipedOut = true; 3189 } 3190 if (!childWasSwipedOut) { 3191 Rect clipBounds = child.getClipBounds(); 3192 childWasSwipedOut = clipBounds != null && clipBounds.height() == 0; 3193 3194 if (childWasSwipedOut) { 3195 // Clean up any potential transient views if the child has already been swiped 3196 // out, as we won't be animating it further (due to its height already being 3197 // clipped to 0. 3198 ViewGroup transientContainer = child.getTransientContainer(); 3199 if (transientContainer != null) { 3200 transientContainer.removeTransientView(child); 3201 } 3202 } 3203 } 3204 int animationType = childWasSwipedOut 3205 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT 3206 : AnimationEvent.ANIMATION_TYPE_REMOVE; 3207 AnimationEvent event = new AnimationEvent(child, animationType); 3208 event.viewAfterChangingView = getFirstChildBelowTranlsationY(removedTranslation, 3209 ignoreChildren); 3210 mAnimationEvents.add(event); 3211 mSwipedOutViews.remove(child); 3212 if (DEBUG_REMOVE_ANIMATION) { 3213 String key = ""; 3214 if (child instanceof ExpandableNotificationRow) { 3215 key = ((ExpandableNotificationRow) child).getEntry().getKey(); 3216 } 3217 Log.d(TAG, "created Remove Event - SwipedOut: " + childWasSwipedOut + " " + key); 3218 } 3219 } 3220 mChildrenToRemoveAnimated.clear(); 3221 } 3222 3223 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) generatePositionChangeEvents()3224 private void generatePositionChangeEvents() { 3225 for (ExpandableView child : mChildrenChangingPositions) { 3226 Integer duration = null; 3227 if (child instanceof ExpandableNotificationRow) { 3228 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 3229 if (row.getEntry().isMarkedForUserTriggeredMovement()) { 3230 duration = StackStateAnimator.ANIMATION_DURATION_PRIORITY_CHANGE; 3231 row.getEntry().markForUserTriggeredMovement(false); 3232 } 3233 } 3234 AnimationEvent animEvent = duration == null 3235 ? new AnimationEvent(child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION) 3236 : new AnimationEvent( 3237 child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION, duration); 3238 mAnimationEvents.add(animEvent); 3239 } 3240 mChildrenChangingPositions.clear(); 3241 if (mGenerateChildOrderChangedEvent) { 3242 mAnimationEvents.add(new AnimationEvent(null, 3243 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)); 3244 mGenerateChildOrderChangedEvent = false; 3245 } 3246 } 3247 3248 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) generateChildAdditionEvents()3249 private void generateChildAdditionEvents() { 3250 for (ExpandableView child : mChildrenToAddAnimated) { 3251 if (mFromMoreCardAdditions.contains(child)) { 3252 mAnimationEvents.add(new AnimationEvent(child, 3253 AnimationEvent.ANIMATION_TYPE_ADD, 3254 StackStateAnimator.ANIMATION_DURATION_STANDARD)); 3255 } else { 3256 mAnimationEvents.add(new AnimationEvent(child, 3257 AnimationEvent.ANIMATION_TYPE_ADD)); 3258 } 3259 } 3260 mChildrenToAddAnimated.clear(); 3261 mFromMoreCardAdditions.clear(); 3262 } 3263 3264 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) generateTopPaddingEvent()3265 private void generateTopPaddingEvent() { 3266 if (mTopPaddingNeedsAnimation) { 3267 AnimationEvent event; 3268 if (mAmbientState.isDozing()) { 3269 event = new AnimationEvent(null /* view */, 3270 AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED, 3271 KeyguardSliceView.DEFAULT_ANIM_DURATION); 3272 } else { 3273 event = new AnimationEvent(null /* view */, 3274 AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED); 3275 } 3276 mAnimationEvents.add(event); 3277 } 3278 mTopPaddingNeedsAnimation = false; 3279 } 3280 3281 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) generateActivateEvent()3282 private void generateActivateEvent() { 3283 if (mActivateNeedsAnimation) { 3284 mAnimationEvents.add( 3285 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD)); 3286 } 3287 mActivateNeedsAnimation = false; 3288 } 3289 3290 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) generateAnimateEverythingEvent()3291 private void generateAnimateEverythingEvent() { 3292 if (mEverythingNeedsAnimation) { 3293 mAnimationEvents.add( 3294 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING)); 3295 } 3296 mEverythingNeedsAnimation = false; 3297 } 3298 3299 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) generateDimmedEvent()3300 private void generateDimmedEvent() { 3301 if (mDimmedNeedsAnimation) { 3302 mAnimationEvents.add( 3303 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED)); 3304 } 3305 mDimmedNeedsAnimation = false; 3306 } 3307 3308 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) generateHideSensitiveEvent()3309 private void generateHideSensitiveEvent() { 3310 if (mHideSensitiveNeedsAnimation) { 3311 mAnimationEvents.add( 3312 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE)); 3313 } 3314 mHideSensitiveNeedsAnimation = false; 3315 } 3316 3317 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) generateGoToFullShadeEvent()3318 private void generateGoToFullShadeEvent() { 3319 if (mGoToFullShadeNeedsAnimation) { 3320 mAnimationEvents.add( 3321 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE)); 3322 } 3323 mGoToFullShadeNeedsAnimation = false; 3324 } 3325 3326 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM) createStackScrollAlgorithm(Context context)3327 protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) { 3328 return new StackScrollAlgorithm(context, this); 3329 } 3330 3331 /** 3332 * @return Whether a y coordinate is inside the content. 3333 */ 3334 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) isInContentBounds(float y)3335 public boolean isInContentBounds(float y) { 3336 return y < getHeight() - getEmptyBottomMargin(); 3337 } 3338 getTouchSlop(MotionEvent event)3339 private float getTouchSlop(MotionEvent event) { 3340 // Adjust the touch slop if another gesture may be being performed. 3341 return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE 3342 ? mTouchSlop * mSlopMultiplier 3343 : mTouchSlop; 3344 } 3345 3346 @Override onTouchEvent(MotionEvent ev)3347 public boolean onTouchEvent(MotionEvent ev) { 3348 if (mTouchHandler != null && mTouchHandler.onTouchEvent(ev)) { 3349 return true; 3350 } 3351 3352 return super.onTouchEvent(ev); 3353 } 3354 3355 @ShadeViewRefactor(RefactorComponent.INPUT) dispatchDownEventToScroller(MotionEvent ev)3356 void dispatchDownEventToScroller(MotionEvent ev) { 3357 MotionEvent downEvent = MotionEvent.obtain(ev); 3358 downEvent.setAction(MotionEvent.ACTION_DOWN); 3359 onScrollTouch(downEvent); 3360 downEvent.recycle(); 3361 } 3362 3363 @Override 3364 @ShadeViewRefactor(RefactorComponent.INPUT) onGenericMotionEvent(MotionEvent event)3365 public boolean onGenericMotionEvent(MotionEvent event) { 3366 if (!isScrollingEnabled() 3367 || !mIsExpanded 3368 || mSwipeHelper.isSwiping() 3369 || mExpandingNotification 3370 || mDisallowScrollingInThisMotion) { 3371 return false; 3372 } 3373 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { 3374 switch (event.getAction()) { 3375 case MotionEvent.ACTION_SCROLL: { 3376 if (!mIsBeingDragged) { 3377 final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); 3378 if (vscroll != 0) { 3379 final int delta = (int) (vscroll * getVerticalScrollFactor()); 3380 final int range = getScrollRange(); 3381 int oldScrollY = mOwnScrollY; 3382 int newScrollY = oldScrollY - delta; 3383 if (newScrollY < 0) { 3384 newScrollY = 0; 3385 } else if (newScrollY > range) { 3386 newScrollY = range; 3387 } 3388 if (newScrollY != oldScrollY) { 3389 setOwnScrollY(newScrollY); 3390 return true; 3391 } 3392 } 3393 } 3394 } 3395 } 3396 } 3397 return super.onGenericMotionEvent(event); 3398 } 3399 3400 @ShadeViewRefactor(RefactorComponent.INPUT) onScrollTouch(MotionEvent ev)3401 boolean onScrollTouch(MotionEvent ev) { 3402 if (!isScrollingEnabled()) { 3403 return false; 3404 } 3405 if (isInsideQsContainer(ev) && !mIsBeingDragged) { 3406 return false; 3407 } 3408 mForcedScroll = null; 3409 initVelocityTrackerIfNotExists(); 3410 mVelocityTracker.addMovement(ev); 3411 3412 final int action = ev.getActionMasked(); 3413 if (ev.findPointerIndex(mActivePointerId) == -1 && action != MotionEvent.ACTION_DOWN) { 3414 // Incomplete gesture, possibly due to window swap mid-gesture. Ignore until a new 3415 // one starts. 3416 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent " 3417 + MotionEvent.actionToString(ev.getActionMasked())); 3418 return true; 3419 } 3420 3421 switch (action) { 3422 case MotionEvent.ACTION_DOWN: { 3423 if (getChildCount() == 0 || !isInContentBounds(ev)) { 3424 return false; 3425 } 3426 boolean isBeingDragged = !mScroller.isFinished(); 3427 setIsBeingDragged(isBeingDragged); 3428 /* 3429 * If being flinged and user touches, stop the fling. isFinished 3430 * will be false if being flinged. 3431 */ 3432 if (!mScroller.isFinished()) { 3433 mScroller.forceFinished(true); 3434 } 3435 3436 // Remember where the motion event started 3437 mLastMotionY = (int) ev.getY(); 3438 mDownX = (int) ev.getX(); 3439 mActivePointerId = ev.getPointerId(0); 3440 break; 3441 } 3442 case MotionEvent.ACTION_MOVE: 3443 final int activePointerIndex = ev.findPointerIndex(mActivePointerId); 3444 if (activePointerIndex == -1) { 3445 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent"); 3446 break; 3447 } 3448 3449 final int y = (int) ev.getY(activePointerIndex); 3450 final int x = (int) ev.getX(activePointerIndex); 3451 int deltaY = mLastMotionY - y; 3452 final int xDiff = Math.abs(x - mDownX); 3453 final int yDiff = Math.abs(deltaY); 3454 final float touchSlop = getTouchSlop(ev); 3455 if (!mIsBeingDragged && yDiff > touchSlop && yDiff > xDiff) { 3456 setIsBeingDragged(true); 3457 if (deltaY > 0) { 3458 deltaY -= touchSlop; 3459 } else { 3460 deltaY += touchSlop; 3461 } 3462 } 3463 if (mIsBeingDragged) { 3464 // Scroll to follow the motion event 3465 mLastMotionY = y; 3466 float scrollAmount; 3467 int range; 3468 range = getScrollRange(); 3469 if (mExpandedInThisMotion) { 3470 range = Math.min(range, mMaxScrollAfterExpand); 3471 } 3472 if (deltaY < 0) { 3473 scrollAmount = overScrollDown(deltaY); 3474 } else { 3475 scrollAmount = overScrollUp(deltaY, range); 3476 } 3477 3478 // Calling customOverScrollBy will call onCustomOverScrolled, which 3479 // sets the scrolling if applicable. 3480 if (scrollAmount != 0.0f) { 3481 // The scrolling motion could not be compensated with the 3482 // existing overScroll, we have to scroll the view 3483 customOverScrollBy((int) scrollAmount, mOwnScrollY, 3484 range, getHeight() / 2); 3485 // If we're scrolling, leavebehinds should be dismissed 3486 mController.checkSnoozeLeavebehind(); 3487 } 3488 } 3489 break; 3490 case MotionEvent.ACTION_UP: 3491 if (mIsBeingDragged) { 3492 final VelocityTracker velocityTracker = mVelocityTracker; 3493 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 3494 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); 3495 3496 if (shouldOverScrollFling(initialVelocity)) { 3497 onOverScrollFling(true, initialVelocity); 3498 } else { 3499 if (getChildCount() > 0) { 3500 if ((Math.abs(initialVelocity) > mMinimumVelocity)) { 3501 float currentOverScrollTop = getCurrentOverScrollAmount(true); 3502 if (currentOverScrollTop == 0.0f || initialVelocity > 0) { 3503 mFlingAfterUpEvent = true; 3504 setFinishScrollingCallback(() -> { 3505 mFlingAfterUpEvent = false; 3506 InteractionJankMonitor.getInstance() 3507 .end(CUJ_NOTIFICATION_SHADE_SCROLL_FLING); 3508 setFinishScrollingCallback(null); 3509 }); 3510 fling(-initialVelocity); 3511 } else { 3512 onOverScrollFling(false, initialVelocity); 3513 } 3514 } else { 3515 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, 3516 getScrollRange())) { 3517 animateScroll(); 3518 } 3519 } 3520 } 3521 } 3522 mActivePointerId = INVALID_POINTER; 3523 endDrag(); 3524 } 3525 3526 break; 3527 case MotionEvent.ACTION_CANCEL: 3528 if (mIsBeingDragged && getChildCount() > 0) { 3529 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, 3530 getScrollRange())) { 3531 animateScroll(); 3532 } 3533 mActivePointerId = INVALID_POINTER; 3534 endDrag(); 3535 } 3536 break; 3537 case MotionEvent.ACTION_POINTER_DOWN: { 3538 final int index = ev.getActionIndex(); 3539 mLastMotionY = (int) ev.getY(index); 3540 mDownX = (int) ev.getX(index); 3541 mActivePointerId = ev.getPointerId(index); 3542 break; 3543 } 3544 case MotionEvent.ACTION_POINTER_UP: 3545 onSecondaryPointerUp(ev); 3546 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId)); 3547 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId)); 3548 break; 3549 } 3550 return true; 3551 } 3552 isFlingAfterUpEvent()3553 boolean isFlingAfterUpEvent() { 3554 return mFlingAfterUpEvent; 3555 } 3556 3557 @ShadeViewRefactor(RefactorComponent.INPUT) isInsideQsContainer(MotionEvent ev)3558 protected boolean isInsideQsContainer(MotionEvent ev) { 3559 return ev.getY() < mQsContainer.getBottom(); 3560 } 3561 3562 @ShadeViewRefactor(RefactorComponent.INPUT) onOverScrollFling(boolean open, int initialVelocity)3563 private void onOverScrollFling(boolean open, int initialVelocity) { 3564 if (mOverscrollTopChangedListener != null) { 3565 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open); 3566 } 3567 mDontReportNextOverScroll = true; 3568 setOverScrollAmount(0.0f, true, false); 3569 } 3570 3571 3572 @ShadeViewRefactor(RefactorComponent.INPUT) onSecondaryPointerUp(MotionEvent ev)3573 private void onSecondaryPointerUp(MotionEvent ev) { 3574 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> 3575 MotionEvent.ACTION_POINTER_INDEX_SHIFT; 3576 final int pointerId = ev.getPointerId(pointerIndex); 3577 if (pointerId == mActivePointerId) { 3578 // This was our active pointer going up. Choose a new 3579 // active pointer and adjust accordingly. 3580 // TODO: Make this decision more intelligent. 3581 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 3582 mLastMotionY = (int) ev.getY(newPointerIndex); 3583 mActivePointerId = ev.getPointerId(newPointerIndex); 3584 if (mVelocityTracker != null) { 3585 mVelocityTracker.clear(); 3586 } 3587 } 3588 } 3589 3590 @ShadeViewRefactor(RefactorComponent.INPUT) endDrag()3591 private void endDrag() { 3592 setIsBeingDragged(false); 3593 3594 recycleVelocityTracker(); 3595 3596 if (getCurrentOverScrollAmount(true /* onTop */) > 0) { 3597 setOverScrollAmount(0, true /* onTop */, true /* animate */); 3598 } 3599 if (getCurrentOverScrollAmount(false /* onTop */) > 0) { 3600 setOverScrollAmount(0, false /* onTop */, true /* animate */); 3601 } 3602 } 3603 3604 @Override 3605 @ShadeViewRefactor(RefactorComponent.INPUT) onInterceptTouchEvent(MotionEvent ev)3606 public boolean onInterceptTouchEvent(MotionEvent ev) { 3607 if (mTouchHandler != null && mTouchHandler.onInterceptTouchEvent(ev)) { 3608 return true; 3609 } 3610 return super.onInterceptTouchEvent(ev); 3611 } 3612 3613 @ShadeViewRefactor(RefactorComponent.INPUT) handleEmptySpaceClick(MotionEvent ev)3614 void handleEmptySpaceClick(MotionEvent ev) { 3615 switch (ev.getActionMasked()) { 3616 case MotionEvent.ACTION_MOVE: 3617 final float touchSlop = getTouchSlop(ev); 3618 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > touchSlop 3619 || Math.abs(ev.getX() - mInitialTouchX) > touchSlop)) { 3620 mTouchIsClick = false; 3621 } 3622 break; 3623 case MotionEvent.ACTION_UP: 3624 if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick && 3625 isBelowLastNotification(mInitialTouchX, mInitialTouchY)) { 3626 mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY); 3627 } 3628 break; 3629 } 3630 } 3631 3632 @ShadeViewRefactor(RefactorComponent.INPUT) initDownStates(MotionEvent ev)3633 void initDownStates(MotionEvent ev) { 3634 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 3635 mExpandedInThisMotion = false; 3636 mOnlyScrollingInThisMotion = !mScroller.isFinished(); 3637 mDisallowScrollingInThisMotion = false; 3638 mDisallowDismissInThisMotion = false; 3639 mTouchIsClick = true; 3640 mInitialTouchX = ev.getX(); 3641 mInitialTouchY = ev.getY(); 3642 } 3643 } 3644 3645 @Override 3646 @ShadeViewRefactor(RefactorComponent.INPUT) requestDisallowInterceptTouchEvent(boolean disallowIntercept)3647 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 3648 super.requestDisallowInterceptTouchEvent(disallowIntercept); 3649 if (disallowIntercept) { 3650 cancelLongPress(); 3651 } 3652 } 3653 3654 @ShadeViewRefactor(RefactorComponent.INPUT) onInterceptTouchEventScroll(MotionEvent ev)3655 boolean onInterceptTouchEventScroll(MotionEvent ev) { 3656 if (!isScrollingEnabled()) { 3657 return false; 3658 } 3659 /* 3660 * This method JUST determines whether we want to intercept the motion. 3661 * If we return true, onMotionEvent will be called and we do the actual 3662 * scrolling there. 3663 */ 3664 3665 /* 3666 * Shortcut the most recurring case: the user is in the dragging 3667 * state and is moving their finger. We want to intercept this 3668 * motion. 3669 */ 3670 final int action = ev.getAction(); 3671 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { 3672 return true; 3673 } 3674 3675 switch (action & MotionEvent.ACTION_MASK) { 3676 case MotionEvent.ACTION_MOVE: { 3677 /* 3678 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 3679 * whether the user has moved far enough from the original down touch. 3680 */ 3681 3682 /* 3683 * Locally do absolute value. mLastMotionY is set to the y value 3684 * of the down event. 3685 */ 3686 final int activePointerId = mActivePointerId; 3687 if (activePointerId == INVALID_POINTER) { 3688 // If we don't have a valid id, the touch down wasn't on content. 3689 break; 3690 } 3691 3692 final int pointerIndex = ev.findPointerIndex(activePointerId); 3693 if (pointerIndex == -1) { 3694 Log.e(TAG, "Invalid pointerId=" + activePointerId 3695 + " in onInterceptTouchEvent"); 3696 break; 3697 } 3698 3699 final int y = (int) ev.getY(pointerIndex); 3700 final int x = (int) ev.getX(pointerIndex); 3701 final int yDiff = Math.abs(y - mLastMotionY); 3702 final int xDiff = Math.abs(x - mDownX); 3703 if (yDiff > getTouchSlop(ev) && yDiff > xDiff) { 3704 setIsBeingDragged(true); 3705 mLastMotionY = y; 3706 mDownX = x; 3707 initVelocityTrackerIfNotExists(); 3708 mVelocityTracker.addMovement(ev); 3709 } 3710 break; 3711 } 3712 3713 case MotionEvent.ACTION_DOWN: { 3714 final int y = (int) ev.getY(); 3715 mScrolledToTopOnFirstDown = mScrollAdapter.isScrolledToTop(); 3716 final ExpandableView childAtTouchPos = getChildAtPosition( 3717 ev.getX(), y, false /* requireMinHeight */, false /* ignoreDecors */); 3718 if (childAtTouchPos == null) { 3719 setIsBeingDragged(false); 3720 recycleVelocityTracker(); 3721 break; 3722 } 3723 3724 /* 3725 * Remember location of down touch. 3726 * ACTION_DOWN always refers to pointer index 0. 3727 */ 3728 mLastMotionY = y; 3729 mDownX = (int) ev.getX(); 3730 mActivePointerId = ev.getPointerId(0); 3731 3732 initOrResetVelocityTracker(); 3733 mVelocityTracker.addMovement(ev); 3734 /* 3735 * If being flinged and user touches the screen, initiate drag; 3736 * otherwise don't. mScroller.isFinished should be false when 3737 * being flinged. 3738 */ 3739 boolean isBeingDragged = !mScroller.isFinished(); 3740 setIsBeingDragged(isBeingDragged); 3741 break; 3742 } 3743 3744 case MotionEvent.ACTION_CANCEL: 3745 case MotionEvent.ACTION_UP: 3746 /* Release the drag */ 3747 setIsBeingDragged(false); 3748 mActivePointerId = INVALID_POINTER; 3749 recycleVelocityTracker(); 3750 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { 3751 animateScroll(); 3752 } 3753 break; 3754 case MotionEvent.ACTION_POINTER_UP: 3755 onSecondaryPointerUp(ev); 3756 break; 3757 } 3758 3759 /* 3760 * The only time we want to intercept motion events is if we are in the 3761 * drag mode. 3762 */ 3763 return mIsBeingDragged; 3764 } 3765 3766 /** 3767 * @return Whether the specified motion event is actually happening over the content. 3768 */ 3769 @ShadeViewRefactor(RefactorComponent.INPUT) isInContentBounds(MotionEvent event)3770 private boolean isInContentBounds(MotionEvent event) { 3771 return isInContentBounds(event.getY()); 3772 } 3773 3774 3775 @VisibleForTesting 3776 @ShadeViewRefactor(RefactorComponent.INPUT) setIsBeingDragged(boolean isDragged)3777 void setIsBeingDragged(boolean isDragged) { 3778 mIsBeingDragged = isDragged; 3779 if (isDragged) { 3780 requestDisallowInterceptTouchEvent(true); 3781 cancelLongPress(); 3782 resetExposedMenuView(true /* animate */, true /* force */); 3783 } 3784 } 3785 3786 @ShadeViewRefactor(RefactorComponent.INPUT) requestDisallowLongPress()3787 public void requestDisallowLongPress() { 3788 cancelLongPress(); 3789 } 3790 3791 @ShadeViewRefactor(RefactorComponent.INPUT) requestDisallowDismiss()3792 public void requestDisallowDismiss() { 3793 mDisallowDismissInThisMotion = true; 3794 } 3795 3796 @ShadeViewRefactor(RefactorComponent.INPUT) cancelLongPress()3797 public void cancelLongPress() { 3798 mSwipeHelper.cancelLongPress(); 3799 } 3800 3801 @ShadeViewRefactor(RefactorComponent.INPUT) setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener)3802 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) { 3803 mOnEmptySpaceClickListener = listener; 3804 } 3805 3806 /** @hide */ 3807 @Override 3808 @ShadeViewRefactor(RefactorComponent.INPUT) performAccessibilityActionInternal(int action, Bundle arguments)3809 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 3810 if (super.performAccessibilityActionInternal(action, arguments)) { 3811 return true; 3812 } 3813 if (!isEnabled()) { 3814 return false; 3815 } 3816 int direction = -1; 3817 switch (action) { 3818 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: 3819 // fall through 3820 case android.R.id.accessibilityActionScrollDown: 3821 direction = 1; 3822 // fall through 3823 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: 3824 // fall through 3825 case android.R.id.accessibilityActionScrollUp: 3826 final int viewportHeight = 3827 getHeight() - mPaddingBottom - mTopPadding - mPaddingTop 3828 - mShelf.getIntrinsicHeight(); 3829 final int targetScrollY = Math.max(0, 3830 Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange())); 3831 if (targetScrollY != mOwnScrollY) { 3832 mScroller.startScroll(mScrollX, mOwnScrollY, 0, 3833 targetScrollY - mOwnScrollY); 3834 animateScroll(); 3835 return true; 3836 } 3837 break; 3838 } 3839 return false; 3840 } 3841 3842 @Override 3843 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) onWindowFocusChanged(boolean hasWindowFocus)3844 public void onWindowFocusChanged(boolean hasWindowFocus) { 3845 super.onWindowFocusChanged(hasWindowFocus); 3846 if (!hasWindowFocus) { 3847 cancelLongPress(); 3848 } 3849 } 3850 3851 @Override 3852 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) clearChildFocus(View child)3853 public void clearChildFocus(View child) { 3854 super.clearChildFocus(child); 3855 if (mForcedScroll == child) { 3856 mForcedScroll = null; 3857 } 3858 } 3859 isScrolledToBottom()3860 boolean isScrolledToBottom() { 3861 return mScrollAdapter.isScrolledToBottom(); 3862 } 3863 3864 @ShadeViewRefactor(RefactorComponent.COORDINATOR) getEmptyBottomMargin()3865 int getEmptyBottomMargin() { 3866 return Math.max(mMaxLayoutHeight - mContentHeight, 0); 3867 } 3868 3869 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) onExpansionStarted()3870 void onExpansionStarted() { 3871 mIsExpansionChanging = true; 3872 mAmbientState.setExpansionChanging(true); 3873 } 3874 3875 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) onExpansionStopped()3876 void onExpansionStopped() { 3877 mIsExpansionChanging = false; 3878 mAmbientState.setExpansionChanging(false); 3879 if (!mIsExpanded) { 3880 resetScrollPosition(); 3881 mStatusBar.resetUserExpandedStates(); 3882 clearTemporaryViews(); 3883 clearUserLockedViews(); 3884 if (mSwipeHelper.isSwiping()) { 3885 mSwipeHelper.resetSwipeState(); 3886 updateContinuousShadowDrawing(); 3887 } 3888 } 3889 } 3890 3891 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) clearUserLockedViews()3892 private void clearUserLockedViews() { 3893 for (int i = 0; i < getChildCount(); i++) { 3894 ExpandableView child = (ExpandableView) getChildAt(i); 3895 if (child instanceof ExpandableNotificationRow) { 3896 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 3897 row.setUserLocked(false); 3898 } 3899 } 3900 } 3901 3902 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) clearTemporaryViews()3903 private void clearTemporaryViews() { 3904 // lets make sure nothing is transient anymore 3905 clearTemporaryViewsInGroup(this); 3906 for (int i = 0; i < getChildCount(); i++) { 3907 ExpandableView child = (ExpandableView) getChildAt(i); 3908 if (child instanceof ExpandableNotificationRow) { 3909 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 3910 clearTemporaryViewsInGroup(row.getChildrenContainer()); 3911 } 3912 } 3913 } 3914 3915 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) clearTemporaryViewsInGroup(ViewGroup viewGroup)3916 private void clearTemporaryViewsInGroup(ViewGroup viewGroup) { 3917 while (viewGroup != null && viewGroup.getTransientViewCount() != 0) { 3918 viewGroup.removeTransientView(viewGroup.getTransientView(0)); 3919 } 3920 } 3921 3922 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) onPanelTrackingStarted()3923 void onPanelTrackingStarted() { 3924 mPanelTracking = true; 3925 mAmbientState.setPanelTracking(true); 3926 resetExposedMenuView(true /* animate */, true /* force */); 3927 } 3928 3929 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) onPanelTrackingStopped()3930 void onPanelTrackingStopped() { 3931 mPanelTracking = false; 3932 mAmbientState.setPanelTracking(false); 3933 } 3934 3935 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) resetScrollPosition()3936 void resetScrollPosition() { 3937 mScroller.abortAnimation(); 3938 setOwnScrollY(0); 3939 } 3940 3941 @ShadeViewRefactor(RefactorComponent.COORDINATOR) setIsExpanded(boolean isExpanded)3942 private void setIsExpanded(boolean isExpanded) { 3943 boolean changed = isExpanded != mIsExpanded; 3944 mIsExpanded = isExpanded; 3945 mStackScrollAlgorithm.setIsExpanded(isExpanded); 3946 mAmbientState.setShadeExpanded(isExpanded); 3947 mStateAnimator.setShadeExpanded(isExpanded); 3948 mSwipeHelper.setIsExpanded(isExpanded); 3949 if (changed) { 3950 mWillExpand = false; 3951 if (!mIsExpanded) { 3952 mGroupExpansionManager.collapseGroups(); 3953 mExpandHelper.cancelImmediately(); 3954 } 3955 updateNotificationAnimationStates(); 3956 updateChronometers(); 3957 requestChildrenUpdate(); 3958 updateUseRoundedRectClipping(); 3959 } 3960 } 3961 3962 @ShadeViewRefactor(RefactorComponent.COORDINATOR) updateChronometers()3963 private void updateChronometers() { 3964 int childCount = getChildCount(); 3965 for (int i = 0; i < childCount; i++) { 3966 updateChronometerForChild(getChildAt(i)); 3967 } 3968 } 3969 3970 @ShadeViewRefactor(RefactorComponent.COORDINATOR) updateChronometerForChild(View child)3971 void updateChronometerForChild(View child) { 3972 if (child instanceof ExpandableNotificationRow) { 3973 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 3974 row.setChronometerRunning(mIsExpanded); 3975 } 3976 } 3977 onChildHeightChanged(ExpandableView view, boolean needsAnimation)3978 void onChildHeightChanged(ExpandableView view, boolean needsAnimation) { 3979 boolean previouslyNeededAnimation = mAnimateStackYForContentHeightChange; 3980 if (needsAnimation) { 3981 mAnimateStackYForContentHeightChange = true; 3982 } 3983 updateContentHeight(); 3984 updateScrollPositionOnExpandInBottom(view); 3985 clampScrollPosition(); 3986 notifyHeightChangeListener(view, needsAnimation); 3987 ExpandableNotificationRow row = view instanceof ExpandableNotificationRow 3988 ? (ExpandableNotificationRow) view 3989 : null; 3990 NotificationSection firstSection = getFirstVisibleSection(); 3991 ExpandableView firstVisibleChild = 3992 firstSection == null ? null : firstSection.getFirstVisibleChild(); 3993 if (row != null) { 3994 if (row == firstVisibleChild 3995 || row.getNotificationParent() == firstVisibleChild) { 3996 updateAlgorithmLayoutMinHeight(); 3997 } 3998 } 3999 if (needsAnimation) { 4000 requestAnimationOnViewResize(row); 4001 } 4002 requestChildrenUpdate(); 4003 mAnimateStackYForContentHeightChange = previouslyNeededAnimation; 4004 } 4005 onChildHeightReset(ExpandableView view)4006 void onChildHeightReset(ExpandableView view) { 4007 updateAnimationState(view); 4008 updateChronometerForChild(view); 4009 } 4010 4011 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) updateScrollPositionOnExpandInBottom(ExpandableView view)4012 private void updateScrollPositionOnExpandInBottom(ExpandableView view) { 4013 if (view instanceof ExpandableNotificationRow && !onKeyguard()) { 4014 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 4015 // TODO: once we're recycling this will need to check the adapter position of the child 4016 if (row.isUserLocked() && row != getFirstChildNotGone()) { 4017 if (row.isSummaryWithChildren()) { 4018 return; 4019 } 4020 // We are actually expanding this view 4021 float endPosition = row.getTranslationY() + row.getActualHeight(); 4022 if (row.isChildInGroup()) { 4023 endPosition += row.getNotificationParent().getTranslationY(); 4024 } 4025 int layoutEnd = mMaxLayoutHeight + (int) mStackTranslation; 4026 NotificationSection lastSection = getLastVisibleSection(); 4027 ExpandableView lastVisibleChild = 4028 lastSection == null ? null : lastSection.getLastVisibleChild(); 4029 if (row != lastVisibleChild && mShelf.getVisibility() != GONE) { 4030 layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements; 4031 } 4032 if (endPosition > layoutEnd) { 4033 setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd)); 4034 mDisallowScrollingInThisMotion = true; 4035 } 4036 } 4037 } 4038 } 4039 4040 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setOnHeightChangedListener( ExpandableView.OnHeightChangedListener onHeightChangedListener)4041 void setOnHeightChangedListener( 4042 ExpandableView.OnHeightChangedListener onHeightChangedListener) { 4043 this.mOnHeightChangedListener = onHeightChangedListener; 4044 } 4045 4046 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) onChildAnimationFinished()4047 void onChildAnimationFinished() { 4048 setAnimationRunning(false); 4049 requestChildrenUpdate(); 4050 runAnimationFinishedRunnables(); 4051 clearTransient(); 4052 clearHeadsUpDisappearRunning(); 4053 } 4054 4055 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) clearHeadsUpDisappearRunning()4056 private void clearHeadsUpDisappearRunning() { 4057 for (int i = 0; i < getChildCount(); i++) { 4058 View view = getChildAt(i); 4059 if (view instanceof ExpandableNotificationRow) { 4060 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 4061 row.setHeadsUpAnimatingAway(false); 4062 if (row.isSummaryWithChildren()) { 4063 for (ExpandableNotificationRow child : row.getAttachedChildren()) { 4064 child.setHeadsUpAnimatingAway(false); 4065 } 4066 } 4067 } 4068 } 4069 } 4070 4071 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) clearTransient()4072 private void clearTransient() { 4073 for (ExpandableView view : mClearTransientViewsWhenFinished) { 4074 StackStateAnimator.removeTransientView(view); 4075 } 4076 mClearTransientViewsWhenFinished.clear(); 4077 } 4078 4079 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) runAnimationFinishedRunnables()4080 private void runAnimationFinishedRunnables() { 4081 for (Runnable runnable : mAnimationFinishedRunnables) { 4082 runnable.run(); 4083 } 4084 mAnimationFinishedRunnables.clear(); 4085 } 4086 4087 /** 4088 * See {@link AmbientState#setDimmed}. 4089 */ 4090 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setDimmed(boolean dimmed, boolean animate)4091 void setDimmed(boolean dimmed, boolean animate) { 4092 dimmed &= onKeyguard(); 4093 mAmbientState.setDimmed(dimmed); 4094 if (animate && mAnimationsEnabled) { 4095 mDimmedNeedsAnimation = true; 4096 mNeedsAnimation = true; 4097 animateDimmed(dimmed); 4098 } else { 4099 setDimAmount(dimmed ? 1.0f : 0.0f); 4100 } 4101 requestChildrenUpdate(); 4102 } 4103 4104 @VisibleForTesting 4105 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) isDimmed()4106 boolean isDimmed() { 4107 return mAmbientState.isDimmed(); 4108 } 4109 4110 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setDimAmount(float dimAmount)4111 private void setDimAmount(float dimAmount) { 4112 mDimAmount = dimAmount; 4113 updateBackgroundDimming(); 4114 } 4115 4116 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) animateDimmed(boolean dimmed)4117 private void animateDimmed(boolean dimmed) { 4118 if (mDimAnimator != null) { 4119 mDimAnimator.cancel(); 4120 } 4121 float target = dimmed ? 1.0f : 0.0f; 4122 if (target == mDimAmount) { 4123 return; 4124 } 4125 mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target); 4126 mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED); 4127 mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 4128 mDimAnimator.addListener(mDimEndListener); 4129 mDimAnimator.addUpdateListener(mDimUpdateListener); 4130 mDimAnimator.start(); 4131 } 4132 4133 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) updateSensitiveness(boolean animate, boolean hideSensitive)4134 void updateSensitiveness(boolean animate, boolean hideSensitive) { 4135 if (hideSensitive != mAmbientState.isHideSensitive()) { 4136 int childCount = getChildCount(); 4137 for (int i = 0; i < childCount; i++) { 4138 ExpandableView v = (ExpandableView) getChildAt(i); 4139 v.setHideSensitiveForIntrinsicHeight(hideSensitive); 4140 } 4141 mAmbientState.setHideSensitive(hideSensitive); 4142 if (animate && mAnimationsEnabled) { 4143 mHideSensitiveNeedsAnimation = true; 4144 mNeedsAnimation = true; 4145 } 4146 updateContentHeight(); 4147 requestChildrenUpdate(); 4148 } 4149 } 4150 4151 /** 4152 * See {@link AmbientState#setActivatedChild}. 4153 */ 4154 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setActivatedChild(ActivatableNotificationView activatedChild)4155 void setActivatedChild(ActivatableNotificationView activatedChild) { 4156 mAmbientState.setActivatedChild(activatedChild); 4157 if (mAnimationsEnabled) { 4158 mActivateNeedsAnimation = true; 4159 mNeedsAnimation = true; 4160 } 4161 requestChildrenUpdate(); 4162 } 4163 4164 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) getActivatedChild()4165 public ActivatableNotificationView getActivatedChild() { 4166 return mAmbientState.getActivatedChild(); 4167 } 4168 4169 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) applyCurrentState()4170 private void applyCurrentState() { 4171 int numChildren = getChildCount(); 4172 for (int i = 0; i < numChildren; i++) { 4173 ExpandableView child = (ExpandableView) getChildAt(i); 4174 child.applyViewState(); 4175 } 4176 4177 if (mListener != null) { 4178 mListener.onChildLocationsChanged(); 4179 } 4180 runAnimationFinishedRunnables(); 4181 setAnimationRunning(false); 4182 updateBackground(); 4183 updateViewShadows(); 4184 } 4185 4186 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) updateViewShadows()4187 private void updateViewShadows() { 4188 // we need to work around an issue where the shadow would not cast between siblings when 4189 // their z difference is between 0 and 0.1 4190 4191 // Lefts first sort by Z difference 4192 for (int i = 0; i < getChildCount(); i++) { 4193 ExpandableView child = (ExpandableView) getChildAt(i); 4194 if (child.getVisibility() != GONE) { 4195 mTmpSortedChildren.add(child); 4196 } 4197 } 4198 Collections.sort(mTmpSortedChildren, mViewPositionComparator); 4199 4200 // Now lets update the shadow for the views 4201 ExpandableView previous = null; 4202 for (int i = 0; i < mTmpSortedChildren.size(); i++) { 4203 ExpandableView expandableView = mTmpSortedChildren.get(i); 4204 float translationZ = expandableView.getTranslationZ(); 4205 float otherZ = previous == null ? translationZ : previous.getTranslationZ(); 4206 float diff = otherZ - translationZ; 4207 if (diff <= 0.0f || diff >= FakeShadowView.SHADOW_SIBLING_TRESHOLD) { 4208 // There is no fake shadow to be drawn 4209 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0); 4210 } else { 4211 float yLocation = previous.getTranslationY() + previous.getActualHeight() - 4212 expandableView.getTranslationY() - previous.getExtraBottomPadding(); 4213 expandableView.setFakeShadowIntensity( 4214 diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD, 4215 previous.getOutlineAlpha(), (int) yLocation, 4216 (int) (previous.getOutlineTranslation() + previous.getTranslation())); 4217 } 4218 previous = expandableView; 4219 } 4220 4221 mTmpSortedChildren.clear(); 4222 } 4223 4224 /** 4225 * Update colors of "dismiss" and "empty shade" views. 4226 * 4227 * @param lightTheme True if light theme should be used. 4228 */ 4229 @ShadeViewRefactor(RefactorComponent.DECORATOR) updateDecorViews()4230 void updateDecorViews() { 4231 final @ColorInt int textColor = 4232 Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary); 4233 mSectionsManager.setHeaderForegroundColor(textColor); 4234 mFooterView.setTextColor(textColor); 4235 mEmptyShadeView.setTextColor(textColor); 4236 } 4237 4238 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) goToFullShade(long delay)4239 void goToFullShade(long delay) { 4240 mGoToFullShadeNeedsAnimation = true; 4241 mGoToFullShadeDelay = delay; 4242 mNeedsAnimation = true; 4243 requestChildrenUpdate(); 4244 } 4245 4246 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) cancelExpandHelper()4247 public void cancelExpandHelper() { 4248 mExpandHelper.cancel(); 4249 } 4250 4251 @ShadeViewRefactor(RefactorComponent.COORDINATOR) setIntrinsicPadding(int intrinsicPadding)4252 void setIntrinsicPadding(int intrinsicPadding) { 4253 mIntrinsicPadding = intrinsicPadding; 4254 mAmbientState.setIntrinsicPadding(intrinsicPadding); 4255 } 4256 4257 @ShadeViewRefactor(RefactorComponent.COORDINATOR) getIntrinsicPadding()4258 int getIntrinsicPadding() { 4259 return mIntrinsicPadding; 4260 } 4261 4262 @Override 4263 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) shouldDelayChildPressedState()4264 public boolean shouldDelayChildPressedState() { 4265 return true; 4266 } 4267 4268 /** 4269 * See {@link AmbientState#setDozing}. 4270 */ 4271 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setDozing(boolean dozing, boolean animate, @Nullable PointF touchWakeUpScreenLocation)4272 public void setDozing(boolean dozing, boolean animate, 4273 @Nullable PointF touchWakeUpScreenLocation) { 4274 if (mAmbientState.isDozing() == dozing) { 4275 return; 4276 } 4277 mAmbientState.setDozing(dozing); 4278 requestChildrenUpdate(); 4279 notifyHeightChangeListener(mShelf); 4280 } 4281 4282 /** 4283 * Sets the current hide amount. 4284 * 4285 * @param linearHideAmount The hide amount that follows linear interpoloation in the 4286 * animation, 4287 * i.e. animates from 0 to 1 or vice-versa in a linear manner. 4288 * @param interpolatedHideAmount The hide amount that follows the actual interpolation of the 4289 * animation curve. 4290 */ 4291 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setHideAmount(float linearHideAmount, float interpolatedHideAmount)4292 void setHideAmount(float linearHideAmount, float interpolatedHideAmount) { 4293 mLinearHideAmount = linearHideAmount; 4294 mInterpolatedHideAmount = interpolatedHideAmount; 4295 boolean wasFullyHidden = mAmbientState.isFullyHidden(); 4296 boolean wasHiddenAtAll = mAmbientState.isHiddenAtAll(); 4297 mAmbientState.setHideAmount(interpolatedHideAmount); 4298 boolean nowFullyHidden = mAmbientState.isFullyHidden(); 4299 boolean nowHiddenAtAll = mAmbientState.isHiddenAtAll(); 4300 if (nowFullyHidden != wasFullyHidden) { 4301 updateVisibility(); 4302 } 4303 if (!wasHiddenAtAll && nowHiddenAtAll) { 4304 resetExposedMenuView(true /* animate */, true /* animate */); 4305 } 4306 if (nowFullyHidden != wasFullyHidden || wasHiddenAtAll != nowHiddenAtAll) { 4307 invalidateOutline(); 4308 } 4309 updateAlgorithmHeightAndPadding(); 4310 updateBackgroundDimming(); 4311 requestChildrenUpdate(); 4312 updateOwnTranslationZ(); 4313 } 4314 updateOwnTranslationZ()4315 private void updateOwnTranslationZ() { 4316 // Since we are clipping to the outline we need to make sure that the shadows aren't 4317 // clipped when pulsing 4318 float ownTranslationZ = 0; 4319 if (mKeyguardBypassEnabledProvider.getBypassEnabled() && mAmbientState.isHiddenAtAll()) { 4320 ExpandableView firstChildNotGone = getFirstChildNotGone(); 4321 if (firstChildNotGone != null && firstChildNotGone.showingPulsing()) { 4322 ownTranslationZ = firstChildNotGone.getTranslationZ(); 4323 } 4324 } 4325 setTranslationZ(ownTranslationZ); 4326 } 4327 updateVisibility()4328 private void updateVisibility() { 4329 boolean shouldShow = !mAmbientState.isFullyHidden() || !onKeyguard(); 4330 setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE); 4331 } 4332 4333 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) notifyHideAnimationStart(boolean hide)4334 void notifyHideAnimationStart(boolean hide) { 4335 // We only swap the scaling factor if we're fully hidden or fully awake to avoid 4336 // interpolation issues when playing with the power button. 4337 if (mInterpolatedHideAmount == 0 || mInterpolatedHideAmount == 1) { 4338 mBackgroundXFactor = hide ? 1.8f : 1.5f; 4339 mHideXInterpolator = hide 4340 ? Interpolators.FAST_OUT_SLOW_IN_REVERSE 4341 : Interpolators.FAST_OUT_SLOW_IN; 4342 } 4343 } 4344 4345 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) getNotGoneIndex(View child)4346 private int getNotGoneIndex(View child) { 4347 int count = getChildCount(); 4348 int notGoneIndex = 0; 4349 for (int i = 0; i < count; i++) { 4350 View v = getChildAt(i); 4351 if (child == v) { 4352 return notGoneIndex; 4353 } 4354 if (v.getVisibility() != View.GONE) { 4355 notGoneIndex++; 4356 } 4357 } 4358 return -1; 4359 } 4360 4361 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setFooterView(@onNull FooterView footerView)4362 void setFooterView(@NonNull FooterView footerView) { 4363 int index = -1; 4364 if (mFooterView != null) { 4365 index = indexOfChild(mFooterView); 4366 removeView(mFooterView); 4367 } 4368 mFooterView = footerView; 4369 addView(mFooterView, index); 4370 } 4371 4372 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setEmptyShadeView(EmptyShadeView emptyShadeView)4373 void setEmptyShadeView(EmptyShadeView emptyShadeView) { 4374 int index = -1; 4375 if (mEmptyShadeView != null) { 4376 index = indexOfChild(mEmptyShadeView); 4377 removeView(mEmptyShadeView); 4378 } 4379 mEmptyShadeView = emptyShadeView; 4380 addView(mEmptyShadeView, index); 4381 } 4382 4383 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) updateEmptyShadeView(boolean visible, boolean notifVisibleInShade)4384 void updateEmptyShadeView(boolean visible, boolean notifVisibleInShade) { 4385 mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled); 4386 4387 int oldTextRes = mEmptyShadeView.getTextResource(); 4388 int newTextRes = notifVisibleInShade 4389 ? R.string.dnd_suppressing_shade_text : R.string.empty_shade_text; 4390 if (oldTextRes != newTextRes) { 4391 mEmptyShadeView.setText(newTextRes); 4392 } 4393 } 4394 4395 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) updateFooterView(boolean visible, boolean showDismissView, boolean showHistory)4396 public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) { 4397 if (mFooterView == null) { 4398 return; 4399 } 4400 boolean animate = mIsExpanded && mAnimationsEnabled; 4401 mFooterView.setVisible(visible, animate); 4402 mFooterView.setSecondaryVisible(showDismissView, animate); 4403 mFooterView.showHistory(showHistory); 4404 } 4405 4406 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setDismissAllInProgress(boolean dismissAllInProgress)4407 public void setDismissAllInProgress(boolean dismissAllInProgress) { 4408 mDismissAllInProgress = dismissAllInProgress; 4409 mAmbientState.setDismissAllInProgress(dismissAllInProgress); 4410 handleDismissAllClipping(); 4411 } 4412 getDismissAllInProgress()4413 boolean getDismissAllInProgress() { 4414 return mDismissAllInProgress; 4415 } 4416 4417 @ShadeViewRefactor(RefactorComponent.ADAPTER) handleDismissAllClipping()4418 private void handleDismissAllClipping() { 4419 final int count = getChildCount(); 4420 boolean previousChildWillBeDismissed = false; 4421 for (int i = 0; i < count; i++) { 4422 ExpandableView child = (ExpandableView) getChildAt(i); 4423 if (child.getVisibility() == GONE) { 4424 continue; 4425 } 4426 if (mDismissAllInProgress && previousChildWillBeDismissed) { 4427 child.setMinClipTopAmount(child.getClipTopAmount()); 4428 } else { 4429 child.setMinClipTopAmount(0); 4430 } 4431 previousChildWillBeDismissed = canChildBeDismissed(child); 4432 } 4433 } 4434 4435 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) isFooterViewNotGone()4436 public boolean isFooterViewNotGone() { 4437 return mFooterView != null 4438 && mFooterView.getVisibility() != View.GONE 4439 && !mFooterView.willBeGone(); 4440 } 4441 4442 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) isFooterViewContentVisible()4443 public boolean isFooterViewContentVisible() { 4444 return mFooterView != null && mFooterView.isContentVisible(); 4445 } 4446 4447 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) getFooterViewHeightWithPadding()4448 public int getFooterViewHeightWithPadding() { 4449 return mFooterView == null ? 0 : mFooterView.getHeight() 4450 + mPaddingBetweenElements 4451 + mGapHeight; 4452 } 4453 4454 /** 4455 * @return the padding after the media header on the lockscreen 4456 */ getPaddingAfterMedia()4457 public int getPaddingAfterMedia() { 4458 return mGapHeight + mPaddingBetweenElements; 4459 } 4460 4461 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) getEmptyShadeViewHeight()4462 public int getEmptyShadeViewHeight() { 4463 return mEmptyShadeView.getHeight(); 4464 } 4465 4466 @ShadeViewRefactor(RefactorComponent.COORDINATOR) getBottomMostNotificationBottom()4467 public float getBottomMostNotificationBottom() { 4468 final int count = getChildCount(); 4469 float max = 0; 4470 for (int childIdx = 0; childIdx < count; childIdx++) { 4471 ExpandableView child = (ExpandableView) getChildAt(childIdx); 4472 if (child.getVisibility() == GONE) { 4473 continue; 4474 } 4475 float bottom = child.getTranslationY() + child.getActualHeight() 4476 - child.getClipBottomAmount(); 4477 if (bottom > max) { 4478 max = bottom; 4479 } 4480 } 4481 return max + getStackTranslation(); 4482 } 4483 4484 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setStatusBar(StatusBar statusBar)4485 public void setStatusBar(StatusBar statusBar) { 4486 this.mStatusBar = statusBar; 4487 } 4488 4489 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) requestAnimateEverything()4490 void requestAnimateEverything() { 4491 if (mIsExpanded && mAnimationsEnabled) { 4492 mEverythingNeedsAnimation = true; 4493 mNeedsAnimation = true; 4494 requestChildrenUpdate(); 4495 } 4496 } 4497 4498 @ShadeViewRefactor(RefactorComponent.COORDINATOR) isBelowLastNotification(float touchX, float touchY)4499 public boolean isBelowLastNotification(float touchX, float touchY) { 4500 int childCount = getChildCount(); 4501 for (int i = childCount - 1; i >= 0; i--) { 4502 ExpandableView child = (ExpandableView) getChildAt(i); 4503 if (child.getVisibility() != View.GONE) { 4504 float childTop = child.getY(); 4505 if (childTop > touchY) { 4506 // we are above a notification entirely let's abort 4507 return false; 4508 } 4509 boolean belowChild = touchY > childTop + child.getActualHeight() 4510 - child.getClipBottomAmount(); 4511 if (child == mFooterView) { 4512 if (!belowChild && !mFooterView.isOnEmptySpace(touchX - mFooterView.getX(), 4513 touchY - childTop)) { 4514 // We clicked on the dismiss button 4515 return false; 4516 } 4517 } else if (child == mEmptyShadeView) { 4518 // We arrived at the empty shade view, for which we accept all clicks 4519 return true; 4520 } else if (!belowChild) { 4521 // We are on a child 4522 return false; 4523 } 4524 } 4525 } 4526 return touchY > mTopPadding + mStackTranslation; 4527 } 4528 4529 /** @hide */ 4530 @Override 4531 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) onInitializeAccessibilityEventInternal(AccessibilityEvent event)4532 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 4533 super.onInitializeAccessibilityEventInternal(event); 4534 event.setScrollable(mScrollable); 4535 event.setMaxScrollX(mScrollX); 4536 event.setScrollY(mOwnScrollY); 4537 event.setMaxScrollY(getScrollRange()); 4538 } 4539 4540 @Override 4541 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)4542 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 4543 super.onInitializeAccessibilityNodeInfoInternal(info); 4544 if (mScrollable) { 4545 info.setScrollable(true); 4546 if (mBackwardScrollable) { 4547 info.addAction( 4548 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD); 4549 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP); 4550 } 4551 if (mForwardScrollable) { 4552 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD); 4553 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_DOWN); 4554 } 4555 } 4556 // Talkback only listenes to scroll events of certain classes, let's make us a scrollview 4557 info.setClassName(ScrollView.class.getName()); 4558 } 4559 4560 @ShadeViewRefactor(RefactorComponent.COORDINATOR) generateChildOrderChangedEvent()4561 public void generateChildOrderChangedEvent() { 4562 if (mIsExpanded && mAnimationsEnabled) { 4563 mGenerateChildOrderChangedEvent = true; 4564 mNeedsAnimation = true; 4565 requestChildrenUpdate(); 4566 } 4567 } 4568 4569 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) getContainerChildCount()4570 public int getContainerChildCount() { 4571 return getChildCount(); 4572 } 4573 4574 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) getContainerChildAt(int i)4575 public View getContainerChildAt(int i) { 4576 return getChildAt(i); 4577 } 4578 4579 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) removeContainerView(View v)4580 public void removeContainerView(View v) { 4581 Assert.isMainThread(); 4582 removeView(v); 4583 if (v instanceof ExpandableNotificationRow && !mController.isShowingEmptyShadeView()) { 4584 mController.updateShowEmptyShadeView(); 4585 updateFooter(); 4586 } 4587 4588 updateSpeedBumpIndex(); 4589 } 4590 4591 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) addContainerView(View v)4592 public void addContainerView(View v) { 4593 Assert.isMainThread(); 4594 addView(v); 4595 if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) { 4596 mController.updateShowEmptyShadeView(); 4597 updateFooter(); 4598 } 4599 4600 updateSpeedBumpIndex(); 4601 } 4602 addContainerViewAt(View v, int index)4603 public void addContainerViewAt(View v, int index) { 4604 Assert.isMainThread(); 4605 addView(v, index); 4606 if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) { 4607 mController.updateShowEmptyShadeView(); 4608 updateFooter(); 4609 } 4610 4611 updateSpeedBumpIndex(); 4612 } 4613 4614 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) runAfterAnimationFinished(Runnable runnable)4615 public void runAfterAnimationFinished(Runnable runnable) { 4616 mAnimationFinishedRunnables.add(runnable); 4617 } 4618 generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp)4619 public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) { 4620 ExpandableNotificationRow row = entry.getHeadsUpAnimationView(); 4621 generateHeadsUpAnimation(row, isHeadsUp); 4622 } 4623 4624 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp)4625 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) { 4626 if (mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed)) { 4627 mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp)); 4628 mNeedsAnimation = true; 4629 if (!mIsExpanded && !mWillExpand && !isHeadsUp) { 4630 row.setHeadsUpAnimatingAway(true); 4631 } 4632 requestChildrenUpdate(); 4633 } 4634 } 4635 4636 /** 4637 * Set the boundary for the bottom heads up position. The heads up will always be above this 4638 * position. 4639 * 4640 * @param height the height of the screen 4641 * @param bottomBarHeight the height of the bar on the bottom 4642 */ 4643 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setHeadsUpBoundaries(int height, int bottomBarHeight)4644 public void setHeadsUpBoundaries(int height, int bottomBarHeight) { 4645 mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight); 4646 mStateAnimator.setHeadsUpAppearHeightBottom(height); 4647 requestChildrenUpdate(); 4648 } 4649 setWillExpand(boolean willExpand)4650 public void setWillExpand(boolean willExpand) { 4651 mWillExpand = willExpand; 4652 } 4653 4654 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setTrackingHeadsUp(ExpandableNotificationRow row)4655 public void setTrackingHeadsUp(ExpandableNotificationRow row) { 4656 mAmbientState.setTrackedHeadsUpRow(row); 4657 mTrackingHeadsUp = row != null; 4658 } 4659 4660 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) forceNoOverlappingRendering(boolean force)4661 public void forceNoOverlappingRendering(boolean force) { 4662 mForceNoOverlappingRendering = force; 4663 } 4664 4665 @Override 4666 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) hasOverlappingRendering()4667 public boolean hasOverlappingRendering() { 4668 return !mForceNoOverlappingRendering && super.hasOverlappingRendering(); 4669 } 4670 4671 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) setAnimationRunning(boolean animationRunning)4672 public void setAnimationRunning(boolean animationRunning) { 4673 if (animationRunning != mAnimationRunning) { 4674 if (animationRunning) { 4675 getViewTreeObserver().addOnPreDrawListener(mRunningAnimationUpdater); 4676 } else { 4677 getViewTreeObserver().removeOnPreDrawListener(mRunningAnimationUpdater); 4678 } 4679 mAnimationRunning = animationRunning; 4680 updateContinuousShadowDrawing(); 4681 } 4682 } 4683 4684 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) isExpanded()4685 public boolean isExpanded() { 4686 return mIsExpanded; 4687 } 4688 4689 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setPulsing(boolean pulsing, boolean animated)4690 public void setPulsing(boolean pulsing, boolean animated) { 4691 if (!mPulsing && !pulsing) { 4692 return; 4693 } 4694 mPulsing = pulsing; 4695 mAmbientState.setPulsing(pulsing); 4696 mSwipeHelper.setPulsing(pulsing); 4697 updateNotificationAnimationStates(); 4698 updateAlgorithmHeightAndPadding(); 4699 updateContentHeight(); 4700 requestChildrenUpdate(); 4701 notifyHeightChangeListener(null, animated); 4702 } 4703 4704 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setQsExpanded(boolean qsExpanded)4705 public void setQsExpanded(boolean qsExpanded) { 4706 mQsExpanded = qsExpanded; 4707 updateAlgorithmLayoutMinHeight(); 4708 updateScrollability(); 4709 } 4710 isQsExpanded()4711 boolean isQsExpanded() { 4712 return mQsExpanded; 4713 } 4714 4715 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setQsExpansionFraction(float qsExpansionFraction)4716 public void setQsExpansionFraction(float qsExpansionFraction) { 4717 mQsExpansionFraction = qsExpansionFraction; 4718 updateUseRoundedRectClipping(); 4719 4720 // If notifications are scrolled, 4721 // clear out scrollY by the time we push notifications offscreen 4722 if (mOwnScrollY > 0) { 4723 setOwnScrollY((int) MathUtils.lerp(mOwnScrollY, 0, mQsExpansionFraction)); 4724 } 4725 } 4726 4727 @ShadeViewRefactor(RefactorComponent.COORDINATOR) setOwnScrollY(int ownScrollY)4728 private void setOwnScrollY(int ownScrollY) { 4729 setOwnScrollY(ownScrollY, false /* animateScrollChangeListener */); 4730 } 4731 4732 @ShadeViewRefactor(RefactorComponent.COORDINATOR) setOwnScrollY(int ownScrollY, boolean animateStackYChangeListener)4733 private void setOwnScrollY(int ownScrollY, boolean animateStackYChangeListener) { 4734 if (ownScrollY != mOwnScrollY) { 4735 // We still want to call the normal scrolled changed for accessibility reasons 4736 onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY); 4737 mOwnScrollY = ownScrollY; 4738 mAmbientState.setScrollY(mOwnScrollY); 4739 updateOnScrollChange(); 4740 updateStackPosition(animateStackYChangeListener); 4741 } 4742 } 4743 updateOnScrollChange()4744 private void updateOnScrollChange() { 4745 if (mScrollListener != null) { 4746 mScrollListener.accept(mOwnScrollY); 4747 } 4748 updateForwardAndBackwardScrollability(); 4749 requestChildrenUpdate(); 4750 } 4751 4752 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setShelfController(NotificationShelfController notificationShelfController)4753 public void setShelfController(NotificationShelfController notificationShelfController) { 4754 int index = -1; 4755 if (mShelf != null) { 4756 index = indexOfChild(mShelf); 4757 removeView(mShelf); 4758 } 4759 mShelf = notificationShelfController.getView(); 4760 addView(mShelf, index); 4761 mAmbientState.setShelf(mShelf); 4762 mStateAnimator.setShelf(mShelf); 4763 notificationShelfController.bind(mAmbientState, mController); 4764 } 4765 4766 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setMaxDisplayedNotifications(int maxDisplayedNotifications)4767 public void setMaxDisplayedNotifications(int maxDisplayedNotifications) { 4768 if (mMaxDisplayedNotifications != maxDisplayedNotifications) { 4769 mMaxDisplayedNotifications = maxDisplayedNotifications; 4770 updateContentHeight(); 4771 notifyHeightChangeListener(mShelf); 4772 } 4773 } 4774 4775 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setShouldShowShelfOnly(boolean shouldShowShelfOnly)4776 public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) { 4777 mShouldShowShelfOnly = shouldShowShelfOnly; 4778 updateAlgorithmLayoutMinHeight(); 4779 } 4780 4781 @ShadeViewRefactor(RefactorComponent.COORDINATOR) getMinExpansionHeight()4782 public int getMinExpansionHeight() { 4783 return mShelf.getIntrinsicHeight() 4784 - (mShelf.getIntrinsicHeight() - mStatusBarHeight + mWaterfallTopInset) / 2 4785 + mWaterfallTopInset; 4786 } 4787 4788 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setInHeadsUpPinnedMode(boolean inHeadsUpPinnedMode)4789 public void setInHeadsUpPinnedMode(boolean inHeadsUpPinnedMode) { 4790 mInHeadsUpPinnedMode = inHeadsUpPinnedMode; 4791 updateClipping(); 4792 } 4793 4794 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setHeadsUpAnimatingAway(boolean headsUpAnimatingAway)4795 public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { 4796 mHeadsUpAnimatingAway = headsUpAnimatingAway; 4797 updateClipping(); 4798 } 4799 4800 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 4801 @VisibleForTesting setStatusBarState(int statusBarState)4802 public void setStatusBarState(int statusBarState) { 4803 mStatusBarState = statusBarState; 4804 mAmbientState.setStatusBarState(statusBarState); 4805 updateSpeedBumpIndex(); 4806 updateDismissBehavior(); 4807 } 4808 onStatePostChange(boolean fromShadeLocked)4809 void onStatePostChange(boolean fromShadeLocked) { 4810 boolean onKeyguard = onKeyguard(); 4811 4812 mAmbientState.setActivatedChild(null); 4813 mAmbientState.setDimmed(onKeyguard); 4814 4815 if (mHeadsUpAppearanceController != null) { 4816 mHeadsUpAppearanceController.onStateChanged(); 4817 } 4818 4819 setDimmed(onKeyguard, fromShadeLocked); 4820 setExpandingEnabled(!onKeyguard); 4821 ActivatableNotificationView activatedChild = getActivatedChild(); 4822 setActivatedChild(null); 4823 if (activatedChild != null) { 4824 activatedChild.makeInactive(false /* animate */); 4825 } 4826 updateFooter(); 4827 requestChildrenUpdate(); 4828 onUpdateRowStates(); 4829 updateVisibility(); 4830 } 4831 4832 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setExpandingVelocity(float expandingVelocity)4833 public void setExpandingVelocity(float expandingVelocity) { 4834 mAmbientState.setExpandingVelocity(expandingVelocity); 4835 } 4836 4837 @ShadeViewRefactor(RefactorComponent.COORDINATOR) getOpeningHeight()4838 public float getOpeningHeight() { 4839 if (mEmptyShadeView.getVisibility() == GONE) { 4840 return getMinExpansionHeight(); 4841 } else { 4842 return getAppearEndPosition(); 4843 } 4844 } 4845 4846 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setIsFullWidth(boolean isFullWidth)4847 public void setIsFullWidth(boolean isFullWidth) { 4848 mAmbientState.setPanelFullWidth(isFullWidth); 4849 } 4850 4851 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setUnlockHintRunning(boolean running)4852 public void setUnlockHintRunning(boolean running) { 4853 mAmbientState.setUnlockHintRunning(running); 4854 } 4855 4856 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setQsCustomizerShowing(boolean isShowing)4857 public void setQsCustomizerShowing(boolean isShowing) { 4858 mAmbientState.setQsCustomizerShowing(isShowing); 4859 requestChildrenUpdate(); 4860 } 4861 4862 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setHeadsUpGoingAwayAnimationsAllowed(boolean headsUpGoingAwayAnimationsAllowed)4863 public void setHeadsUpGoingAwayAnimationsAllowed(boolean headsUpGoingAwayAnimationsAllowed) { 4864 mHeadsUpGoingAwayAnimationsAllowed = headsUpGoingAwayAnimationsAllowed; 4865 } 4866 4867 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) dump(FileDescriptor fd, PrintWriter pw, String[] args)4868 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 4869 pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s" 4870 + " alpha=%f scrollY:%d maxTopPadding=%d showShelfOnly=%s" 4871 + " qsExpandFraction=%f" 4872 + " hideAmount=%f]", 4873 this.getClass().getSimpleName(), 4874 mPulsing ? "T" : "f", 4875 mAmbientState.isQsCustomizerShowing() ? "T" : "f", 4876 getVisibility() == View.VISIBLE ? "visible" 4877 : getVisibility() == View.GONE ? "gone" 4878 : "invisible", 4879 getAlpha(), 4880 mAmbientState.getScrollY(), 4881 mMaxTopPadding, 4882 mShouldShowShelfOnly ? "T" : "f", 4883 mQsExpansionFraction, 4884 mAmbientState.getHideAmount())); 4885 int childCount = getChildCount(); 4886 pw.println(" Number of children: " + childCount); 4887 pw.println(); 4888 4889 for (int i = 0; i < childCount; i++) { 4890 ExpandableView child = (ExpandableView) getChildAt(i); 4891 child.dump(fd, pw, args); 4892 if (!(child instanceof ExpandableNotificationRow)) { 4893 pw.println(" " + child.getClass().getSimpleName()); 4894 // Notifications dump it's viewstate as part of their dump to support children 4895 ExpandableViewState viewState = child.getViewState(); 4896 if (viewState == null) { 4897 pw.println(" no viewState!!!"); 4898 } else { 4899 pw.print(" "); 4900 viewState.dump(fd, pw, args); 4901 pw.println(); 4902 pw.println(); 4903 } 4904 } 4905 } 4906 int transientViewCount = getTransientViewCount(); 4907 pw.println(" Transient Views: " + transientViewCount); 4908 for (int i = 0; i < transientViewCount; i++) { 4909 ExpandableView child = (ExpandableView) getTransientView(i); 4910 child.dump(fd, pw, args); 4911 } 4912 View swipedView = mSwipeHelper.getSwipedView(); 4913 pw.println(" Swiped view: " + swipedView); 4914 if (swipedView instanceof ExpandableView) { 4915 ExpandableView expandableView = (ExpandableView) swipedView; 4916 expandableView.dump(fd, pw, args); 4917 } 4918 } 4919 4920 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) isFullyHidden()4921 public boolean isFullyHidden() { 4922 return mAmbientState.isFullyHidden(); 4923 } 4924 4925 /** 4926 * Add a listener whenever the expanded height changes. The first value passed as an 4927 * argument is the expanded height and the second one is the appearFraction. 4928 * 4929 * @param listener the listener to notify. 4930 */ 4931 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener)4932 public void addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) { 4933 mExpandedHeightListeners.add(listener); 4934 } 4935 4936 /** 4937 * Stop a listener from listening to the expandedHeight. 4938 */ 4939 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) removeOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener)4940 public void removeOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) { 4941 mExpandedHeightListeners.remove(listener); 4942 } 4943 4944 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) setHeadsUpAppearanceController( HeadsUpAppearanceController headsUpAppearanceController)4945 void setHeadsUpAppearanceController( 4946 HeadsUpAppearanceController headsUpAppearanceController) { 4947 mHeadsUpAppearanceController = headsUpAppearanceController; 4948 } 4949 4950 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 4951 @VisibleForTesting clearNotifications(@electedRows int selection, boolean closeShade)4952 void clearNotifications(@SelectedRows int selection, boolean closeShade) { 4953 // animate-swipe all dismissable notifications, then animate the shade closed 4954 int numChildren = getChildCount(); 4955 4956 final ArrayList<View> viewsToHide = new ArrayList<>(numChildren); 4957 final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren); 4958 for (int i = 0; i < numChildren; i++) { 4959 final View child = getChildAt(i); 4960 if (child instanceof ExpandableNotificationRow) { 4961 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 4962 boolean parentVisible = false; 4963 boolean hasClipBounds = child.getClipBounds(mTmpRect); 4964 if (includeChildInDismissAll(row, selection)) { 4965 viewsToRemove.add(row); 4966 if (child.getVisibility() == View.VISIBLE 4967 && (!hasClipBounds || mTmpRect.height() > 0)) { 4968 viewsToHide.add(child); 4969 parentVisible = true; 4970 } 4971 } else if (child.getVisibility() == View.VISIBLE 4972 && (!hasClipBounds || mTmpRect.height() > 0)) { 4973 parentVisible = true; 4974 } 4975 List<ExpandableNotificationRow> children = row.getAttachedChildren(); 4976 if (children != null) { 4977 for (ExpandableNotificationRow childRow : children) { 4978 if (includeChildInDismissAll(row, selection)) { 4979 viewsToRemove.add(childRow); 4980 if (parentVisible && row.areChildrenExpanded()) { 4981 hasClipBounds = childRow.getClipBounds(mTmpRect); 4982 if (childRow.getVisibility() == View.VISIBLE 4983 && (!hasClipBounds || mTmpRect.height() > 0)) { 4984 viewsToHide.add(childRow); 4985 } 4986 } 4987 } 4988 } 4989 } 4990 } 4991 } 4992 4993 if (mDismissListener != null) { 4994 mDismissListener.onDismiss(selection); 4995 } 4996 4997 if (viewsToRemove.isEmpty()) { 4998 if (closeShade && mShadeController != null) { 4999 mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 5000 } 5001 return; 5002 } 5003 5004 performDismissAllAnimations( 5005 viewsToHide, 5006 closeShade, 5007 () -> onDismissAllAnimationsEnd(viewsToRemove, selection)); 5008 } 5009 includeChildInDismissAll( ExpandableNotificationRow row, @SelectedRows int selection)5010 private boolean includeChildInDismissAll( 5011 ExpandableNotificationRow row, 5012 @SelectedRows int selection) { 5013 return canChildBeDismissed(row) && matchesSelection(row, selection); 5014 } 5015 5016 /** 5017 * Given a list of rows, animates them away in a staggered fashion as if they were dismissed. 5018 * Doesn't actually dismiss them, though -- that must be done in the onAnimationComplete 5019 * handler. 5020 * 5021 * @param hideAnimatedList List of rows to animated away. Should only be views that are 5022 * currently visible, or else the stagger will look funky. 5023 * @param closeShade Whether to close the shade after the stagger animation completes. 5024 * @param onAnimationComplete Called after the entire animation completes (including the shade 5025 * closing if appropriate). The rows must be dismissed for real here. 5026 */ 5027 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) performDismissAllAnimations( final ArrayList<View> hideAnimatedList, final boolean closeShade, final Runnable onAnimationComplete)5028 private void performDismissAllAnimations( 5029 final ArrayList<View> hideAnimatedList, 5030 final boolean closeShade, 5031 final Runnable onAnimationComplete) { 5032 5033 final Runnable onSlideAwayAnimationComplete = () -> { 5034 if (closeShade) { 5035 mShadeController.addPostCollapseAction(() -> { 5036 setDismissAllInProgress(false); 5037 onAnimationComplete.run(); 5038 }); 5039 mShadeController.animateCollapsePanels( 5040 CommandQueue.FLAG_EXCLUDE_NONE); 5041 } else { 5042 setDismissAllInProgress(false); 5043 onAnimationComplete.run(); 5044 } 5045 }; 5046 5047 if (hideAnimatedList.isEmpty()) { 5048 onSlideAwayAnimationComplete.run(); 5049 return; 5050 } 5051 5052 // let's disable our normal animations 5053 setDismissAllInProgress(true); 5054 5055 // Decrease the delay for every row we animate to give the sense of 5056 // accelerating the swipes 5057 int rowDelayDecrement = 10; 5058 int currentDelay = 140; 5059 int totalDelay = 180; 5060 int numItems = hideAnimatedList.size(); 5061 for (int i = numItems - 1; i >= 0; i--) { 5062 View view = hideAnimatedList.get(i); 5063 Runnable endRunnable = null; 5064 if (i == 0) { 5065 endRunnable = onSlideAwayAnimationComplete; 5066 } 5067 dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE); 5068 currentDelay = Math.max(50, currentDelay - rowDelayDecrement); 5069 totalDelay += currentDelay; 5070 } 5071 } 5072 setNotificationActivityStarter( NotificationActivityStarter notificationActivityStarter)5073 public void setNotificationActivityStarter( 5074 NotificationActivityStarter notificationActivityStarter) { 5075 mNotificationActivityStarter = notificationActivityStarter; 5076 } 5077 5078 @VisibleForTesting 5079 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) inflateFooterView()5080 protected void inflateFooterView() { 5081 FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate( 5082 R.layout.status_bar_notification_footer, this, false); 5083 footerView.setDismissButtonClickListener(v -> { 5084 if (mFooterDismissListener != null) { 5085 mFooterDismissListener.onDismiss(); 5086 } 5087 clearNotifications(ROWS_ALL, true /* closeShade */); 5088 }); 5089 footerView.setManageButtonClickListener(v -> { 5090 mNotificationActivityStarter.startHistoryIntent(v, mFooterView.isHistoryShown()); 5091 }); 5092 setFooterView(footerView); 5093 } 5094 5095 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) inflateEmptyShadeView()5096 private void inflateEmptyShadeView() { 5097 EmptyShadeView view = (EmptyShadeView) LayoutInflater.from(mContext).inflate( 5098 R.layout.status_bar_no_notifications, this, false); 5099 view.setText(R.string.empty_shade_text); 5100 view.setOnClickListener(v -> { 5101 final boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(), 5102 Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1; 5103 Intent intent = showHistory ? new Intent( 5104 Settings.ACTION_NOTIFICATION_HISTORY) : new Intent( 5105 Settings.ACTION_NOTIFICATION_SETTINGS); 5106 mStatusBar.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP); 5107 }); 5108 setEmptyShadeView(view); 5109 } 5110 5111 /** 5112 * Updates expanded, dimmed and locked states of notification rows. 5113 */ 5114 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) onUpdateRowStates()5115 public void onUpdateRowStates() { 5116 5117 // The following views will be moved to the end of mStackScroller. This counter represents 5118 // the offset from the last child. Initialized to 1 for the very last position. It is post- 5119 // incremented in the following "changeViewPosition" calls so that its value is correct for 5120 // subsequent calls. 5121 int offsetFromEnd = 1; 5122 if (mFgsSectionView != null) { 5123 changeViewPosition(mFgsSectionView, getChildCount() - offsetFromEnd++); 5124 } 5125 changeViewPosition(mFooterView, getChildCount() - offsetFromEnd++); 5126 changeViewPosition(mEmptyShadeView, getChildCount() - offsetFromEnd++); 5127 5128 // No post-increment for this call because it is the last one. Make sure to add one if 5129 // another "changeViewPosition" call is ever added. 5130 changeViewPosition(mShelf, 5131 getChildCount() - offsetFromEnd); 5132 } 5133 5134 /** 5135 * Set how far the wake up is when waking up from pulsing. This is a height and will adjust the 5136 * notification positions accordingly. 5137 * @param height the new wake up height 5138 * @return the overflow how much the height is further than he lowest notification 5139 */ setPulseHeight(float height)5140 public float setPulseHeight(float height) { 5141 float overflow; 5142 mAmbientState.setPulseHeight(height); 5143 if (mKeyguardBypassEnabledProvider.getBypassEnabled()) { 5144 notifyAppearChangedListeners(); 5145 overflow = Math.max(0, height - getIntrinsicPadding()); 5146 } else { 5147 overflow = Math.max(0, height 5148 - mAmbientState.getInnerHeight(true /* ignorePulseHeight */)); 5149 } 5150 requestChildrenUpdate(); 5151 return overflow; 5152 } 5153 getPulseHeight()5154 public float getPulseHeight() { 5155 return mAmbientState.getPulseHeight(); 5156 } 5157 5158 /** 5159 * Set the amount how much we're dozing. This is different from how hidden the shade is, when 5160 * the notification is pulsing. 5161 */ setDozeAmount(float dozeAmount)5162 public void setDozeAmount(float dozeAmount) { 5163 mAmbientState.setDozeAmount(dozeAmount); 5164 updateContinuousBackgroundDrawing(); 5165 requestChildrenUpdate(); 5166 } 5167 isFullyAwake()5168 public boolean isFullyAwake() { 5169 return mAmbientState.isFullyAwake(); 5170 } 5171 wakeUpFromPulse()5172 public void wakeUpFromPulse() { 5173 setPulseHeight(getWakeUpHeight()); 5174 // Let's place the hidden views at the end of the pulsing notification to make sure we have 5175 // a smooth animation 5176 boolean firstVisibleView = true; 5177 float wakeUplocation = -1f; 5178 int childCount = getChildCount(); 5179 for (int i = 0; i < childCount; i++) { 5180 ExpandableView view = (ExpandableView) getChildAt(i); 5181 if (view.getVisibility() == View.GONE) { 5182 continue; 5183 } 5184 boolean isShelf = view == mShelf; 5185 if (!(view instanceof ExpandableNotificationRow) && !isShelf) { 5186 continue; 5187 } 5188 if (view.getVisibility() == View.VISIBLE && !isShelf) { 5189 if (firstVisibleView) { 5190 firstVisibleView = false; 5191 wakeUplocation = view.getTranslationY() 5192 + view.getActualHeight() - mShelf.getIntrinsicHeight(); 5193 } 5194 } else if (!firstVisibleView) { 5195 view.setTranslationY(wakeUplocation); 5196 } 5197 } 5198 mDimmedNeedsAnimation = true; 5199 } 5200 setAnimateBottomOnLayout(boolean animateBottomOnLayout)5201 void setAnimateBottomOnLayout(boolean animateBottomOnLayout) { 5202 mAnimateBottomOnLayout = animateBottomOnLayout; 5203 } 5204 setOnPulseHeightChangedListener(Runnable listener)5205 public void setOnPulseHeightChangedListener(Runnable listener) { 5206 mAmbientState.setOnPulseHeightChangedListener(listener); 5207 } 5208 calculateAppearFractionBypass()5209 public float calculateAppearFractionBypass() { 5210 float pulseHeight = getPulseHeight(); 5211 // The total distance required to fully reveal the header 5212 float totalDistance = getIntrinsicPadding(); 5213 return MathUtils.smoothStep(0, totalDistance, pulseHeight); 5214 } 5215 setController( NotificationStackScrollLayoutController notificationStackScrollLayoutController)5216 public void setController( 5217 NotificationStackScrollLayoutController notificationStackScrollLayoutController) { 5218 mController = notificationStackScrollLayoutController; 5219 mController.getNoticationRoundessManager().setAnimatedChildren(mChildrenToAddAnimated); 5220 } 5221 getController()5222 public NotificationStackScrollLayoutController getController() { 5223 return mController; 5224 } 5225 addSwipedOutView(View v)5226 void addSwipedOutView(View v) { 5227 mSwipedOutViews.add(v); 5228 } 5229 onSwipeBegin(View viewSwiped)5230 void onSwipeBegin(View viewSwiped) { 5231 if (!(viewSwiped instanceof ExpandableNotificationRow)) { 5232 return; 5233 } 5234 final int indexOfSwipedView = indexOfChild(viewSwiped); 5235 if (indexOfSwipedView < 0) { 5236 return; 5237 } 5238 mSectionsManager.updateFirstAndLastViewsForAllSections( 5239 mSections, getChildrenWithBackground()); 5240 View viewBefore = null; 5241 if (indexOfSwipedView > 0) { 5242 viewBefore = getChildAt(indexOfSwipedView - 1); 5243 if (mSectionsManager.beginsSection(viewSwiped, viewBefore)) { 5244 viewBefore = null; 5245 } 5246 } 5247 View viewAfter = null; 5248 if (indexOfSwipedView < getChildCount()) { 5249 viewAfter = getChildAt(indexOfSwipedView + 1); 5250 if (mSectionsManager.beginsSection(viewAfter, viewSwiped)) { 5251 viewAfter = null; 5252 } 5253 } 5254 mController.getNoticationRoundessManager() 5255 .setViewsAffectedBySwipe((ExpandableView) viewBefore, 5256 (ExpandableView) viewSwiped, 5257 (ExpandableView) viewAfter, 5258 getResources().getBoolean(R.bool.flag_notif_updates)); 5259 5260 updateFirstAndLastBackgroundViews(); 5261 requestDisallowInterceptTouchEvent(true); 5262 updateContinuousShadowDrawing(); 5263 updateContinuousBackgroundDrawing(); 5264 requestChildrenUpdate(); 5265 } 5266 onSwipeEnd()5267 void onSwipeEnd() { 5268 updateFirstAndLastBackgroundViews(); 5269 mController.getNoticationRoundessManager() 5270 .setViewsAffectedBySwipe(null, null, null, 5271 getResources().getBoolean(R.bool.flag_notif_updates)); 5272 // Round bottom corners for notification right before shelf. 5273 mShelf.updateAppearance(); 5274 } 5275 setTopHeadsUpEntry(NotificationEntry topEntry)5276 void setTopHeadsUpEntry(NotificationEntry topEntry) { 5277 mTopHeadsUpEntry = topEntry; 5278 } 5279 setNumHeadsUp(long numHeadsUp)5280 void setNumHeadsUp(long numHeadsUp) { 5281 mNumHeadsUp = numHeadsUp; 5282 mAmbientState.setHasAlertEntries(numHeadsUp > 0); 5283 } 5284 getIsExpanded()5285 public boolean getIsExpanded() { 5286 return mIsExpanded; 5287 } 5288 getOnlyScrollingInThisMotion()5289 boolean getOnlyScrollingInThisMotion() { 5290 return mOnlyScrollingInThisMotion; 5291 } 5292 getExpandHelper()5293 ExpandHelper getExpandHelper() { 5294 return mExpandHelper; 5295 } 5296 isExpandingNotification()5297 boolean isExpandingNotification() { 5298 return mExpandingNotification; 5299 } 5300 getDisallowScrollingInThisMotion()5301 boolean getDisallowScrollingInThisMotion() { 5302 return mDisallowScrollingInThisMotion; 5303 } 5304 isBeingDragged()5305 boolean isBeingDragged() { 5306 return mIsBeingDragged; 5307 } 5308 getExpandedInThisMotion()5309 boolean getExpandedInThisMotion() { 5310 return mExpandedInThisMotion; 5311 } 5312 getDisallowDismissInThisMotion()5313 boolean getDisallowDismissInThisMotion() { 5314 return mDisallowDismissInThisMotion; 5315 } 5316 setCheckForLeaveBehind(boolean checkForLeaveBehind)5317 void setCheckForLeaveBehind(boolean checkForLeaveBehind) { 5318 mCheckForLeavebehind = checkForLeaveBehind; 5319 } 5320 setTouchHandler(NotificationStackScrollLayoutController.TouchHandler touchHandler)5321 void setTouchHandler(NotificationStackScrollLayoutController.TouchHandler touchHandler) { 5322 mTouchHandler = touchHandler; 5323 } 5324 getCheckSnoozeLeaveBehind()5325 boolean getCheckSnoozeLeaveBehind() { 5326 return mCheckForLeavebehind; 5327 } 5328 setDismissListener(DismissListener listener)5329 void setDismissListener (DismissListener listener) { 5330 mDismissListener = listener; 5331 } 5332 setDismissAllAnimationListener(DismissAllAnimationListener dismissAllAnimationListener)5333 void setDismissAllAnimationListener(DismissAllAnimationListener dismissAllAnimationListener) { 5334 mDismissAllAnimationListener = dismissAllAnimationListener; 5335 } 5336 setHighPriorityBeforeSpeedBump(boolean highPriorityBeforeSpeedBump)5337 public void setHighPriorityBeforeSpeedBump(boolean highPriorityBeforeSpeedBump) { 5338 mHighPriorityBeforeSpeedBump = highPriorityBeforeSpeedBump; 5339 } 5340 setFooterDismissListener(FooterDismissListener listener)5341 void setFooterDismissListener(FooterDismissListener listener) { 5342 mFooterDismissListener = listener; 5343 } 5344 setRemoteInputManager(NotificationRemoteInputManager remoteInputManager)5345 public void setRemoteInputManager(NotificationRemoteInputManager remoteInputManager) { 5346 mRemoteInputManager = remoteInputManager; 5347 } 5348 setShadeController(ShadeController shadeController)5349 void setShadeController(ShadeController shadeController) { 5350 mShadeController = shadeController; 5351 } 5352 5353 /** 5354 * Sets the extra top inset for the full shade transition. This moves notifications down 5355 * during the drag down. 5356 */ setExtraTopInsetForFullShadeTransition(float inset)5357 public void setExtraTopInsetForFullShadeTransition(float inset) { 5358 mExtraTopInsetForFullShadeTransition = inset; 5359 updateStackPosition(); 5360 requestChildrenUpdate(); 5361 } 5362 5363 /** 5364 * Set a listener to when scrolling changes. 5365 */ setOnScrollListener(Consumer<Integer> listener)5366 public void setOnScrollListener(Consumer<Integer> listener) { 5367 mScrollListener = listener; 5368 } 5369 5370 /** 5371 * Set rounded rect clipping bounds on this view. 5372 */ setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius, int bottomRadius)5373 public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius, 5374 int bottomRadius) { 5375 if (mRoundedRectClippingLeft == left && mRoundedRectClippingRight == right 5376 && mRoundedRectClippingBottom == bottom && mRoundedRectClippingTop == top 5377 && mBgCornerRadii[0] == topRadius && mBgCornerRadii[5] == bottomRadius) { 5378 return; 5379 } 5380 mRoundedRectClippingLeft = left; 5381 mRoundedRectClippingTop = top; 5382 mRoundedRectClippingBottom = bottom; 5383 mRoundedRectClippingRight = right; 5384 mBgCornerRadii[0] = topRadius; 5385 mBgCornerRadii[1] = topRadius; 5386 mBgCornerRadii[2] = topRadius; 5387 mBgCornerRadii[3] = topRadius; 5388 mBgCornerRadii[4] = bottomRadius; 5389 mBgCornerRadii[5] = bottomRadius; 5390 mBgCornerRadii[6] = bottomRadius; 5391 mBgCornerRadii[7] = bottomRadius; 5392 mRoundedClipPath.reset(); 5393 mRoundedClipPath.addRoundRect(left, top, right, bottom, mBgCornerRadii, Path.Direction.CW); 5394 if (mShouldUseRoundedRectClipping) { 5395 invalidate(); 5396 } 5397 } 5398 updateSplitNotificationShade()5399 private void updateSplitNotificationShade() { 5400 boolean split = shouldUseSplitNotificationShade(mFeatureFlags, getResources()); 5401 if (split != mShouldUseSplitNotificationShade) { 5402 mShouldUseSplitNotificationShade = split; 5403 updateDismissBehavior(); 5404 updateUseRoundedRectClipping(); 5405 } 5406 } 5407 updateDismissBehavior()5408 private void updateDismissBehavior() { 5409 // On the split keyguard, dismissing with clipping without a visual boundary looks odd, 5410 // so let's use the content dismiss behavior instead. 5411 boolean dismissUsingRowTranslationX = !mShouldUseSplitNotificationShade 5412 || mStatusBarState != StatusBarState.KEYGUARD; 5413 if (mDismissUsingRowTranslationX != dismissUsingRowTranslationX) { 5414 mDismissUsingRowTranslationX = dismissUsingRowTranslationX; 5415 for (int i = 0; i < getChildCount(); i++) { 5416 View child = getChildAt(i); 5417 if (child instanceof ExpandableNotificationRow) { 5418 ((ExpandableNotificationRow) child).setDismissUsingRowTranslationX( 5419 dismissUsingRowTranslationX); 5420 } 5421 } 5422 } 5423 } 5424 5425 /** 5426 * Set if we're launching a notification right now. 5427 */ setLaunchingNotification(boolean launching)5428 private void setLaunchingNotification(boolean launching) { 5429 if (launching == mLaunchingNotification) { 5430 return; 5431 } 5432 mLaunchingNotification = launching; 5433 mLaunchingNotificationNeedsToBeClipped = mLaunchAnimationParams != null 5434 && (mLaunchAnimationParams.getStartRoundedTopClipping() > 0 5435 || mLaunchAnimationParams.getParentStartRoundedTopClipping() > 0); 5436 if (!mLaunchingNotificationNeedsToBeClipped || !mLaunchingNotification) { 5437 mLaunchedNotificationClipPath.reset(); 5438 } 5439 // When launching notifications, we're clipping the children individually instead of in 5440 // dispatchDraw 5441 invalidate(); 5442 } 5443 5444 /** 5445 * Should we use rounded rect clipping 5446 */ updateUseRoundedRectClipping()5447 private void updateUseRoundedRectClipping() { 5448 // We don't want to clip notifications when QS is expanded, because incoming heads up on 5449 // the bottom would be clipped otherwise 5450 boolean qsAllowsClipping = mQsExpansionFraction < 0.5f || mShouldUseSplitNotificationShade; 5451 boolean clip = mIsExpanded && qsAllowsClipping; 5452 if (clip != mShouldUseRoundedRectClipping) { 5453 mShouldUseRoundedRectClipping = clip; 5454 invalidate(); 5455 } 5456 } 5457 5458 /** 5459 * Update the clip path for launched notifications in case they were originally clipped 5460 */ 5461 private void updateLaunchedNotificationClipPath() { 5462 if (!mLaunchingNotificationNeedsToBeClipped || !mLaunchingNotification 5463 || mExpandingNotificationRow == null) { 5464 return; 5465 } 5466 int left = Math.min(mLaunchAnimationParams.getLeft(), mRoundedRectClippingLeft); 5467 int right = Math.max(mLaunchAnimationParams.getRight(), mRoundedRectClippingRight); 5468 int bottom = Math.max(mLaunchAnimationParams.getBottom(), mRoundedRectClippingBottom); 5469 float expandProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( 5470 mLaunchAnimationParams.getProgress(0, 5471 NotificationLaunchAnimatorController.ANIMATION_DURATION_TOP_ROUNDING)); 5472 int top = (int) Math.min(MathUtils.lerp(mRoundedRectClippingTop, 5473 mLaunchAnimationParams.getTop(), expandProgress), 5474 mRoundedRectClippingTop); 5475 float topRadius = mLaunchAnimationParams.getTopCornerRadius(); 5476 float bottomRadius = mLaunchAnimationParams.getBottomCornerRadius(); 5477 mLaunchedNotificationRadii[0] = topRadius; 5478 mLaunchedNotificationRadii[1] = topRadius; 5479 mLaunchedNotificationRadii[2] = topRadius; 5480 mLaunchedNotificationRadii[3] = topRadius; 5481 mLaunchedNotificationRadii[4] = bottomRadius; 5482 mLaunchedNotificationRadii[5] = bottomRadius; 5483 mLaunchedNotificationRadii[6] = bottomRadius; 5484 mLaunchedNotificationRadii[7] = bottomRadius; 5485 mLaunchedNotificationClipPath.reset(); 5486 mLaunchedNotificationClipPath.addRoundRect(left, top, right, bottom, 5487 mLaunchedNotificationRadii, Path.Direction.CW); 5488 // Offset into notification clip coordinates instead of parent ones. 5489 // This is needed since the notification changes in translationZ, where clipping via 5490 // canvas dispatching won't work. 5491 ExpandableNotificationRow expandingRow = mExpandingNotificationRow; 5492 if (expandingRow.getNotificationParent() != null) { 5493 expandingRow = expandingRow.getNotificationParent(); 5494 } 5495 mLaunchedNotificationClipPath.offset( 5496 -expandingRow.getLeft() - expandingRow.getTranslationX(), 5497 -expandingRow.getTop() - expandingRow.getTranslationY()); 5498 expandingRow.setExpandingClipPath(mLaunchedNotificationClipPath); 5499 if (mShouldUseRoundedRectClipping) { 5500 invalidate(); 5501 } 5502 } 5503 5504 @Override 5505 protected void dispatchDraw(Canvas canvas) { 5506 if (mShouldUseRoundedRectClipping && !mLaunchingNotification) { 5507 // When launching notifications, we're clipping the children individually instead of in 5508 // dispatchDraw 5509 // Let's clip rounded. 5510 canvas.clipPath(mRoundedClipPath); 5511 } 5512 super.dispatchDraw(canvas); 5513 } 5514 5515 @Override 5516 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 5517 if (mShouldUseRoundedRectClipping && mLaunchingNotification) { 5518 // Let's clip children individually during notification launch 5519 canvas.save(); 5520 ExpandableView expandableView = (ExpandableView) child; 5521 Path clipPath; 5522 if (expandableView.isExpandAnimationRunning() 5523 || ((ExpandableView) child).hasExpandingChild()) { 5524 // When launching the notification, it is not clipped by this layout, but by the 5525 // view itself. This is because the view is Translating in Z, where this clipPath 5526 // wouldn't apply. 5527 clipPath = null; 5528 } else { 5529 clipPath = mRoundedClipPath; 5530 } 5531 if (clipPath != null) { 5532 canvas.clipPath(clipPath); 5533 } 5534 boolean result = super.drawChild(canvas, child, drawingTime); 5535 canvas.restore(); 5536 return result; 5537 } else { 5538 return super.drawChild(canvas, child, drawingTime); 5539 } 5540 } 5541 5542 /** 5543 * Calculate the total translation needed when dismissing. 5544 */ 5545 public float getTotalTranslationLength(View animView) { 5546 if (!mDismissUsingRowTranslationX) { 5547 return animView.getMeasuredWidth(); 5548 } 5549 float notificationWidth = animView.getMeasuredWidth(); 5550 int containerWidth = getMeasuredWidth(); 5551 float padding = (containerWidth - notificationWidth) / 2.0f; 5552 return containerWidth - padding; 5553 } 5554 5555 /** 5556 * @return the start location where we start clipping notifications. 5557 */ 5558 public int getTopClippingStartLocation() { 5559 return mIsExpanded ? mQsScrollBoundaryPosition : 0; 5560 } 5561 5562 /** 5563 * Request an animation whenever the toppadding changes next 5564 */ 5565 public void animateNextTopPaddingChange() { 5566 mAnimateNextTopPaddingChange = true; 5567 } 5568 5569 /** 5570 * A listener that is notified when the empty space below the notifications is clicked on 5571 */ 5572 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5573 public interface OnEmptySpaceClickListener { 5574 void onEmptySpaceClicked(float x, float y); 5575 } 5576 5577 /** 5578 * A listener that gets notified when the overscroll at the top has changed. 5579 */ 5580 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5581 public interface OnOverscrollTopChangedListener { 5582 5583 /** 5584 * Notifies a listener that the overscroll has changed. 5585 * 5586 * @param amount the amount of overscroll, in pixels 5587 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an 5588 * unrubberbanded motion to directly expand overscroll view (e.g 5589 * expand 5590 * QS) 5591 */ 5592 void onOverscrollTopChanged(float amount, boolean isRubberbanded); 5593 5594 /** 5595 * Notify a listener that the scroller wants to escape from the scrolling motion and 5596 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS) 5597 * 5598 * @param velocity The velocity that the Scroller had when over flinging 5599 * @param open Should the fling open or close the overscroll view. 5600 */ 5601 void flingTopOverscroll(float velocity, boolean open); 5602 } 5603 5604 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5605 private void updateSpeedBumpIndex() { 5606 mSpeedBumpIndexDirty = true; 5607 } 5608 5609 /** Updates the indices of the boundaries between sections. */ 5610 @ShadeViewRefactor(RefactorComponent.INPUT) 5611 public void updateSectionBoundaries(String reason) { 5612 mSectionsManager.updateSectionBoundaries(reason); 5613 } 5614 5615 void updateContinuousBackgroundDrawing() { 5616 boolean continuousBackground = !mAmbientState.isFullyAwake() 5617 && mSwipeHelper.isSwiping(); 5618 if (continuousBackground != mContinuousBackgroundUpdate) { 5619 mContinuousBackgroundUpdate = continuousBackground; 5620 if (continuousBackground) { 5621 getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater); 5622 } else { 5623 getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater); 5624 } 5625 } 5626 } 5627 5628 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 5629 void updateContinuousShadowDrawing() { 5630 boolean continuousShadowUpdate = mAnimationRunning 5631 || mSwipeHelper.isSwiping(); 5632 if (continuousShadowUpdate != mContinuousShadowUpdate) { 5633 if (continuousShadowUpdate) { 5634 getViewTreeObserver().addOnPreDrawListener(mShadowUpdater); 5635 } else { 5636 getViewTreeObserver().removeOnPreDrawListener(mShadowUpdater); 5637 } 5638 mContinuousShadowUpdate = continuousShadowUpdate; 5639 } 5640 } 5641 5642 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5643 private void resetExposedMenuView(boolean animate, boolean force) { 5644 mSwipeHelper.resetExposedMenuView(animate, force); 5645 } 5646 5647 static boolean matchesSelection( 5648 ExpandableNotificationRow row, 5649 @SelectedRows int selection) { 5650 switch (selection) { 5651 case ROWS_ALL: 5652 return true; 5653 case ROWS_HIGH_PRIORITY: 5654 return row.getEntry().getBucket() < BUCKET_SILENT; 5655 case ROWS_GENTLE: 5656 return row.getEntry().getBucket() == BUCKET_SILENT; 5657 default: 5658 throw new IllegalArgumentException("Unknown selection: " + selection); 5659 } 5660 } 5661 5662 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) 5663 static class AnimationEvent { 5664 5665 static AnimationFilter[] FILTERS = new AnimationFilter[]{ 5666 5667 // ANIMATION_TYPE_ADD 5668 new AnimationFilter() 5669 .animateAlpha() 5670 .animateHeight() 5671 .animateTopInset() 5672 .animateY() 5673 .animateZ() 5674 .hasDelays(), 5675 5676 // ANIMATION_TYPE_REMOVE 5677 new AnimationFilter() 5678 .animateAlpha() 5679 .animateHeight() 5680 .animateTopInset() 5681 .animateY() 5682 .animateZ() 5683 .hasDelays(), 5684 5685 // ANIMATION_TYPE_REMOVE_SWIPED_OUT 5686 new AnimationFilter() 5687 .animateHeight() 5688 .animateTopInset() 5689 .animateY() 5690 .animateZ() 5691 .hasDelays(), 5692 5693 // ANIMATION_TYPE_TOP_PADDING_CHANGED 5694 new AnimationFilter() 5695 .animateHeight() 5696 .animateTopInset() 5697 .animateY() 5698 .animateDimmed() 5699 .animateZ(), 5700 5701 // ANIMATION_TYPE_ACTIVATED_CHILD 5702 new AnimationFilter() 5703 .animateZ(), 5704 5705 // ANIMATION_TYPE_DIMMED 5706 new AnimationFilter() 5707 .animateDimmed(), 5708 5709 // ANIMATION_TYPE_CHANGE_POSITION 5710 new AnimationFilter() 5711 .animateAlpha() // maybe the children change positions 5712 .animateHeight() 5713 .animateTopInset() 5714 .animateY() 5715 .animateZ(), 5716 5717 // ANIMATION_TYPE_GO_TO_FULL_SHADE 5718 new AnimationFilter() 5719 .animateHeight() 5720 .animateTopInset() 5721 .animateY() 5722 .animateDimmed() 5723 .animateZ() 5724 .hasDelays(), 5725 5726 // ANIMATION_TYPE_HIDE_SENSITIVE 5727 new AnimationFilter() 5728 .animateHideSensitive(), 5729 5730 // ANIMATION_TYPE_VIEW_RESIZE 5731 new AnimationFilter() 5732 .animateHeight() 5733 .animateTopInset() 5734 .animateY() 5735 .animateZ(), 5736 5737 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED 5738 new AnimationFilter() 5739 .animateAlpha() 5740 .animateHeight() 5741 .animateTopInset() 5742 .animateY() 5743 .animateZ(), 5744 5745 // ANIMATION_TYPE_HEADS_UP_APPEAR 5746 new AnimationFilter() 5747 .animateHeight() 5748 .animateTopInset() 5749 .animateY() 5750 .animateZ(), 5751 5752 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR 5753 new AnimationFilter() 5754 .animateHeight() 5755 .animateTopInset() 5756 .animateY() 5757 .animateZ() 5758 .hasDelays(), 5759 5760 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK 5761 new AnimationFilter() 5762 .animateHeight() 5763 .animateTopInset() 5764 .animateY() 5765 .animateZ() 5766 .hasDelays(), 5767 5768 // ANIMATION_TYPE_HEADS_UP_OTHER 5769 new AnimationFilter() 5770 .animateHeight() 5771 .animateTopInset() 5772 .animateY() 5773 .animateZ(), 5774 5775 // ANIMATION_TYPE_EVERYTHING 5776 new AnimationFilter() 5777 .animateAlpha() 5778 .animateDimmed() 5779 .animateHideSensitive() 5780 .animateHeight() 5781 .animateTopInset() 5782 .animateY() 5783 .animateZ(), 5784 }; 5785 5786 static int[] LENGTHS = new int[]{ 5787 5788 // ANIMATION_TYPE_ADD 5789 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR, 5790 5791 // ANIMATION_TYPE_REMOVE 5792 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR, 5793 5794 // ANIMATION_TYPE_REMOVE_SWIPED_OUT 5795 StackStateAnimator.ANIMATION_DURATION_STANDARD, 5796 5797 // ANIMATION_TYPE_TOP_PADDING_CHANGED 5798 StackStateAnimator.ANIMATION_DURATION_STANDARD, 5799 5800 // ANIMATION_TYPE_ACTIVATED_CHILD 5801 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED, 5802 5803 // ANIMATION_TYPE_DIMMED 5804 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED, 5805 5806 // ANIMATION_TYPE_CHANGE_POSITION 5807 StackStateAnimator.ANIMATION_DURATION_STANDARD, 5808 5809 // ANIMATION_TYPE_GO_TO_FULL_SHADE 5810 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, 5811 5812 // ANIMATION_TYPE_HIDE_SENSITIVE 5813 StackStateAnimator.ANIMATION_DURATION_STANDARD, 5814 5815 // ANIMATION_TYPE_VIEW_RESIZE 5816 StackStateAnimator.ANIMATION_DURATION_STANDARD, 5817 5818 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED 5819 StackStateAnimator.ANIMATION_DURATION_STANDARD, 5820 5821 // ANIMATION_TYPE_HEADS_UP_APPEAR 5822 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR, 5823 5824 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR 5825 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR, 5826 5827 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK 5828 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR, 5829 5830 // ANIMATION_TYPE_HEADS_UP_OTHER 5831 StackStateAnimator.ANIMATION_DURATION_STANDARD, 5832 5833 // ANIMATION_TYPE_EVERYTHING 5834 StackStateAnimator.ANIMATION_DURATION_STANDARD, 5835 }; 5836 5837 static final int ANIMATION_TYPE_ADD = 0; 5838 static final int ANIMATION_TYPE_REMOVE = 1; 5839 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2; 5840 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3; 5841 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 4; 5842 static final int ANIMATION_TYPE_DIMMED = 5; 5843 static final int ANIMATION_TYPE_CHANGE_POSITION = 6; 5844 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 7; 5845 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 8; 5846 static final int ANIMATION_TYPE_VIEW_RESIZE = 9; 5847 static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 10; 5848 static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 11; 5849 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 12; 5850 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 13; 5851 static final int ANIMATION_TYPE_HEADS_UP_OTHER = 14; 5852 static final int ANIMATION_TYPE_EVERYTHING = 15; 5853 5854 final long eventStartTime; 5855 final ExpandableView mChangingView; 5856 final int animationType; 5857 final AnimationFilter filter; 5858 final long length; 5859 View viewAfterChangingView; 5860 boolean headsUpFromBottom; 5861 5862 AnimationEvent(ExpandableView view, int type) { 5863 this(view, type, LENGTHS[type]); 5864 } 5865 5866 AnimationEvent(ExpandableView view, int type, AnimationFilter filter) { 5867 this(view, type, LENGTHS[type], filter); 5868 } 5869 5870 AnimationEvent(ExpandableView view, int type, long length) { 5871 this(view, type, length, FILTERS[type]); 5872 } 5873 5874 AnimationEvent(ExpandableView view, int type, long length, AnimationFilter filter) { 5875 eventStartTime = AnimationUtils.currentAnimationTimeMillis(); 5876 mChangingView = view; 5877 animationType = type; 5878 this.length = length; 5879 this.filter = filter; 5880 } 5881 5882 /** 5883 * Combines the length of several animation events into a single value. 5884 * 5885 * @param events The events of the lengths to combine. 5886 * @return The combined length. Depending on the event types, this might be the maximum of 5887 * all events or the length of a specific event. 5888 */ 5889 static long combineLength(ArrayList<AnimationEvent> events) { 5890 long length = 0; 5891 int size = events.size(); 5892 for (int i = 0; i < size; i++) { 5893 AnimationEvent event = events.get(i); 5894 length = Math.max(length, event.length); 5895 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) { 5896 return event.length; 5897 } 5898 } 5899 return length; 5900 } 5901 } 5902 5903 static boolean canChildBeDismissed(View v) { 5904 if (v instanceof ExpandableNotificationRow) { 5905 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 5906 if (row.isBlockingHelperShowingAndTranslationFinished()) { 5907 return true; 5908 } 5909 if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) { 5910 return false; 5911 } 5912 return row.canViewBeDismissed(); 5913 } 5914 if (v instanceof PeopleHubView) { 5915 return ((PeopleHubView) v).getCanSwipe(); 5916 } 5917 return false; 5918 } 5919 5920 // --------------------- NotificationEntryManager/NotifPipeline methods ------------------------ 5921 5922 void onEntryUpdated(NotificationEntry entry) { 5923 // If the row already exists, the user may have performed a dismiss action on the 5924 // notification. Since it's not clearable we should snap it back. 5925 if (entry.rowExists() && !entry.getSbn().isClearable()) { 5926 snapViewIfNeeded(entry); 5927 } 5928 } 5929 5930 /** 5931 * Called after the animations for a "clear all notifications" action has ended. 5932 */ 5933 private void onDismissAllAnimationsEnd( 5934 List<ExpandableNotificationRow> viewsToRemove, 5935 @SelectedRows int selectedRows) { 5936 if (mDismissAllAnimationListener != null) { 5937 mDismissAllAnimationListener.onAnimationEnd(viewsToRemove, selectedRows); 5938 } 5939 } 5940 5941 void resetCheckSnoozeLeavebehind() { 5942 setCheckForLeaveBehind(true); 5943 } 5944 5945 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5946 private final HeadsUpTouchHelper.Callback mHeadsUpCallback = new HeadsUpTouchHelper.Callback() { 5947 @Override 5948 public ExpandableView getChildAtRawPosition(float touchX, float touchY) { 5949 return NotificationStackScrollLayout.this.getChildAtRawPosition(touchX, touchY); 5950 } 5951 5952 @Override 5953 public boolean isExpanded() { 5954 return mIsExpanded; 5955 } 5956 5957 @Override 5958 public Context getContext() { 5959 return mContext; 5960 } 5961 }; 5962 5963 public HeadsUpTouchHelper.Callback getHeadsUpCallback() { return mHeadsUpCallback; } 5964 5965 void onGroupExpandChanged(ExpandableNotificationRow changedRow, boolean expanded) { 5966 boolean animated = mAnimationsEnabled && (mIsExpanded || changedRow.isPinned()); 5967 if (animated) { 5968 mExpandedGroupView = changedRow; 5969 mNeedsAnimation = true; 5970 } 5971 changedRow.setChildrenExpanded(expanded, animated); 5972 onChildHeightChanged(changedRow, false /* needsAnimation */); 5973 5974 runAfterAnimationFinished(new Runnable() { 5975 @Override 5976 public void run() { 5977 changedRow.onFinishedExpansionChange(); 5978 } 5979 }); 5980 } 5981 5982 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) 5983 private ExpandHelper.Callback mExpandHelperCallback = new ExpandHelper.Callback() { 5984 @Override 5985 public ExpandableView getChildAtPosition(float touchX, float touchY) { 5986 return NotificationStackScrollLayout.this.getChildAtPosition(touchX, touchY); 5987 } 5988 5989 @Override 5990 public ExpandableView getChildAtRawPosition(float touchX, float touchY) { 5991 return NotificationStackScrollLayout.this.getChildAtRawPosition(touchX, touchY); 5992 } 5993 5994 @Override 5995 public boolean canChildBeExpanded(View v) { 5996 return v instanceof ExpandableNotificationRow 5997 && ((ExpandableNotificationRow) v).isExpandable() 5998 && !((ExpandableNotificationRow) v).areGutsExposed() 5999 && (mIsExpanded || !((ExpandableNotificationRow) v).isPinned()); 6000 } 6001 6002 /* Only ever called as a consequence of an expansion gesture in the shade. */ 6003 @Override 6004 public void setUserExpandedChild(View v, boolean userExpanded) { 6005 if (v instanceof ExpandableNotificationRow) { 6006 ExpandableNotificationRow row = (ExpandableNotificationRow) v; 6007 if (userExpanded && onKeyguard()) { 6008 // Due to a race when locking the screen while touching, a notification may be 6009 // expanded even after we went back to keyguard. An example of this happens if 6010 // you click in the empty space while expanding a group. 6011 6012 // We also need to un-user lock it here, since otherwise the content height 6013 // calculated might be wrong. We also can't invert the two calls since 6014 // un-userlocking it will trigger a layout switch in the content view. 6015 row.setUserLocked(false); 6016 updateContentHeight(); 6017 notifyHeightChangeListener(row); 6018 return; 6019 } 6020 row.setUserExpanded(userExpanded, true /* allowChildrenExpansion */); 6021 row.onExpandedByGesture(userExpanded); 6022 } 6023 } 6024 6025 @Override 6026 public void setExpansionCancelled(View v) { 6027 if (v instanceof ExpandableNotificationRow) { 6028 ((ExpandableNotificationRow) v).setGroupExpansionChanging(false); 6029 } 6030 } 6031 6032 @Override 6033 public void setUserLockedChild(View v, boolean userLocked) { 6034 if (v instanceof ExpandableNotificationRow) { 6035 ((ExpandableNotificationRow) v).setUserLocked(userLocked); 6036 } 6037 cancelLongPress(); 6038 requestDisallowInterceptTouchEvent(true); 6039 } 6040 6041 @Override 6042 public void expansionStateChanged(boolean isExpanding) { 6043 mExpandingNotification = isExpanding; 6044 if (!mExpandedInThisMotion) { 6045 mMaxScrollAfterExpand = mOwnScrollY; 6046 mExpandedInThisMotion = true; 6047 } 6048 } 6049 6050 @Override 6051 public int getMaxExpandHeight(ExpandableView view) { 6052 return view.getMaxContentHeight(); 6053 } 6054 }; 6055 6056 public ExpandHelper.Callback getExpandHelperCallback() { 6057 return mExpandHelperCallback; 6058 } 6059 6060 /** Enum for selecting some or all notification rows (does not included non-notif views). */ 6061 @Retention(SOURCE) 6062 @IntDef({ROWS_ALL, ROWS_HIGH_PRIORITY, ROWS_GENTLE}) 6063 @interface SelectedRows {} 6064 /** All rows representing notifs. */ 6065 public static final int ROWS_ALL = 0; 6066 /** Only rows where entry.isHighPriority() is true. */ 6067 public static final int ROWS_HIGH_PRIORITY = 1; 6068 /** Only rows where entry.isHighPriority() is false. */ 6069 public static final int ROWS_GENTLE = 2; 6070 6071 interface KeyguardBypassEnabledProvider { 6072 boolean getBypassEnabled(); 6073 } 6074 6075 interface DismissListener { 6076 void onDismiss(@SelectedRows int selectedRows); 6077 } 6078 6079 interface FooterDismissListener { 6080 void onDismiss(); 6081 } 6082 6083 interface DismissAllAnimationListener { 6084 void onAnimationEnd( 6085 List<ExpandableNotificationRow> viewsToRemove, @SelectedRows int selectedRows); 6086 } 6087 } 6088