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