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