OSDN Git Service

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