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