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