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