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