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