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