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