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