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