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