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