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