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.stack;
18 
19 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator
20         .ExpandAnimationParameters;
21 
22 import android.animation.Animator;
23 import android.animation.AnimatorListenerAdapter;
24 import android.animation.ObjectAnimator;
25 import android.animation.PropertyValuesHolder;
26 import android.animation.TimeAnimator;
27 import android.animation.ValueAnimator;
28 import android.animation.ValueAnimator.AnimatorUpdateListener;
29 import android.annotation.FloatRange;
30 import android.annotation.Nullable;
31 import android.content.Context;
32 import android.content.res.Configuration;
33 import android.content.res.Resources;
34 import android.graphics.Canvas;
35 import android.graphics.Color;
36 import android.graphics.Paint;
37 import android.graphics.Path;
38 import android.graphics.PointF;
39 import android.graphics.PorterDuff;
40 import android.graphics.PorterDuffXfermode;
41 import android.graphics.Rect;
42 import android.os.Bundle;
43 import android.os.Handler;
44 import android.service.notification.StatusBarNotification;
45 import android.support.annotation.NonNull;
46 import android.support.annotation.VisibleForTesting;
47 import android.support.v4.graphics.ColorUtils;
48 import android.util.AttributeSet;
49 import android.util.FloatProperty;
50 import android.util.Log;
51 import android.util.MathUtils;
52 import android.util.Pair;
53 import android.util.Property;
54 import android.view.ContextThemeWrapper;
55 import android.view.InputDevice;
56 import android.view.MotionEvent;
57 import android.view.VelocityTracker;
58 import android.view.View;
59 import android.view.ViewConfiguration;
60 import android.view.ViewGroup;
61 import android.view.ViewTreeObserver;
62 import android.view.WindowInsets;
63 import android.view.accessibility.AccessibilityEvent;
64 import android.view.accessibility.AccessibilityNodeInfo;
65 import android.view.animation.AnimationUtils;
66 import android.view.animation.Interpolator;
67 import android.widget.OverScroller;
68 import android.widget.ScrollView;
69 
70 import com.android.internal.logging.MetricsLogger;
71 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
72 import com.android.keyguard.KeyguardSliceView;
73 import com.android.settingslib.Utils;
74 import com.android.systemui.Dependency;
75 import com.android.systemui.ExpandHelper;
76 import com.android.systemui.Interpolators;
77 import com.android.systemui.R;
78 import com.android.systemui.SwipeHelper;
79 import com.android.systemui.classifier.FalsingManager;
80 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
81 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
82 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
83 import com.android.systemui.statusbar.ActivatableNotificationView;
84 import com.android.systemui.statusbar.EmptyShadeView;
85 import com.android.systemui.statusbar.ExpandableNotificationRow;
86 import com.android.systemui.statusbar.ExpandableView;
87 import com.android.systemui.statusbar.FooterView;
88 import com.android.systemui.statusbar.NotificationBlockingHelperManager;
89 import com.android.systemui.statusbar.NotificationData;
90 import com.android.systemui.statusbar.NotificationGuts;
91 import com.android.systemui.statusbar.NotificationListContainer;
92 import com.android.systemui.statusbar.NotificationLogger;
93 import com.android.systemui.statusbar.NotificationShelf;
94 import com.android.systemui.statusbar.NotificationSnooze;
95 import com.android.systemui.statusbar.StackScrollerDecorView;
96 import com.android.systemui.statusbar.StatusBarState;
97 import com.android.systemui.statusbar.notification.FakeShadowView;
98 import com.android.systemui.statusbar.notification.NotificationUtils;
99 import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
100 import com.android.systemui.statusbar.phone.DozeParameters;
101 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
102 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
103 import com.android.systemui.statusbar.phone.NotificationGroupManager;
104 import com.android.systemui.statusbar.phone.ScrimController;
105 import com.android.systemui.statusbar.phone.StatusBar;
106 import com.android.systemui.statusbar.policy.HeadsUpUtil;
107 import com.android.systemui.statusbar.policy.ScrollAdapter;
108 
109 import java.io.FileDescriptor;
110 import java.io.PrintWriter;
111 import java.util.ArrayList;
112 import java.util.Collections;
113 import java.util.Comparator;
114 import java.util.HashSet;
115 import java.util.List;
116 import java.util.function.BiConsumer;
117 
118 /**
119  * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
120  */
121 public class NotificationStackScrollLayout extends ViewGroup
122         implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
123         ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
124         NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider,
125         NotificationListContainer {
126 
127     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
128     private static final String TAG = "StackScroller";
129     private static final boolean DEBUG = false;
130     private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
131     private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
132     private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
133     /**
134      * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
135      */
136     private static final int INVALID_POINTER = -1;
137 
138     private ExpandHelper mExpandHelper;
139     private NotificationSwipeHelper mSwipeHelper;
140     private boolean mSwipingInProgress;
141     private int mCurrentStackHeight = Integer.MAX_VALUE;
142     private final Paint mBackgroundPaint = new Paint();
143     private final Path mBackgroundPath = new Path();
144     private final boolean mShouldDrawNotificationBackground;
145 
146     private float mExpandedHeight;
147     private int mOwnScrollY;
148     private int mMaxLayoutHeight;
149 
150     private VelocityTracker mVelocityTracker;
151     private OverScroller mScroller;
152     private Runnable mFinishScrollingCallback;
153     private int mTouchSlop;
154     private int mMinimumVelocity;
155     private int mMaximumVelocity;
156     private int mOverflingDistance;
157     private float mMaxOverScroll;
158     private boolean mIsBeingDragged;
159     private int mLastMotionY;
160     private int mDownX;
161     private int mActivePointerId = INVALID_POINTER;
162     private boolean mTouchIsClick;
163     private float mInitialTouchX;
164     private float mInitialTouchY;
165 
166     private Paint mDebugPaint;
167     private int mContentHeight;
168     private int mIntrinsicContentHeight;
169     private int mCollapsedSize;
170     private int mPaddingBetweenElements;
171     private int mIncreasedPaddingBetweenElements;
172     private int mMaxTopPadding;
173     private int mRegularTopPadding;
174     private int mDarkTopPadding;
175     // Current padding, will be either mRegularTopPadding or mDarkTopPadding
176     private int mTopPadding;
177     // Distance between AOD separator and shelf
178     private int mDarkSeparatorPadding;
179     private int mBottomMargin;
180     private int mBottomInset = 0;
181     private float mQsExpansionFraction;
182 
183     /**
184      * The algorithm which calculates the properties for our children
185      */
186     protected final StackScrollAlgorithm mStackScrollAlgorithm;
187 
188     /**
189      * The current State this Layout is in
190      */
191     private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
192     private final AmbientState mAmbientState;
193     private NotificationGroupManager mGroupManager;
194     private HashSet<View> mChildrenToAddAnimated = new HashSet<>();
195     private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
196     private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
197     private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
198     private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>();
199     private ArrayList<View> mChildrenChangingPositions = new ArrayList<>();
200     private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
201     private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
202     private ArrayList<View> mSwipedOutViews = new ArrayList<>();
203     private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
204     private boolean mAnimationsEnabled;
205     private boolean mChangePositionInProgress;
206     private boolean mChildTransferInProgress;
207 
208     /**
209      * The raw amount of the overScroll on the top, which is not rubber-banded.
210      */
211     private float mOverScrolledTopPixels;
212 
213     /**
214      * The raw amount of the overScroll on the bottom, which is not rubber-banded.
215      */
216     private float mOverScrolledBottomPixels;
217     private NotificationLogger.OnChildLocationsChangedListener mListener;
218     private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
219     private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
220     private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
221     private boolean mNeedsAnimation;
222     private boolean mTopPaddingNeedsAnimation;
223     private boolean mDimmedNeedsAnimation;
224     private boolean mHideSensitiveNeedsAnimation;
225     private boolean mDarkNeedsAnimation;
226     private int mDarkAnimationOriginIndex;
227     private boolean mActivateNeedsAnimation;
228     private boolean mGoToFullShadeNeedsAnimation;
229     private boolean mIsExpanded = true;
230     private boolean mChildrenUpdateRequested;
231     private boolean mIsExpansionChanging;
232     private boolean mPanelTracking;
233     private boolean mExpandingNotification;
234     private boolean mExpandedInThisMotion;
235     private boolean mShouldShowShelfOnly;
236     protected boolean mScrollingEnabled;
237     protected FooterView mFooterView;
238     protected EmptyShadeView mEmptyShadeView;
239     private boolean mDismissAllInProgress;
240     private boolean mFadeNotificationsOnDismiss;
241 
242     /**
243      * Was the scroller scrolled to the top when the down motion was observed?
244      */
245     private boolean mScrolledToTopOnFirstDown;
246     /**
247      * The minimal amount of over scroll which is needed in order to switch to the quick settings
248      * when over scrolling on a expanded card.
249      */
250     private float mMinTopOverScrollToEscape;
251     private int mIntrinsicPadding;
252     private float mStackTranslation;
253     private float mTopPaddingOverflow;
254     private boolean mDontReportNextOverScroll;
255     private boolean mDontClampNextScroll;
256     private boolean mNeedViewResizeAnimation;
257     private View mExpandedGroupView;
258     private boolean mEverythingNeedsAnimation;
259 
260     /**
261      * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
262      * This is needed to avoid scrolling too far after the notification was collapsed in the same
263      * motion.
264      */
265     private int mMaxScrollAfterExpand;
266     private ExpandableNotificationRow.LongPressListener mLongPressListener;
267 
268     private NotificationMenuRowPlugin mCurrMenuRow;
269     private View mTranslatingParentView;
270     private View mMenuExposedView;
271     boolean mCheckForLeavebehind;
272 
273     /**
274      * Should in this touch motion only be scrolling allowed? It's true when the scroller was
275      * animating.
276      */
277     private boolean mOnlyScrollingInThisMotion;
278     private boolean mDisallowDismissInThisMotion;
279     private boolean mDisallowScrollingInThisMotion;
280     private long mGoToFullShadeDelay;
281     private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
282             = new ViewTreeObserver.OnPreDrawListener() {
283         @Override
284         public boolean onPreDraw() {
285             updateForcedScroll();
286             updateChildren();
287             mChildrenUpdateRequested = false;
288             getViewTreeObserver().removeOnPreDrawListener(this);
289             return true;
290         }
291     };
292     private StatusBar mStatusBar;
293     private int[] mTempInt2 = new int[2];
294     private boolean mGenerateChildOrderChangedEvent;
295     private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
296     private HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>();
297     private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
298             = new HashSet<>();
299     private HeadsUpManagerPhone mHeadsUpManager;
300     private NotificationRoundnessManager mRoundnessManager = new NotificationRoundnessManager();
301     private boolean mTrackingHeadsUp;
302     private ScrimController mScrimController;
303     private boolean mForceNoOverlappingRendering;
304     private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
305     private FalsingManager mFalsingManager;
306     private boolean mAnimationRunning;
307     private ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater
308             = new ViewTreeObserver.OnPreDrawListener() {
309         @Override
310         public boolean onPreDraw() {
311             onPreDrawDuringAnimation();
312             return true;
313         }
314     };
315     private Rect mBackgroundBounds = new Rect();
316     private Rect mStartAnimationRect = new Rect();
317     private Rect mEndAnimationRect = new Rect();
318     private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
319     private boolean mAnimateNextBackgroundBottom;
320     private boolean mAnimateNextBackgroundTop;
321     private ObjectAnimator mBottomAnimator = null;
322     private ObjectAnimator mTopAnimator = null;
323     private ActivatableNotificationView mFirstVisibleBackgroundChild = null;
324     private ActivatableNotificationView mLastVisibleBackgroundChild = null;
325     private int mBgColor;
326     private float mDimAmount;
327     private ValueAnimator mDimAnimator;
328     private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
329     private Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
330         @Override
331         public void onAnimationEnd(Animator animation) {
332             mDimAnimator = null;
333         }
334     };
335     private ValueAnimator.AnimatorUpdateListener mDimUpdateListener
336             = new ValueAnimator.AnimatorUpdateListener() {
337 
338         @Override
339         public void onAnimationUpdate(ValueAnimator animation) {
340             setDimAmount((Float) animation.getAnimatedValue());
341         }
342     };
343     protected ViewGroup mQsContainer;
344     private boolean mContinuousShadowUpdate;
345     private ViewTreeObserver.OnPreDrawListener mShadowUpdater
346             = new ViewTreeObserver.OnPreDrawListener() {
347 
348         @Override
349         public boolean onPreDraw() {
350             updateViewShadows();
351             return true;
352         }
353     };
354     private Comparator<ExpandableView> mViewPositionComparator = new Comparator<ExpandableView>() {
355         @Override
356         public int compare(ExpandableView view, ExpandableView otherView) {
357             float endY = view.getTranslationY() + view.getActualHeight();
358             float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
359             if (endY < otherEndY) {
360                 return -1;
361             } else if (endY > otherEndY) {
362                 return 1;
363             } else {
364                 // The two notifications end at the same location
365                 return 0;
366             }
367         }
368     };
369     private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
370     private boolean mPulsing;
371     private boolean mDrawBackgroundAsSrc;
372     private boolean mFadingOut;
373     private boolean mParentNotFullyVisible;
374     private boolean mGroupExpandedForMeasure;
375     private boolean mScrollable;
376     private View mForcedScroll;
377     private View mNeedingPulseAnimation;
378     private float mDarkAmount = 0f;
379     private static final Property<NotificationStackScrollLayout, Float> DARK_AMOUNT =
380             new FloatProperty<NotificationStackScrollLayout>("darkAmount") {
381                 @Override
382                 public void setValue(NotificationStackScrollLayout object, float value) {
383                     object.setDarkAmount(value);
384                 }
385 
386                 @Override
387                 public Float get(NotificationStackScrollLayout object) {
388                     return object.getDarkAmount();
389                 }
390             };
391     private ObjectAnimator mDarkAmountAnimator;
392     private boolean mUsingLightTheme;
393     private boolean mQsExpanded;
394     private boolean mForwardScrollable;
395     private boolean mBackwardScrollable;
396     private NotificationShelf mShelf;
397     private int mMaxDisplayedNotifications = -1;
398     private int mStatusBarHeight;
399     private int mMinInteractionHeight;
400     private boolean mNoAmbient;
401     private final Rect mClipRect = new Rect();
402     private boolean mIsClipped;
403     private Rect mRequestedClipBounds;
404     private boolean mInHeadsUpPinnedMode;
405     private boolean mHeadsUpAnimatingAway;
406     private int mStatusBarState;
407     private int mCachedBackgroundColor;
408     private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
409     private Runnable mAnimateScroll = this::animateScroll;
410     private int mCornerRadius;
411     private int mSidePaddings;
412     private final int mSeparatorWidth;
413     private final int mSeparatorThickness;
414     private final Rect mBackgroundAnimationRect = new Rect();
415     private int mAntiBurnInOffsetX;
416     private ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>();
417     private int mHeadsUpInset;
418     private HeadsUpAppearanceController mHeadsUpAppearanceController;
419 
NotificationStackScrollLayout(Context context)420     public NotificationStackScrollLayout(Context context) {
421         this(context, null);
422     }
423 
NotificationStackScrollLayout(Context context, AttributeSet attrs)424     public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
425         this(context, attrs, 0);
426     }
427 
NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr)428     public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
429         this(context, attrs, defStyleAttr, 0);
430     }
431 
NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)432     public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
433             int defStyleRes) {
434         super(context, attrs, defStyleAttr, defStyleRes);
435         Resources res = getResources();
436 
437         mAmbientState = new AmbientState(context);
438         mBgColor = context.getColor(R.color.notification_shade_background_color);
439         int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
440         int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
441         mExpandHelper = new ExpandHelper(getContext(), this,
442                 minHeight, maxHeight);
443         mExpandHelper.setEventSource(this);
444         mExpandHelper.setScrollAdapter(this);
445         mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext());
446         mStackScrollAlgorithm = createStackScrollAlgorithm(context);
447         initView(context);
448         mFalsingManager = FalsingManager.getInstance(context);
449         mShouldDrawNotificationBackground =
450                 res.getBoolean(R.bool.config_drawNotificationBackground);
451         mFadeNotificationsOnDismiss =
452                 res.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
453         mSeparatorWidth = res.getDimensionPixelSize(R.dimen.widget_separator_width);
454         mSeparatorThickness = res.getDimensionPixelSize(R.dimen.widget_separator_thickness);
455         mDarkSeparatorPadding = res.getDimensionPixelSize(R.dimen.widget_bottom_separator_padding);
456         mRoundnessManager.setAnimatedChildren(mChildrenToAddAnimated);
457         mRoundnessManager.setOnRoundingChangedCallback(this::invalidate);
458         addOnExpandedHeightListener(mRoundnessManager::setExpanded);
459 
460         // Blocking helper manager wants to know the expanded state, update as well.
461         NotificationBlockingHelperManager blockingHelperManager =
462                 Dependency.get(NotificationBlockingHelperManager.class);
463         addOnExpandedHeightListener((height, unused) -> {
464             blockingHelperManager.setNotificationShadeExpanded(height);
465         });
466 
467         updateWillNotDraw();
468         mBackgroundPaint.setAntiAlias(true);
469         if (DEBUG) {
470             mDebugPaint = new Paint();
471             mDebugPaint.setColor(0xffff0000);
472             mDebugPaint.setStrokeWidth(2);
473             mDebugPaint.setStyle(Paint.Style.STROKE);
474         }
475     }
476 
477     @Override
getSwipeActionHelper()478     public NotificationSwipeActionHelper getSwipeActionHelper() {
479         return mSwipeHelper;
480     }
481 
482     @Override
onMenuClicked(View view, int x, int y, MenuItem item)483     public void onMenuClicked(View view, int x, int y, MenuItem item) {
484         if (mLongPressListener == null) {
485             return;
486         }
487         if (view instanceof ExpandableNotificationRow) {
488             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
489             MetricsLogger.action(mContext, MetricsEvent.ACTION_TOUCH_GEAR,
490                     row.getStatusBarNotification().getPackageName());
491         }
492         mLongPressListener.onLongPress(view, x, y, item);
493     }
494 
495     @Override
onMenuReset(View row)496     public void onMenuReset(View row) {
497         if (mTranslatingParentView != null && row == mTranslatingParentView) {
498             mMenuExposedView = null;
499             mTranslatingParentView = null;
500         }
501     }
502 
503     @Override
onMenuShown(View row)504     public void onMenuShown(View row) {
505         mMenuExposedView = mTranslatingParentView;
506         if (row instanceof ExpandableNotificationRow) {
507             MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR,
508                     ((ExpandableNotificationRow) row).getStatusBarNotification()
509                             .getPackageName());
510         }
511         mSwipeHelper.onMenuShown(row);
512     }
513 
onDraw(Canvas canvas)514     protected void onDraw(Canvas canvas) {
515         if (mShouldDrawNotificationBackground
516                 && (mCurrentBounds.top < mCurrentBounds.bottom || mAmbientState.isDark())) {
517             drawBackground(canvas);
518         }
519 
520         if (DEBUG) {
521             int y = mTopPadding;
522             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
523             y = getLayoutHeight();
524             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
525             y = getHeight() - getEmptyBottomMargin();
526             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
527         }
528     }
529 
drawBackground(Canvas canvas)530     private void drawBackground(Canvas canvas) {
531         final int lockScreenLeft = mSidePaddings;
532         final int lockScreenRight = getWidth() - mSidePaddings;
533         final int lockScreenTop = mCurrentBounds.top;
534         final int lockScreenBottom = mCurrentBounds.bottom;
535         final int darkLeft = getWidth() / 2 - mSeparatorWidth / 2;
536         final int darkRight = darkLeft + mSeparatorWidth;
537         final int darkTop = (int) (mRegularTopPadding + mSeparatorThickness / 2f);
538         final int darkBottom = darkTop + mSeparatorThickness;
539 
540         if (mAmbientState.hasPulsingNotifications()) {
541             // No divider, we have a notification icon instead
542         } else if (mAmbientState.isFullyDark()) {
543             // Only draw divider on AOD if we actually have notifications
544             if (mFirstVisibleBackgroundChild != null) {
545                 canvas.drawRect(darkLeft, darkTop, darkRight, darkBottom, mBackgroundPaint);
546             }
547         } else {
548             float inverseDark = 1 - mDarkAmount;
549             float yProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(inverseDark);
550             float xProgress = Interpolators.FAST_OUT_SLOW_IN
551                     .getInterpolation(inverseDark * 2f);
552 
553             mBackgroundAnimationRect.set(
554                     (int) MathUtils.lerp(darkLeft, lockScreenLeft, xProgress),
555                     (int) MathUtils.lerp(darkTop, lockScreenTop, yProgress),
556                     (int) MathUtils.lerp(darkRight, lockScreenRight, xProgress),
557                     (int) MathUtils.lerp(darkBottom, lockScreenBottom, yProgress));
558             if (!mAmbientState.isDark() || mFirstVisibleBackgroundChild != null) {
559                 canvas.drawRoundRect(mBackgroundAnimationRect.left, mBackgroundAnimationRect.top,
560                         mBackgroundAnimationRect.right, mBackgroundAnimationRect.bottom,
561                         mCornerRadius, mCornerRadius, mBackgroundPaint);
562             }
563         }
564         updateClipping();
565     }
566 
updateBackgroundDimming()567     private void updateBackgroundDimming() {
568         // No need to update the background color if it's not being drawn.
569         if (!mShouldDrawNotificationBackground) {
570             return;
571         }
572 
573         float alpha =
574                 BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount);
575         alpha *= 1f - mDarkAmount;
576         // We need to manually blend in the background color.
577         int scrimColor = mScrimController.getBackgroundColor();
578         int awakeColor = ColorUtils.blendARGB(scrimColor, mBgColor, alpha);
579 
580         // Interpolate between semi-transparent notification panel background color
581         // and white AOD separator.
582         float colorInterpolation = Interpolators.DECELERATE_QUINT.getInterpolation(mDarkAmount);
583         int color = ColorUtils.blendARGB(awakeColor, Color.WHITE, colorInterpolation);
584 
585         if (mCachedBackgroundColor != color) {
586             mCachedBackgroundColor = color;
587             mBackgroundPaint.setColor(color);
588             invalidate();
589         }
590     }
591 
initView(Context context)592     private void initView(Context context) {
593         mScroller = new OverScroller(getContext());
594         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
595         setClipChildren(false);
596         final ViewConfiguration configuration = ViewConfiguration.get(context);
597         mTouchSlop = configuration.getScaledTouchSlop();
598         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
599         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
600         mOverflingDistance = configuration.getScaledOverflingDistance();
601 
602         Resources res = context.getResources();
603         mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height);
604         mStackScrollAlgorithm.initView(context);
605         mAmbientState.reload(context);
606         mPaddingBetweenElements = Math.max(1,
607                 res.getDimensionPixelSize(R.dimen.notification_divider_height));
608         mIncreasedPaddingBetweenElements =
609                 res.getDimensionPixelSize(R.dimen.notification_divider_height_increased);
610         mMinTopOverScrollToEscape = res.getDimensionPixelSize(
611                 R.dimen.min_top_overscroll_to_qs);
612         mStatusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height);
613         mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
614         mSidePaddings = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
615         mMinInteractionHeight = res.getDimensionPixelSize(
616                 R.dimen.notification_min_interaction_height);
617         mCornerRadius = res.getDimensionPixelSize(
618                 Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
619         mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize(
620                 R.dimen.heads_up_status_bar_padding);
621     }
622 
setDrawBackgroundAsSrc(boolean asSrc)623     public void setDrawBackgroundAsSrc(boolean asSrc) {
624         mDrawBackgroundAsSrc = asSrc;
625         updateSrcDrawing();
626     }
627 
updateSrcDrawing()628     private void updateSrcDrawing() {
629         if (!mShouldDrawNotificationBackground) {
630             return;
631         }
632 
633         mBackgroundPaint.setXfermode(mDrawBackgroundAsSrc && !mFadingOut && !mParentNotFullyVisible
634                 ? mSrcMode : null);
635         invalidate();
636     }
637 
notifyHeightChangeListener(ExpandableView view)638     private void notifyHeightChangeListener(ExpandableView view) {
639         notifyHeightChangeListener(view, false /* needsAnimation */);
640     }
641 
notifyHeightChangeListener(ExpandableView view, boolean needsAnimation)642     private void notifyHeightChangeListener(ExpandableView view, boolean needsAnimation) {
643         if (mOnHeightChangedListener != null) {
644             mOnHeightChangedListener.onHeightChanged(view, needsAnimation);
645         }
646     }
647 
648     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)649     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
650         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
651 
652         int width = MeasureSpec.getSize(widthMeasureSpec);
653         int childWidthSpec = MeasureSpec.makeMeasureSpec(width - mSidePaddings * 2,
654                 MeasureSpec.getMode(widthMeasureSpec));
655         // We need to measure all children even the GONE ones, such that the heights are calculated
656         // correctly as they are used to calculate how many we can fit on the screen.
657         final int size = getChildCount();
658         for (int i = 0; i < size; i++) {
659             measureChild(getChildAt(i), childWidthSpec, heightMeasureSpec);
660         }
661     }
662 
663     @Override
onLayout(boolean changed, int l, int t, int r, int b)664     protected void onLayout(boolean changed, int l, int t, int r, int b) {
665         // we layout all our children centered on the top
666         float centerX = getWidth() / 2.0f;
667         for (int i = 0; i < getChildCount(); i++) {
668             View child = getChildAt(i);
669             // We need to layout all children even the GONE ones, such that the heights are
670             // calculated correctly as they are used to calculate how many we can fit on the screen
671             float width = child.getMeasuredWidth();
672             float height = child.getMeasuredHeight();
673             child.layout((int) (centerX - width / 2.0f),
674                     0,
675                     (int) (centerX + width / 2.0f),
676                     (int) height);
677         }
678         setMaxLayoutHeight(getHeight());
679         updateContentHeight();
680         clampScrollPosition();
681         requestChildrenUpdate();
682         updateFirstAndLastBackgroundViews();
683         updateAlgorithmLayoutMinHeight();
684     }
685 
requestAnimationOnViewResize(ExpandableNotificationRow row)686     private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
687         if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) {
688             mNeedViewResizeAnimation = true;
689             mNeedsAnimation = true;
690         }
691     }
692 
updateSpeedBumpIndex(int newIndex, boolean noAmbient)693     public void updateSpeedBumpIndex(int newIndex, boolean noAmbient) {
694         mAmbientState.setSpeedBumpIndex(newIndex);
695         mNoAmbient = noAmbient;
696     }
697 
698     @Override
setChildLocationsChangedListener( NotificationLogger.OnChildLocationsChangedListener listener)699     public void setChildLocationsChangedListener(
700             NotificationLogger.OnChildLocationsChangedListener listener) {
701         mListener = listener;
702     }
703 
704     @Override
isInVisibleLocation(ExpandableNotificationRow row)705     public boolean isInVisibleLocation(ExpandableNotificationRow row) {
706         ExpandableViewState childViewState = mCurrentStackScrollState.getViewStateForView(row);
707         if (childViewState == null) {
708             return false;
709         }
710         if ((childViewState.location & ExpandableViewState.VISIBLE_LOCATIONS) == 0) {
711             return false;
712         }
713         if (row.getVisibility() != View.VISIBLE) {
714             return false;
715         }
716         return true;
717     }
718 
setMaxLayoutHeight(int maxLayoutHeight)719     private void setMaxLayoutHeight(int maxLayoutHeight) {
720         mMaxLayoutHeight = maxLayoutHeight;
721         mShelf.setMaxLayoutHeight(maxLayoutHeight);
722         updateAlgorithmHeightAndPadding();
723     }
724 
updateAlgorithmHeightAndPadding()725     private void updateAlgorithmHeightAndPadding() {
726         mTopPadding = (int) MathUtils.lerp(mRegularTopPadding, mDarkTopPadding, mDarkAmount);
727         mAmbientState.setLayoutHeight(getLayoutHeight());
728         updateAlgorithmLayoutMinHeight();
729         mAmbientState.setTopPadding(mTopPadding);
730     }
731 
updateAlgorithmLayoutMinHeight()732     private void updateAlgorithmLayoutMinHeight() {
733         mAmbientState.setLayoutMinHeight(mQsExpanded || isHeadsUpTransition()
734                 ? getLayoutMinHeight() : 0);
735     }
736 
737     /**
738      * Updates the children views according to the stack scroll algorithm. Call this whenever
739      * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
740      */
updateChildren()741     private void updateChildren() {
742         updateScrollStateForAddedChildren();
743         mAmbientState.setCurrentScrollVelocity(mScroller.isFinished()
744                 ? 0
745                 : mScroller.getCurrVelocity());
746         mAmbientState.setScrollY(mOwnScrollY);
747         mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
748         if (!isCurrentlyAnimating() && !mNeedsAnimation) {
749             applyCurrentState();
750         } else {
751             startAnimationToState();
752         }
753     }
754 
onPreDrawDuringAnimation()755     private void onPreDrawDuringAnimation() {
756         mShelf.updateAppearance();
757         updateClippingToTopRoundedCorner();
758         if (!mNeedsAnimation && !mChildrenUpdateRequested) {
759             updateBackground();
760         }
761     }
762 
updateClippingToTopRoundedCorner()763     private void updateClippingToTopRoundedCorner() {
764         Float clipStart = (float) mTopPadding
765                                  + mStackTranslation
766                                  + mAmbientState.getExpandAnimationTopChange();
767         Float clipEnd = clipStart + mCornerRadius;
768         boolean first = true;
769         for (int i = 0; i < getChildCount(); i++) {
770             ExpandableView child = (ExpandableView) getChildAt(i);
771             if (child.getVisibility() == GONE) {
772                 continue;
773             }
774             float start = child.getTranslationY();
775             float end = start + child.getActualHeight();
776             boolean clip = clipStart > start && clipStart < end
777                     || clipEnd >= start && clipEnd <= end;
778             clip &= !(first && mOwnScrollY == 0);
779             child.setDistanceToTopRoundness(clip ? Math.max(start - clipStart, 0)
780                     : ExpandableView.NO_ROUNDNESS);
781             first = false;
782         }
783     }
784 
updateScrollStateForAddedChildren()785     private void updateScrollStateForAddedChildren() {
786         if (mChildrenToAddAnimated.isEmpty()) {
787             return;
788         }
789         for (int i = 0; i < getChildCount(); i++) {
790             ExpandableView child = (ExpandableView) getChildAt(i);
791             if (mChildrenToAddAnimated.contains(child)) {
792                 int startingPosition = getPositionInLinearLayout(child);
793                 float increasedPaddingAmount = child.getIncreasedPaddingAmount();
794                 int padding = increasedPaddingAmount == 1.0f ? mIncreasedPaddingBetweenElements
795                         : increasedPaddingAmount == -1.0f ? 0 : mPaddingBetweenElements;
796                 int childHeight = getIntrinsicHeight(child) + padding;
797                 if (startingPosition < mOwnScrollY) {
798                     // This child starts off screen, so let's keep it offscreen to keep the others visible
799 
800                     setOwnScrollY(mOwnScrollY + childHeight);
801                 }
802             }
803         }
804         clampScrollPosition();
805     }
806 
updateForcedScroll()807     private void updateForcedScroll() {
808         if (mForcedScroll != null && (!mForcedScroll.hasFocus()
809                 || !mForcedScroll.isAttachedToWindow())) {
810             mForcedScroll = null;
811         }
812         if (mForcedScroll != null) {
813             ExpandableView expandableView = (ExpandableView) mForcedScroll;
814             int positionInLinearLayout = getPositionInLinearLayout(expandableView);
815             int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
816             int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
817 
818             targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
819 
820             // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
821             // that it is not visible anymore.
822             if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
823                 setOwnScrollY(targetScroll);
824             }
825         }
826     }
827 
requestChildrenUpdate()828     private void requestChildrenUpdate() {
829         if (!mChildrenUpdateRequested) {
830             getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
831             mChildrenUpdateRequested = true;
832             invalidate();
833         }
834     }
835 
isCurrentlyAnimating()836     private boolean isCurrentlyAnimating() {
837         return mStateAnimator.isRunning();
838     }
839 
clampScrollPosition()840     private void clampScrollPosition() {
841         int scrollRange = getScrollRange();
842         if (scrollRange < mOwnScrollY) {
843             setOwnScrollY(scrollRange);
844         }
845     }
846 
getTopPadding()847     public int getTopPadding() {
848         return mTopPadding;
849     }
850 
setTopPadding(int topPadding, boolean animate)851     private void setTopPadding(int topPadding, boolean animate) {
852         if (mRegularTopPadding != topPadding) {
853             mRegularTopPadding = topPadding;
854             mDarkTopPadding = topPadding + mDarkSeparatorPadding;
855             mAmbientState.setDarkTopPadding(mDarkTopPadding);
856             updateAlgorithmHeightAndPadding();
857             updateContentHeight();
858             if (animate && mAnimationsEnabled && mIsExpanded) {
859                 mTopPaddingNeedsAnimation = true;
860                 mNeedsAnimation =  true;
861             }
862             requestChildrenUpdate();
863             notifyHeightChangeListener(null, animate);
864         }
865     }
866 
867     /**
868      * Update the height of the panel.
869      *
870      * @param height the expanded height of the panel
871      */
setExpandedHeight(float height)872     public void setExpandedHeight(float height) {
873         mExpandedHeight = height;
874         setIsExpanded(height > 0);
875         int minExpansionHeight = getMinExpansionHeight();
876         if (height < minExpansionHeight) {
877             mClipRect.left = 0;
878             mClipRect.right = getWidth();
879             mClipRect.top = 0;
880             mClipRect.bottom = (int) height;
881             height = minExpansionHeight;
882             setRequestedClipBounds(mClipRect);
883         } else {
884             setRequestedClipBounds(null);
885         }
886         int stackHeight;
887         float translationY;
888         float appearEndPosition = getAppearEndPosition();
889         float appearStartPosition = getAppearStartPosition();
890         float appearFraction = 1.0f;
891         boolean appearing = height < appearEndPosition;
892         mAmbientState.setAppearing(appearing);
893         if (!appearing) {
894             translationY = 0;
895             if (mShouldShowShelfOnly) {
896                 stackHeight = mTopPadding + mShelf.getIntrinsicHeight();
897             } else if (mQsExpanded) {
898                 int stackStartPosition = mContentHeight - mTopPadding + mIntrinsicPadding;
899                 int stackEndPosition = mMaxTopPadding + mShelf.getIntrinsicHeight();
900                 if (stackStartPosition <= stackEndPosition) {
901                     stackHeight = stackEndPosition;
902                 } else {
903                     stackHeight = (int) NotificationUtils.interpolate(stackStartPosition,
904                             stackEndPosition, mQsExpansionFraction);
905                 }
906             } else {
907                 stackHeight = (int) height;
908             }
909         } else {
910             appearFraction = getAppearFraction(height);
911             if (appearFraction >= 0) {
912                 translationY = NotificationUtils.interpolate(getExpandTranslationStart(), 0,
913                         appearFraction);
914             } else {
915                 // This may happen when pushing up a heads up. We linearly push it up from the
916                 // start
917                 translationY = height - appearStartPosition + getExpandTranslationStart();
918             }
919             if (isHeadsUpTransition()) {
920                 stackHeight = mFirstVisibleBackgroundChild.getPinnedHeadsUpHeight();
921                 translationY = MathUtils.lerp(mHeadsUpInset - mTopPadding, 0, appearFraction);
922             } else {
923                 stackHeight = (int) (height - translationY);
924             }
925         }
926         if (stackHeight != mCurrentStackHeight) {
927             mCurrentStackHeight = stackHeight;
928             updateAlgorithmHeightAndPadding();
929             requestChildrenUpdate();
930         }
931         setStackTranslation(translationY);
932         for (int i = 0; i < mExpandedHeightListeners.size(); i++) {
933             BiConsumer<Float, Float> listener = mExpandedHeightListeners.get(i);
934             listener.accept(mExpandedHeight, appearFraction);
935         }
936     }
937 
938     private void setRequestedClipBounds(Rect clipRect) {
939         mRequestedClipBounds = clipRect;
940         updateClipping();
941     }
942 
943     /**
944      * Return the height of the content ignoring the footer.
945      */
946     public int getIntrinsicContentHeight() {
947         return mIntrinsicContentHeight;
948     }
949 
950     public void updateClipping() {
951         boolean animatingClipping = mDarkAmount > 0 && mDarkAmount < 1;
952         boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode
953                 && !mHeadsUpAnimatingAway;
954         if (mIsClipped != clipped) {
955             mIsClipped = clipped;
956             updateFadingState();
957         }
958 
959         if (animatingClipping) {
960             setClipBounds(mBackgroundAnimationRect);
961         } else if (clipped) {
962             setClipBounds(mRequestedClipBounds);
963         } else {
964             setClipBounds(null);
965         }
966     }
967 
968     /**
969      * @return The translation at the beginning when expanding.
970      *         Measured relative to the resting position.
971      */
972     private float getExpandTranslationStart() {
973         return - mTopPadding;
974     }
975 
976     /**
977      * @return the position from where the appear transition starts when expanding.
978      *         Measured in absolute height.
979      */
980     private float getAppearStartPosition() {
981         if (isHeadsUpTransition()) {
982             return mHeadsUpInset + mFirstVisibleBackgroundChild.getPinnedHeadsUpHeight();
983         }
984         return getMinExpansionHeight();
985     }
986 
987     /**
988      * @return the height of the top heads up notification when pinned. This is different from the
989      *         intrinsic height, which also includes whether the notification is system expanded and
990      *         is mainly used when dragging down from a heads up notification.
991      */
992     private int getTopHeadsUpPinnedHeight() {
993         NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry();
994         if (topEntry == null) {
995             return 0;
996         }
997         ExpandableNotificationRow row = topEntry.row;
998         if (row.isChildInGroup()) {
999             final ExpandableNotificationRow groupSummary
1000                     = mGroupManager.getGroupSummary(row.getStatusBarNotification());
1001             if (groupSummary != null) {
1002                 row = groupSummary;
1003             }
1004         }
1005         return row.getPinnedHeadsUpHeight();
1006     }
1007 
1008     /**
1009      * @return the position from where the appear transition ends when expanding.
1010      *         Measured in absolute height.
1011      */
1012     private float getAppearEndPosition() {
1013         int appearPosition;
1014         int notGoneChildCount = getNotGoneChildCount();
1015         if (mEmptyShadeView.getVisibility() == GONE && notGoneChildCount != 0) {
1016             if (isHeadsUpTransition()
1017                     || (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDark())) {
1018                 appearPosition = getTopHeadsUpPinnedHeight();
1019             } else {
1020                 appearPosition = 0;
1021                 if (notGoneChildCount >= 1 && mShelf.getVisibility() != GONE) {
1022                     appearPosition += mShelf.getIntrinsicHeight();
1023                 }
1024             }
1025         } else {
1026             appearPosition = mEmptyShadeView.getHeight();
1027         }
1028         return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding);
1029     }
1030 
1031     private boolean isHeadsUpTransition() {
1032         return mTrackingHeadsUp && mFirstVisibleBackgroundChild != null
1033                 && mAmbientState.isAboveShelf(mFirstVisibleBackgroundChild);
1034     }
1035 
1036     /**
1037      * @param height the height of the panel
1038      * @return the fraction of the appear animation that has been performed
1039      */
1040     public float getAppearFraction(float height) {
1041         float appearEndPosition = getAppearEndPosition();
1042         float appearStartPosition = getAppearStartPosition();
1043         return (height - appearStartPosition)
1044                 / (appearEndPosition - appearStartPosition);
1045     }
1046 
1047     public float getStackTranslation() {
1048         return mStackTranslation;
1049     }
1050 
1051     private void setStackTranslation(float stackTranslation) {
1052         if (stackTranslation != mStackTranslation) {
1053             mStackTranslation = stackTranslation;
1054             mAmbientState.setStackTranslation(stackTranslation);
1055             requestChildrenUpdate();
1056         }
1057     }
1058 
1059     /**
1060      * Get the current height of the view. This is at most the msize of the view given by a the
1061      * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
1062      *
1063      * @return either the layout height or the externally defined height, whichever is smaller
1064      */
1065     private int getLayoutHeight() {
1066         return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
1067     }
1068 
1069     public int getFirstItemMinHeight() {
1070         final ExpandableView firstChild = getFirstChildNotGone();
1071         return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
1072     }
1073 
1074     public void setLongPressListener(ExpandableNotificationRow.LongPressListener listener) {
1075         mLongPressListener = listener;
1076     }
1077 
1078     public void setQsContainer(ViewGroup qsContainer) {
1079         mQsContainer = qsContainer;
1080     }
1081 
1082     /**
1083      * Handles cleanup after the given {@code view} has been fully swiped out (including
1084      * re-invoking dismiss logic in case the notification has not made its way out yet).
1085      */
1086     @Override
1087     public void onChildDismissed(View view) {
1088         ExpandableNotificationRow row = (ExpandableNotificationRow) view;
1089         if (!row.isDismissed()) {
1090             handleChildViewDismissed(view);
1091         }
1092         ViewGroup transientContainer = row.getTransientContainer();
1093         if (transientContainer != null) {
1094             transientContainer.removeTransientView(view);
1095         }
1096     }
1097 
1098     /**
1099      * Starts up notification dismiss and tells the notification, if any, to remove itself from
1100      * layout.
1101      *
1102      * @param view view (e.g. notification) to dismiss from the layout
1103      */
1104     private void handleChildViewDismissed(View view) {
1105         if (mDismissAllInProgress) {
1106             return;
1107         }
1108 
1109         boolean isBlockingHelperShown = false;
1110 
1111         setSwipingInProgress(false);
1112         if (mDragAnimPendingChildren.contains(view)) {
1113             // We start the swipe and finish it in the same frame; we don't want a drag animation.
1114             mDragAnimPendingChildren.remove(view);
1115         }
1116         mAmbientState.onDragFinished(view);
1117         updateContinuousShadowDrawing();
1118 
1119         if (view instanceof ExpandableNotificationRow) {
1120             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
1121             if (row.isHeadsUp()) {
1122                 mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
1123             }
1124             isBlockingHelperShown =
1125                     row.performDismissWithBlockingHelper(false /* fromAccessibility */);
1126         }
1127 
1128         if (!isBlockingHelperShown) {
1129             mSwipedOutViews.add(view);
1130         }
1131         mFalsingManager.onNotificationDismissed();
1132         if (mFalsingManager.shouldEnforceBouncer()) {
1133             mStatusBar.executeRunnableDismissingKeyguard(
1134                     null,
1135                     null /* cancelAction */,
1136                     false /* dismissShade */,
1137                     true /* afterKeyguardGone */,
1138                     false /* deferred */);
1139         }
1140     }
1141 
1142     @Override
1143     public void onChildSnappedBack(View animView, float targetLeft) {
1144         mAmbientState.onDragFinished(animView);
1145         updateContinuousShadowDrawing();
1146         if (!mDragAnimPendingChildren.contains(animView)) {
1147             if (mAnimationsEnabled) {
1148                 mSnappedBackChildren.add(animView);
1149                 mNeedsAnimation = true;
1150             }
1151             requestChildrenUpdate();
1152         } else {
1153             // We start the swipe and snap back in the same frame, we don't want any animation
1154             mDragAnimPendingChildren.remove(animView);
1155         }
1156         if (mCurrMenuRow != null && targetLeft == 0) {
1157             mCurrMenuRow.resetMenu();
1158             mCurrMenuRow = null;
1159         }
1160     }
1161 
1162     @Override
1163     public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
1164         // Returning true prevents alpha fading.
1165         return !mFadeNotificationsOnDismiss;
1166     }
1167 
1168     @Override
1169     public void onBeginDrag(View v) {
1170         mFalsingManager.onNotificatonStartDismissing();
1171         setSwipingInProgress(true);
1172         mAmbientState.onBeginDrag(v);
1173         updateContinuousShadowDrawing();
1174         if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
1175             mDragAnimPendingChildren.add(v);
1176             mNeedsAnimation = true;
1177         }
1178         requestChildrenUpdate();
1179     }
1180 
1181     public static boolean isPinnedHeadsUp(View v) {
1182         if (v instanceof ExpandableNotificationRow) {
1183             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
1184             return row.isHeadsUp() && row.isPinned();
1185         }
1186         return false;
1187     }
1188 
1189     private boolean isHeadsUp(View v) {
1190         if (v instanceof ExpandableNotificationRow) {
1191             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
1192             return row.isHeadsUp();
1193         }
1194         return false;
1195     }
1196 
1197     @Override
1198     public void onDragCancelled(View v) {
1199         mFalsingManager.onNotificatonStopDismissing();
1200         setSwipingInProgress(false);
1201     }
1202 
1203     @Override
1204     public float getFalsingThresholdFactor() {
1205         return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
1206     }
1207 
1208     @Override
1209     public View getChildAtPosition(MotionEvent ev) {
1210         View child = getChildAtPosition(ev.getX(), ev.getY());
1211         if (child instanceof ExpandableNotificationRow) {
1212             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1213             ExpandableNotificationRow parent = row.getNotificationParent();
1214             if (parent != null && parent.areChildrenExpanded()
1215                     && (parent.areGutsExposed()
1216                             || mMenuExposedView == parent
1217                         || (parent.getNotificationChildren().size() == 1
1218                                 && parent.isClearable()))) {
1219                 // In this case the group is expanded and showing the menu for the
1220                 // group, further interaction should apply to the group, not any
1221                 // child notifications so we use the parent of the child. We also do the same
1222                 // if we only have a single child.
1223                 child = parent;
1224             }
1225         }
1226         return child;
1227     }
1228 
1229     public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
1230         getLocationOnScreen(mTempInt2);
1231         float localTouchY = touchY - mTempInt2[1];
1232 
1233         ExpandableView closestChild = null;
1234         float minDist = Float.MAX_VALUE;
1235 
1236         // find the view closest to the location, accounting for GONE views
1237         final int count = getChildCount();
1238         for (int childIdx = 0; childIdx < count; childIdx++) {
1239             ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
1240             if (slidingChild.getVisibility() == GONE
1241                     || slidingChild instanceof StackScrollerDecorView) {
1242                 continue;
1243             }
1244             float childTop = slidingChild.getTranslationY();
1245             float top = childTop + slidingChild.getClipTopAmount();
1246             float bottom = childTop + slidingChild.getActualHeight()
1247                     - slidingChild.getClipBottomAmount();
1248 
1249             float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
1250             if (dist < minDist) {
1251                 closestChild = slidingChild;
1252                 minDist = dist;
1253             }
1254         }
1255         return closestChild;
1256     }
1257 
1258     @Override
1259     public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
1260         getLocationOnScreen(mTempInt2);
1261         return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
1262     }
1263 
1264     @Override
1265     public ExpandableView getChildAtPosition(float touchX, float touchY) {
1266         return getChildAtPosition(touchX, touchY, true /* requireMinHeight */);
1267 
1268     }
1269 
1270     /**
1271      * Get the child at a certain screen location.
1272      *
1273      * @param touchX the x coordinate
1274      * @param touchY the y coordinate
1275      * @param requireMinHeight Whether a minimum height is required for a child to be returned.
1276      * @return the child at the given location.
1277      */
1278     private ExpandableView getChildAtPosition(float touchX, float touchY,
1279             boolean requireMinHeight) {
1280         // find the view under the pointer, accounting for GONE views
1281         final int count = getChildCount();
1282         for (int childIdx = 0; childIdx < count; childIdx++) {
1283             ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
1284             if (slidingChild.getVisibility() != VISIBLE
1285                     || slidingChild instanceof StackScrollerDecorView) {
1286                 continue;
1287             }
1288             float childTop = slidingChild.getTranslationY();
1289             float top = childTop + slidingChild.getClipTopAmount();
1290             float bottom = childTop + slidingChild.getActualHeight()
1291                     - slidingChild.getClipBottomAmount();
1292 
1293             // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
1294             // camera affordance).
1295             int left = 0;
1296             int right = getWidth();
1297 
1298             if ((bottom - top >= mMinInteractionHeight || !requireMinHeight)
1299                     && touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
1300                 if (slidingChild instanceof ExpandableNotificationRow) {
1301                     ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
1302                     if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
1303                             && mHeadsUpManager.getTopEntry().row != row
1304                             && mGroupManager.getGroupSummary(
1305                                 mHeadsUpManager.getTopEntry().row.getStatusBarNotification())
1306                                 != row) {
1307                         continue;
1308                     }
1309                     return row.getViewAtPosition(touchY - childTop);
1310                 }
1311                 return slidingChild;
1312             }
1313         }
1314         return null;
1315     }
1316 
1317     @Override
1318     public boolean canChildBeExpanded(View v) {
1319         return v instanceof ExpandableNotificationRow
1320                 && ((ExpandableNotificationRow) v).isExpandable()
1321                 && !((ExpandableNotificationRow) v).areGutsExposed()
1322                 && (mIsExpanded || !((ExpandableNotificationRow) v).isPinned());
1323     }
1324 
1325     /* Only ever called as a consequence of an expansion gesture in the shade. */
1326     @Override
1327     public void setUserExpandedChild(View v, boolean userExpanded) {
1328         if (v instanceof ExpandableNotificationRow) {
1329             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
1330             if (userExpanded && onKeyguard()) {
1331                 // Due to a race when locking the screen while touching, a notification may be
1332                 // expanded even after we went back to keyguard. An example of this happens if
1333                 // you click in the empty space while expanding a group.
1334 
1335                 // We also need to un-user lock it here, since otherwise the content height
1336                 // calculated might be wrong. We also can't invert the two calls since
1337                 // un-userlocking it will trigger a layout switch in the content view.
1338                 row.setUserLocked(false);
1339                 updateContentHeight();
1340                 notifyHeightChangeListener(row);
1341                 return;
1342             }
1343             row.setUserExpanded(userExpanded, true /* allowChildrenExpansion */);
1344             row.onExpandedByGesture(userExpanded);
1345         }
1346     }
1347 
1348     @Override
1349     public void setExpansionCancelled(View v) {
1350         if (v instanceof ExpandableNotificationRow) {
1351             ((ExpandableNotificationRow) v).setGroupExpansionChanging(false);
1352         }
1353     }
1354 
1355     @Override
1356     public void setUserLockedChild(View v, boolean userLocked) {
1357         if (v instanceof ExpandableNotificationRow) {
1358             ((ExpandableNotificationRow) v).setUserLocked(userLocked);
1359         }
1360         cancelLongPress();
1361         requestDisallowInterceptTouchEvent(true);
1362     }
1363 
1364     @Override
1365     public void expansionStateChanged(boolean isExpanding) {
1366         mExpandingNotification = isExpanding;
1367         if (!mExpandedInThisMotion) {
1368             mMaxScrollAfterExpand = mOwnScrollY;
1369             mExpandedInThisMotion = true;
1370         }
1371     }
1372 
1373     @Override
1374     public int getMaxExpandHeight(ExpandableView view) {
1375         return view.getMaxContentHeight();
1376     }
1377 
1378     public void setScrollingEnabled(boolean enable) {
1379         mScrollingEnabled = enable;
1380     }
1381 
1382     public void lockScrollTo(View v) {
1383         if (mForcedScroll == v) {
1384             return;
1385         }
1386         mForcedScroll = v;
1387         scrollTo(v);
1388     }
1389 
1390     public boolean scrollTo(View v) {
1391         ExpandableView expandableView = (ExpandableView) v;
1392         int positionInLinearLayout = getPositionInLinearLayout(v);
1393         int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
1394         int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
1395 
1396         // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
1397         // that it is not visible anymore.
1398         if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
1399             mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
1400             mDontReportNextOverScroll = true;
1401             animateScroll();
1402             return true;
1403         }
1404         return false;
1405     }
1406 
1407     /**
1408      * @return the scroll necessary to make the bottom edge of {@param v} align with the top of
1409      *         the IME.
1410      */
1411     private int targetScrollForView(ExpandableView v, int positionInLinearLayout) {
1412         return positionInLinearLayout + v.getIntrinsicHeight() +
1413                 getImeInset() - getHeight() + getTopPadding();
1414     }
1415 
1416     @Override
1417     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
1418         mBottomInset = insets.getSystemWindowInsetBottom();
1419 
1420         int range = getScrollRange();
1421         if (mOwnScrollY > range) {
1422             // HACK: We're repeatedly getting staggered insets here while the IME is
1423             // animating away. To work around that we'll wait until things have settled.
1424             removeCallbacks(mReclamp);
1425             postDelayed(mReclamp, 50);
1426         } else if (mForcedScroll != null) {
1427             // The scroll was requested before we got the actual inset - in case we need
1428             // to scroll up some more do so now.
1429             scrollTo(mForcedScroll);
1430         }
1431         return insets;
1432     }
1433 
1434     private Runnable mReclamp = new Runnable() {
1435         @Override
1436         public void run() {
1437             int range = getScrollRange();
1438             mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY);
1439             mDontReportNextOverScroll = true;
1440             mDontClampNextScroll = true;
1441             animateScroll();
1442         }
1443     };
1444 
1445     public void setExpandingEnabled(boolean enable) {
1446         mExpandHelper.setEnabled(enable);
1447     }
1448 
1449     private boolean isScrollingEnabled() {
1450         return mScrollingEnabled;
1451     }
1452 
1453     @Override
1454     public boolean canChildBeDismissed(View v) {
1455         return StackScrollAlgorithm.canChildBeDismissed(v);
1456     }
1457 
1458     @Override
1459     public boolean isAntiFalsingNeeded() {
1460         return onKeyguard();
1461     }
1462 
1463     private boolean onKeyguard() {
1464         return mStatusBarState == StatusBarState.KEYGUARD;
1465     }
1466 
1467     private void setSwipingInProgress(boolean isSwiped) {
1468         mSwipingInProgress = isSwiped;
1469         if(isSwiped) {
1470             requestDisallowInterceptTouchEvent(true);
1471         }
1472     }
1473 
1474     @Override
1475     protected void onConfigurationChanged(Configuration newConfig) {
1476         super.onConfigurationChanged(newConfig);
1477         mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height);
1478         float densityScale = getResources().getDisplayMetrics().density;
1479         mSwipeHelper.setDensityScale(densityScale);
1480         float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
1481         mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
1482         initView(getContext());
1483     }
1484 
1485     public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
1486         mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration,
1487                 true /* isDismissAll */);
1488     }
1489 
1490     @Override
1491     public void snapViewIfNeeded(ExpandableNotificationRow child) {
1492         boolean animate = mIsExpanded || isPinnedHeadsUp(child);
1493         // If the child is showing the notification menu snap to that
1494         float targetLeft = child.getProvider().isMenuVisible() ? child.getTranslation() : 0;
1495         mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft);
1496     }
1497 
1498     @Override
1499     public ViewGroup getViewParentForNotification(NotificationData.Entry entry) {
1500         return this;
1501     }
1502 
1503     @Override
1504     public boolean onTouchEvent(MotionEvent ev) {
1505         boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
1506                 || ev.getActionMasked()== MotionEvent.ACTION_UP;
1507         handleEmptySpaceClick(ev);
1508         boolean expandWantsIt = false;
1509         if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) {
1510             if (isCancelOrUp) {
1511                 mExpandHelper.onlyObserveMovements(false);
1512             }
1513             boolean wasExpandingBefore = mExpandingNotification;
1514             expandWantsIt = mExpandHelper.onTouchEvent(ev);
1515             if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
1516                     && !mDisallowScrollingInThisMotion) {
1517                 dispatchDownEventToScroller(ev);
1518             }
1519         }
1520         boolean scrollerWantsIt = false;
1521         if (mIsExpanded && !mSwipingInProgress && !mExpandingNotification
1522                 && !mDisallowScrollingInThisMotion) {
1523             scrollerWantsIt = onScrollTouch(ev);
1524         }
1525         boolean horizontalSwipeWantsIt = false;
1526         if (!mIsBeingDragged
1527                 && !mExpandingNotification
1528                 && !mExpandedInThisMotion
1529                 && !mOnlyScrollingInThisMotion
1530                 && !mDisallowDismissInThisMotion) {
1531             horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
1532         }
1533 
1534         // Check if we need to clear any snooze leavebehinds
1535         NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts();
1536         if (guts != null && !isTouchInView(ev, guts)
1537                 && guts.getGutsContent() instanceof NotificationSnooze) {
1538             NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent();
1539             if ((ns.isExpanded() && isCancelOrUp)
1540                     || (!horizontalSwipeWantsIt && scrollerWantsIt)) {
1541                 // If the leavebehind is expanded we clear it on the next up event, otherwise we
1542                 // clear it on the next non-horizontal swipe or expand event.
1543                 checkSnoozeLeavebehind();
1544             }
1545         }
1546         if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
1547             mCheckForLeavebehind = true;
1548         }
1549         return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
1550     }
1551 
1552     private void dispatchDownEventToScroller(MotionEvent ev) {
1553         MotionEvent downEvent = MotionEvent.obtain(ev);
1554         downEvent.setAction(MotionEvent.ACTION_DOWN);
1555         onScrollTouch(downEvent);
1556         downEvent.recycle();
1557     }
1558 
1559     @Override
1560     public boolean onGenericMotionEvent(MotionEvent event) {
1561         if (!isScrollingEnabled() || !mIsExpanded || mSwipingInProgress || mExpandingNotification
1562                 || mDisallowScrollingInThisMotion) {
1563             return false;
1564         }
1565         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
1566             switch (event.getAction()) {
1567                 case MotionEvent.ACTION_SCROLL: {
1568                     if (!mIsBeingDragged) {
1569                         final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1570                         if (vscroll != 0) {
1571                             final int delta = (int) (vscroll * getVerticalScrollFactor());
1572                             final int range = getScrollRange();
1573                             int oldScrollY = mOwnScrollY;
1574                             int newScrollY = oldScrollY - delta;
1575                             if (newScrollY < 0) {
1576                                 newScrollY = 0;
1577                             } else if (newScrollY > range) {
1578                                 newScrollY = range;
1579                             }
1580                             if (newScrollY != oldScrollY) {
1581                                 setOwnScrollY(newScrollY);
1582                                 return true;
1583                             }
1584                         }
1585                     }
1586                 }
1587             }
1588         }
1589         return super.onGenericMotionEvent(event);
1590     }
1591 
1592     private boolean onScrollTouch(MotionEvent ev) {
1593         if (!isScrollingEnabled()) {
1594             return false;
1595         }
1596         if (isInsideQsContainer(ev) && !mIsBeingDragged) {
1597             return false;
1598         }
1599         mForcedScroll = null;
1600         initVelocityTrackerIfNotExists();
1601         mVelocityTracker.addMovement(ev);
1602 
1603         final int action = ev.getAction();
1604 
1605         switch (action & MotionEvent.ACTION_MASK) {
1606             case MotionEvent.ACTION_DOWN: {
1607                 if (getChildCount() == 0 || !isInContentBounds(ev)) {
1608                     return false;
1609                 }
1610                 boolean isBeingDragged = !mScroller.isFinished();
1611                 setIsBeingDragged(isBeingDragged);
1612                 /*
1613                  * If being flinged and user touches, stop the fling. isFinished
1614                  * will be false if being flinged.
1615                  */
1616                 if (!mScroller.isFinished()) {
1617                     mScroller.forceFinished(true);
1618                 }
1619 
1620                 // Remember where the motion event started
1621                 mLastMotionY = (int) ev.getY();
1622                 mDownX = (int) ev.getX();
1623                 mActivePointerId = ev.getPointerId(0);
1624                 break;
1625             }
1626             case MotionEvent.ACTION_MOVE:
1627                 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
1628                 if (activePointerIndex == -1) {
1629                     Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
1630                     break;
1631                 }
1632 
1633                 final int y = (int) ev.getY(activePointerIndex);
1634                 final int x = (int) ev.getX(activePointerIndex);
1635                 int deltaY = mLastMotionY - y;
1636                 final int xDiff = Math.abs(x - mDownX);
1637                 final int yDiff = Math.abs(deltaY);
1638                 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
1639                     setIsBeingDragged(true);
1640                     if (deltaY > 0) {
1641                         deltaY -= mTouchSlop;
1642                     } else {
1643                         deltaY += mTouchSlop;
1644                     }
1645                 }
1646                 if (mIsBeingDragged) {
1647                     // Scroll to follow the motion event
1648                     mLastMotionY = y;
1649                     int range = getScrollRange();
1650                     if (mExpandedInThisMotion) {
1651                         range = Math.min(range, mMaxScrollAfterExpand);
1652                     }
1653 
1654                     float scrollAmount;
1655                     if (deltaY < 0) {
1656                         scrollAmount = overScrollDown(deltaY);
1657                     } else {
1658                         scrollAmount = overScrollUp(deltaY, range);
1659                     }
1660 
1661                     // Calling customOverScrollBy will call onCustomOverScrolled, which
1662                     // sets the scrolling if applicable.
1663                     if (scrollAmount != 0.0f) {
1664                         // The scrolling motion could not be compensated with the
1665                         // existing overScroll, we have to scroll the view
1666                         customOverScrollBy((int) scrollAmount, mOwnScrollY,
1667                                 range, getHeight() / 2);
1668                         // If we're scrolling, leavebehinds should be dismissed
1669                         checkSnoozeLeavebehind();
1670                     }
1671                 }
1672                 break;
1673             case MotionEvent.ACTION_UP:
1674                 if (mIsBeingDragged) {
1675                     final VelocityTracker velocityTracker = mVelocityTracker;
1676                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1677                     int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
1678 
1679                     if (shouldOverScrollFling(initialVelocity)) {
1680                         onOverScrollFling(true, initialVelocity);
1681                     } else {
1682                         if (getChildCount() > 0) {
1683                             if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
1684                                 float currentOverScrollTop = getCurrentOverScrollAmount(true);
1685                                 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
1686                                     fling(-initialVelocity);
1687                                 } else {
1688                                     onOverScrollFling(false, initialVelocity);
1689                                 }
1690                             } else {
1691                                 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
1692                                         getScrollRange())) {
1693                                     animateScroll();
1694                                 }
1695                             }
1696                         }
1697                     }
1698                     mActivePointerId = INVALID_POINTER;
1699                     endDrag();
1700                 }
1701 
1702                 break;
1703             case MotionEvent.ACTION_CANCEL:
1704                 if (mIsBeingDragged && getChildCount() > 0) {
1705                     if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
animateScroll()1706                         animateScroll();
1707                     }
1708                     mActivePointerId = INVALID_POINTER;
endDrag()1709                     endDrag();
1710                 }
1711                 break;
1712             case MotionEvent.ACTION_POINTER_DOWN: {
1713                 final int index = ev.getActionIndex();
1714                 mLastMotionY = (int) ev.getY(index);
1715                 mDownX = (int) ev.getX(index);
1716                 mActivePointerId = ev.getPointerId(index);
1717                 break;
1718             }
1719             case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev)1720                 onSecondaryPointerUp(ev);
1721                 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
1722                 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
1723                 break;
1724         }
1725         return true;
1726     }
1727 
1728     protected boolean isInsideQsContainer(MotionEvent ev) {
1729         return ev.getY() < mQsContainer.getBottom();
1730     }
1731 
1732     private void onOverScrollFling(boolean open, int initialVelocity) {
1733         if (mOverscrollTopChangedListener != null) {
1734             mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
1735         }
1736         mDontReportNextOverScroll = true;
1737         setOverScrollAmount(0.0f, true, false);
1738     }
1739 
1740     /**
1741      * Perform a scroll upwards and adapt the overscroll amounts accordingly
1742      *
1743      * @param deltaY The amount to scroll upwards, has to be positive.
1744      * @return The amount of scrolling to be performed by the scroller,
1745      *         not handled by the overScroll amount.
1746      */
1747     private float overScrollUp(int deltaY, int range) {
1748         deltaY = Math.max(deltaY, 0);
1749         float currentTopAmount = getCurrentOverScrollAmount(true);
1750         float newTopAmount = currentTopAmount - deltaY;
1751         if (currentTopAmount > 0) {
1752             setOverScrollAmount(newTopAmount, true /* onTop */,
1753                     false /* animate */);
1754         }
1755         // Top overScroll might not grab all scrolling motion,
1756         // we have to scroll as well.
1757         float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1758         float newScrollY = mOwnScrollY + scrollAmount;
1759         if (newScrollY > range) {
1760             if (!mExpandedInThisMotion) {
1761                 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1762                 // We overScroll on the top
1763                 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
1764                         false /* onTop */,
1765                         false /* animate */);
1766             }
1767             setOwnScrollY(range);
1768             scrollAmount = 0.0f;
1769         }
1770         return scrollAmount;
1771     }
1772 
1773     /**
1774      * Perform a scroll downward and adapt the overscroll amounts accordingly
1775      *
1776      * @param deltaY The amount to scroll downwards, has to be negative.
1777      * @return The amount of scrolling to be performed by the scroller,
1778      *         not handled by the overScroll amount.
1779      */
1780     private float overScrollDown(int deltaY) {
1781         deltaY = Math.min(deltaY, 0);
1782         float currentBottomAmount = getCurrentOverScrollAmount(false);
1783         float newBottomAmount = currentBottomAmount + deltaY;
1784         if (currentBottomAmount > 0) {
1785             setOverScrollAmount(newBottomAmount, false /* onTop */,
1786                     false /* animate */);
1787         }
1788         // Bottom overScroll might not grab all scrolling motion,
1789         // we have to scroll as well.
1790         float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1791         float newScrollY = mOwnScrollY + scrollAmount;
1792         if (newScrollY < 0) {
1793             float currentTopPixels = getCurrentOverScrolledPixels(true);
1794             // We overScroll on the top
1795             setOverScrolledPixels(currentTopPixels - newScrollY,
1796                     true /* onTop */,
1797                     false /* animate */);
1798             setOwnScrollY(0);
1799             scrollAmount = 0.0f;
1800         }
1801         return scrollAmount;
1802     }
1803 
1804     private void onSecondaryPointerUp(MotionEvent ev) {
1805         final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1806                 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1807         final int pointerId = ev.getPointerId(pointerIndex);
1808         if (pointerId == mActivePointerId) {
1809             // This was our active pointer going up. Choose a new
1810             // active pointer and adjust accordingly.
1811             // TODO: Make this decision more intelligent.
1812             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1813             mLastMotionY = (int) ev.getY(newPointerIndex);
1814             mActivePointerId = ev.getPointerId(newPointerIndex);
1815             if (mVelocityTracker != null) {
1816                 mVelocityTracker.clear();
1817             }
1818         }
1819     }
1820 
1821     private void initVelocityTrackerIfNotExists() {
1822         if (mVelocityTracker == null) {
1823             mVelocityTracker = VelocityTracker.obtain();
1824         }
1825     }
1826 
1827     private void recycleVelocityTracker() {
1828         if (mVelocityTracker != null) {
1829             mVelocityTracker.recycle();
1830             mVelocityTracker = null;
1831         }
1832     }
1833 
1834     private void initOrResetVelocityTracker() {
1835         if (mVelocityTracker == null) {
1836             mVelocityTracker = VelocityTracker.obtain();
1837         } else {
1838             mVelocityTracker.clear();
1839         }
1840     }
1841 
1842     public void setFinishScrollingCallback(Runnable runnable) {
1843         mFinishScrollingCallback = runnable;
1844     }
1845 
1846     private void animateScroll() {
1847         if (mScroller.computeScrollOffset()) {
1848             int oldY = mOwnScrollY;
1849             int y = mScroller.getCurrY();
1850 
1851             if (oldY != y) {
1852                 int range = getScrollRange();
1853                 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
1854                     float currVelocity = mScroller.getCurrVelocity();
1855                     if (currVelocity >= mMinimumVelocity) {
1856                         mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
1857                     }
1858                 }
1859 
1860                 if (mDontClampNextScroll) {
1861                     range = Math.max(range, oldY);
1862                 }
1863                 customOverScrollBy(y - oldY, oldY, range,
1864                         (int) (mMaxOverScroll));
1865             }
1866 
1867             postOnAnimation(mAnimateScroll);
1868         } else {
1869             mDontClampNextScroll = false;
1870             if (mFinishScrollingCallback != null) {
1871                 mFinishScrollingCallback.run();
1872             }
1873         }
1874     }
1875 
1876     private boolean customOverScrollBy(int deltaY, int scrollY, int scrollRangeY,
1877             int maxOverScrollY) {
1878 
1879         int newScrollY = scrollY + deltaY;
1880         final int top = -maxOverScrollY;
1881         final int bottom = maxOverScrollY + scrollRangeY;
1882 
1883         boolean clampedY = false;
1884         if (newScrollY > bottom) {
1885             newScrollY = bottom;
1886             clampedY = true;
1887         } else if (newScrollY < top) {
1888             newScrollY = top;
1889             clampedY = true;
1890         }
1891 
1892         onCustomOverScrolled(newScrollY, clampedY);
1893 
1894         return clampedY;
1895     }
1896 
1897     /**
1898      * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
1899      * overscroll effect based on numPixels. By default this will also cancel animations on the
1900      * same overScroll edge.
1901      *
1902      * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
1903      *                  the rubber-banding logic.
1904      * @param onTop Should the effect be applied on top of the scroller.
1905      * @param animate Should an animation be performed.
1906      */
1907     public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
1908         setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
1909     }
1910 
1911     /**
1912      * Set the effective overScroll amount which will be directly reflected in the layout.
1913      * By default this will also cancel animations on the same overScroll edge.
1914      *
1915      * @param amount The amount to overScroll by.
1916      * @param onTop Should the effect be applied on top of the scroller.
1917      * @param animate Should an animation be performed.
1918      */
1919     public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
1920         setOverScrollAmount(amount, onTop, animate, true);
1921     }
1922 
1923     /**
1924      * Set the effective overScroll amount which will be directly reflected in the layout.
1925      *
1926      * @param amount The amount to overScroll by.
1927      * @param onTop Should the effect be applied on top of the scroller.
1928      * @param animate Should an animation be performed.
1929      * @param cancelAnimators Should running animations be cancelled.
1930      */
1931     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1932             boolean cancelAnimators) {
1933         setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
1934     }
1935 
1936     /**
1937      * Set the effective overScroll amount which will be directly reflected in the layout.
1938      *
1939      * @param amount The amount to overScroll by.
1940      * @param onTop Should the effect be applied on top of the scroller.
1941      * @param animate Should an animation be performed.
1942      * @param cancelAnimators Should running animations be cancelled.
1943      * @param isRubberbanded The value which will be passed to
1944      *                     {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
1945      */
1946     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1947             boolean cancelAnimators, boolean isRubberbanded) {
1948         if (cancelAnimators) {
1949             mStateAnimator.cancelOverScrollAnimators(onTop);
1950         }
1951         setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
1952     }
1953 
1954     private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
1955             boolean isRubberbanded) {
1956         amount = Math.max(0, amount);
1957         if (animate) {
1958             mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
1959         } else {
1960             setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
1961             mAmbientState.setOverScrollAmount(amount, onTop);
1962             if (onTop) {
1963                 notifyOverscrollTopListener(amount, isRubberbanded);
1964             }
1965             requestChildrenUpdate();
1966         }
1967     }
1968 
1969     private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
1970         mExpandHelper.onlyObserveMovements(amount > 1.0f);
1971         if (mDontReportNextOverScroll) {
1972             mDontReportNextOverScroll = false;
1973             return;
1974         }
1975         if (mOverscrollTopChangedListener != null) {
1976             mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
1977         }
1978     }
1979 
1980     public void setOverscrollTopChangedListener(
1981             OnOverscrollTopChangedListener overscrollTopChangedListener) {
1982         mOverscrollTopChangedListener = overscrollTopChangedListener;
1983     }
1984 
1985     public float getCurrentOverScrollAmount(boolean top) {
1986         return mAmbientState.getOverScrollAmount(top);
1987     }
1988 
1989     public float getCurrentOverScrolledPixels(boolean top) {
1990         return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
1991     }
1992 
1993     private void setOverScrolledPixels(float amount, boolean onTop) {
1994         if (onTop) {
1995             mOverScrolledTopPixels = amount;
1996         } else {
1997             mOverScrolledBottomPixels = amount;
1998         }
1999     }
2000 
2001     private void onCustomOverScrolled(int scrollY, boolean clampedY) {
2002         // Treat animating scrolls differently; see #computeScroll() for why.
2003         if (!mScroller.isFinished()) {
2004             setOwnScrollY(scrollY);
2005             if (clampedY) {
2006                 springBack();
2007             } else {
2008                 float overScrollTop = getCurrentOverScrollAmount(true);
2009                 if (mOwnScrollY < 0) {
2010                     notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
2011                 } else {
2012                     notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
2013                 }
2014             }
2015         } else {
2016             setOwnScrollY(scrollY);
2017         }
2018     }
2019 
2020     private void springBack() {
2021         int scrollRange = getScrollRange();
2022         boolean overScrolledTop = mOwnScrollY <= 0;
2023         boolean overScrolledBottom = mOwnScrollY >= scrollRange;
2024         if (overScrolledTop || overScrolledBottom) {
2025             boolean onTop;
2026             float newAmount;
2027             if (overScrolledTop) {
2028                 onTop = true;
2029                 newAmount = -mOwnScrollY;
2030                 setOwnScrollY(0);
2031                 mDontReportNextOverScroll = true;
2032             } else {
2033                 onTop = false;
2034                 newAmount = mOwnScrollY - scrollRange;
2035                 setOwnScrollY(scrollRange);
2036             }
2037             setOverScrollAmount(newAmount, onTop, false);
2038             setOverScrollAmount(0.0f, onTop, true);
2039             mScroller.forceFinished(true);
2040         }
2041     }
2042 
2043     private int getScrollRange() {
2044         int contentHeight = getContentHeight();
2045         int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight);
2046         int imeInset = getImeInset();
2047         scrollRange += Math.min(imeInset, Math.max(0,
2048                 getContentHeight() - (getHeight() - imeInset)));
2049         return scrollRange;
2050     }
2051 
2052     private int getImeInset() {
2053         return Math.max(0, mBottomInset - (getRootView().getHeight() - getHeight()));
2054     }
2055 
2056     /**
2057      * @return the first child which has visibility unequal to GONE
2058      */
2059     public ExpandableView getFirstChildNotGone() {
2060         int childCount = getChildCount();
2061         for (int i = 0; i < childCount; i++) {
2062             View child = getChildAt(i);
2063             if (child.getVisibility() != View.GONE && child != mShelf) {
2064                 return (ExpandableView) child;
2065             }
2066         }
2067         return null;
2068     }
2069 
2070     /**
2071      * @return the child before the given view which has visibility unequal to GONE
2072      */
2073     public ExpandableView getViewBeforeView(ExpandableView view) {
2074         ExpandableView previousView = null;
2075         int childCount = getChildCount();
2076         for (int i = 0; i < childCount; i++) {
2077             View child = getChildAt(i);
2078             if (child == view) {
2079                 return previousView;
2080             }
2081             if (child.getVisibility() != View.GONE) {
2082                 previousView = (ExpandableView) child;
2083             }
2084         }
2085         return null;
2086     }
2087 
2088     /**
2089      * @return The first child which has visibility unequal to GONE which is currently below the
2090      *         given translationY or equal to it.
2091      */
2092     private View getFirstChildBelowTranlsationY(float translationY, boolean ignoreChildren) {
2093         int childCount = getChildCount();
2094         for (int i = 0; i < childCount; i++) {
2095             View child = getChildAt(i);
2096             if (child.getVisibility() == View.GONE) {
2097                 continue;
2098             }
2099             float rowTranslation = child.getTranslationY();
2100             if (rowTranslation >= translationY) {
2101                 return child;
2102             } else if (!ignoreChildren && child instanceof ExpandableNotificationRow) {
2103                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2104                 if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
2105                     List<ExpandableNotificationRow> notificationChildren =
2106                             row.getNotificationChildren();
2107                     for (int childIndex = 0; childIndex < notificationChildren.size();
2108                             childIndex++) {
2109                         ExpandableNotificationRow rowChild = notificationChildren.get(childIndex);
2110                         if (rowChild.getTranslationY() + rowTranslation >= translationY) {
2111                             return rowChild;
2112                         }
2113                     }
2114                 }
2115             }
2116         }
2117         return null;
2118     }
2119 
2120     /**
2121      * @return the last child which has visibility unequal to GONE
2122      */
2123     public View getLastChildNotGone() {
2124         int childCount = getChildCount();
2125         for (int i = childCount - 1; i >= 0; i--) {
2126             View child = getChildAt(i);
2127             if (child.getVisibility() != View.GONE && child != mShelf) {
2128                 return child;
2129             }
2130         }
2131         return null;
2132     }
2133 
2134     /**
2135      * @return the number of children which have visibility unequal to GONE
2136      */
2137     public int getNotGoneChildCount() {
2138         int childCount = getChildCount();
2139         int count = 0;
2140         for (int i = 0; i < childCount; i++) {
2141             ExpandableView child = (ExpandableView) getChildAt(i);
2142             if (child.getVisibility() != View.GONE && !child.willBeGone() && child != mShelf) {
2143                 count++;
2144             }
2145         }
2146         return count;
2147     }
2148 
2149     public int getContentHeight() {
2150         return mContentHeight;
2151     }
2152 
2153     private void updateContentHeight() {
2154         int height = 0;
2155         float previousPaddingRequest = mPaddingBetweenElements;
2156         float previousPaddingAmount = 0.0f;
2157         int numShownItems = 0;
2158         boolean finish = false;
2159         int maxDisplayedNotifications = mAmbientState.isFullyDark()
2160                 ? (hasPulsingNotifications() ? 1 : 0)
2161                 : mMaxDisplayedNotifications;
2162 
2163         for (int i = 0; i < getChildCount(); i++) {
2164             ExpandableView expandableView = (ExpandableView) getChildAt(i);
2165             boolean footerViewOnLockScreen = expandableView == mFooterView && onKeyguard();
2166             if (expandableView.getVisibility() != View.GONE
2167                     && !expandableView.hasNoContentHeight() && !footerViewOnLockScreen) {
2168                 boolean limitReached = maxDisplayedNotifications != -1
2169                         && numShownItems >= maxDisplayedNotifications;
2170                 boolean notificationOnAmbientThatIsNotPulsing = mAmbientState.isFullyDark()
2171                         && hasPulsingNotifications()
2172                         && expandableView instanceof ExpandableNotificationRow
2173                         && !isPulsing(((ExpandableNotificationRow) expandableView).getEntry());
2174                 if (limitReached || notificationOnAmbientThatIsNotPulsing) {
2175                     expandableView = mShelf;
2176                     finish = true;
2177                 }
2178                 float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount();
2179                 float padding;
2180                 if (increasedPaddingAmount >= 0.0f) {
2181                     padding = (int) NotificationUtils.interpolate(
2182                             previousPaddingRequest,
2183                             mIncreasedPaddingBetweenElements,
2184                             increasedPaddingAmount);
2185                     previousPaddingRequest = (int) NotificationUtils.interpolate(
2186                             mPaddingBetweenElements,
2187                             mIncreasedPaddingBetweenElements,
2188                             increasedPaddingAmount);
2189                 } else {
2190                     int ownPadding = (int) NotificationUtils.interpolate(
2191                             0,
2192                             mPaddingBetweenElements,
2193                             1.0f + increasedPaddingAmount);
2194                     if (previousPaddingAmount > 0.0f) {
2195                         padding = (int) NotificationUtils.interpolate(
2196                                 ownPadding,
2197                                 mIncreasedPaddingBetweenElements,
2198                                 previousPaddingAmount);
2199                     } else {
2200                         padding = ownPadding;
2201                     }
2202                     previousPaddingRequest = ownPadding;
2203                 }
2204                 if (height != 0) {
2205                     height += padding;
2206                 }
2207                 previousPaddingAmount = increasedPaddingAmount;
2208                 height += expandableView.getIntrinsicHeight();
2209                 numShownItems++;
2210                 if (finish) {
2211                     break;
2212                 }
2213             }
2214         }
2215         mIntrinsicContentHeight = height;
2216         mContentHeight = height + mTopPadding + mBottomMargin;
2217         updateScrollability();
2218         clampScrollPosition();
2219         mAmbientState.setLayoutMaxHeight(mContentHeight);
2220     }
2221 
2222     private boolean isPulsing(NotificationData.Entry entry) {
2223         return mAmbientState.isPulsing(entry);
2224     }
2225 
2226     @Override
2227     public boolean hasPulsingNotifications() {
2228         return mPulsing;
2229     }
2230 
2231     private void updateScrollability() {
2232         boolean scrollable = !mQsExpanded && getScrollRange() > 0;
2233         if (scrollable != mScrollable) {
2234             mScrollable = scrollable;
2235             setFocusable(scrollable);
2236             updateForwardAndBackwardScrollability();
2237         }
2238     }
2239 
2240     private void updateForwardAndBackwardScrollability() {
2241         boolean forwardScrollable = mScrollable && mOwnScrollY < getScrollRange();
2242         boolean backwardsScrollable = mScrollable && mOwnScrollY > 0;
2243         boolean changed = forwardScrollable != mForwardScrollable
2244                 || backwardsScrollable != mBackwardScrollable;
2245         mForwardScrollable = forwardScrollable;
2246         mBackwardScrollable = backwardsScrollable;
2247         if (changed) {
2248             sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
2249         }
2250     }
2251 
2252     private void updateBackground() {
2253         // No need to update the background color if it's not being drawn.
2254         if (!mShouldDrawNotificationBackground || mAmbientState.isFullyDark()) {
2255             return;
2256         }
2257 
2258         updateBackgroundBounds();
2259         if (!mCurrentBounds.equals(mBackgroundBounds)) {
2260             boolean animate = mAnimateNextBackgroundTop || mAnimateNextBackgroundBottom
2261                     || areBoundsAnimating();
2262             if (!isExpanded()) {
2263                 abortBackgroundAnimators();
2264                 animate = false;
2265             }
2266             if (animate) {
2267                 startBackgroundAnimation();
2268             } else {
2269                 mCurrentBounds.set(mBackgroundBounds);
2270                 applyCurrentBackgroundBounds();
2271             }
2272         } else {
2273             abortBackgroundAnimators();
2274         }
2275         mAnimateNextBackgroundBottom = false;
2276         mAnimateNextBackgroundTop = false;
2277     }
2278 
2279     private void abortBackgroundAnimators() {
2280         if (mBottomAnimator != null) {
2281             mBottomAnimator.cancel();
2282         }
2283         if (mTopAnimator != null) {
2284             mTopAnimator.cancel();
2285         }
2286     }
2287 
2288     private boolean areBoundsAnimating() {
2289         return mBottomAnimator != null || mTopAnimator != null;
2290     }
2291 
2292     private void startBackgroundAnimation() {
2293         // left and right are always instantly applied
2294         mCurrentBounds.left = mBackgroundBounds.left;
2295         mCurrentBounds.right = mBackgroundBounds.right;
2296         startBottomAnimation();
2297         startTopAnimation();
2298     }
2299 
2300     private void startTopAnimation() {
2301         int previousEndValue = mEndAnimationRect.top;
2302         int newEndValue = mBackgroundBounds.top;
2303         ObjectAnimator previousAnimator = mTopAnimator;
2304         if (previousAnimator != null && previousEndValue == newEndValue) {
2305             return;
2306         }
2307         if (!mAnimateNextBackgroundTop) {
2308             // just a local update was performed
2309             if (previousAnimator != null) {
2310                 // we need to increase all animation keyframes of the previous animator by the
2311                 // relative change to the end value
2312                 int previousStartValue = mStartAnimationRect.top;
2313                 PropertyValuesHolder[] values = previousAnimator.getValues();
2314                 values[0].setIntValues(previousStartValue, newEndValue);
2315                 mStartAnimationRect.top = previousStartValue;
2316                 mEndAnimationRect.top = newEndValue;
2317                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
2318                 return;
2319             } else {
2320                 // no new animation needed, let's just apply the value
2321                 setBackgroundTop(newEndValue);
2322                 return;
2323             }
2324         }
2325         if (previousAnimator != null) {
2326             previousAnimator.cancel();
2327         }
2328         ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundTop",
2329                 mCurrentBounds.top, newEndValue);
2330         Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
2331         animator.setInterpolator(interpolator);
2332         animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
2333         // remove the tag when the animation is finished
2334         animator.addListener(new AnimatorListenerAdapter() {
2335             @Override
2336             public void onAnimationEnd(Animator animation) {
2337                 mStartAnimationRect.top = -1;
2338                 mEndAnimationRect.top = -1;
2339                 mTopAnimator = null;
2340             }
2341         });
2342         animator.start();
2343         mStartAnimationRect.top = mCurrentBounds.top;
2344         mEndAnimationRect.top = newEndValue;
2345         mTopAnimator = animator;
2346     }
2347 
2348     private void startBottomAnimation() {
2349         int previousStartValue = mStartAnimationRect.bottom;
2350         int previousEndValue = mEndAnimationRect.bottom;
2351         int newEndValue = mBackgroundBounds.bottom;
2352         ObjectAnimator previousAnimator = mBottomAnimator;
2353         if (previousAnimator != null && previousEndValue == newEndValue) {
2354             return;
2355         }
2356         if (!mAnimateNextBackgroundBottom) {
2357             // just a local update was performed
2358             if (previousAnimator != null) {
2359                 // we need to increase all animation keyframes of the previous animator by the
2360                 // relative change to the end value
2361                 PropertyValuesHolder[] values = previousAnimator.getValues();
2362                 values[0].setIntValues(previousStartValue, newEndValue);
2363                 mStartAnimationRect.bottom = previousStartValue;
2364                 mEndAnimationRect.bottom = newEndValue;
2365                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
2366                 return;
2367             } else {
2368                 // no new animation needed, let's just apply the value
2369                 setBackgroundBottom(newEndValue);
2370                 return;
2371             }
2372         }
2373         if (previousAnimator != null) {
2374             previousAnimator.cancel();
2375         }
2376         ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundBottom",
2377                 mCurrentBounds.bottom, newEndValue);
2378         Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
2379         animator.setInterpolator(interpolator);
2380         animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
2381         // remove the tag when the animation is finished
2382         animator.addListener(new AnimatorListenerAdapter() {
2383             @Override
2384             public void onAnimationEnd(Animator animation) {
2385                 mStartAnimationRect.bottom = -1;
2386                 mEndAnimationRect.bottom = -1;
2387                 mBottomAnimator = null;
2388             }
2389         });
2390         animator.start();
2391         mStartAnimationRect.bottom = mCurrentBounds.bottom;
2392         mEndAnimationRect.bottom = newEndValue;
2393         mBottomAnimator = animator;
2394     }
2395 
2396     private void setBackgroundTop(int top) {
2397         mCurrentBounds.top = top;
2398         applyCurrentBackgroundBounds();
2399     }
2400 
2401     public void setBackgroundBottom(int bottom) {
2402         mCurrentBounds.bottom = bottom;
2403         applyCurrentBackgroundBounds();
2404     }
2405 
2406     private void applyCurrentBackgroundBounds() {
2407         // If the background of the notification is not being drawn, then there is no need to
2408         // exclude an area in the scrim. Rather, the scrim's color should serve as the background.
2409         if (!mShouldDrawNotificationBackground) {
2410             return;
2411         }
2412 
2413         final boolean awake = mDarkAmount != 0 || mAmbientState.isDark();
2414         mScrimController.setExcludedBackgroundArea(
2415                 mFadingOut || mParentNotFullyVisible || awake || mIsClipped ? null
2416                         : mCurrentBounds);
2417         invalidate();
2418     }
2419 
2420     /**
2421      * Update the background bounds to the new desired bounds
2422      */
2423     private void updateBackgroundBounds() {
2424         getLocationInWindow(mTempInt2);
2425         mBackgroundBounds.left = mTempInt2[0] + mSidePaddings;
2426         mBackgroundBounds.right = mTempInt2[0] + getWidth() - mSidePaddings;
2427 
2428         if (!mIsExpanded) {
2429             mBackgroundBounds.top = 0;
2430             mBackgroundBounds.bottom = 0;
2431             return;
2432         }
2433         ActivatableNotificationView firstView = mFirstVisibleBackgroundChild;
2434         int top = 0;
2435         if (firstView != null) {
2436             // Round Y up to avoid seeing the background during animation
2437             int finalTranslationY = (int) Math.ceil(ViewState.getFinalTranslationY(firstView));
2438             if (mAnimateNextBackgroundTop
2439                     || mTopAnimator == null && mCurrentBounds.top == finalTranslationY
2440                     || mTopAnimator != null && mEndAnimationRect.top == finalTranslationY) {
2441                 // we're ending up at the same location as we are now, lets just skip the animation
2442                 top = finalTranslationY;
2443             } else {
2444                 top = (int) Math.ceil(firstView.getTranslationY());
2445             }
2446         }
2447         ActivatableNotificationView lastView =
2448                 mShelf.hasItemsInStableShelf() && mShelf.getVisibility() != GONE
2449                         ? mShelf
2450                         : mLastVisibleBackgroundChild;
2451         int bottom;
2452         if (lastView != null) {
2453             int finalTranslationY;
2454             if (lastView == mShelf) {
2455                 finalTranslationY = (int) mShelf.getTranslationY();
2456             } else {
2457                 finalTranslationY = (int) ViewState.getFinalTranslationY(lastView);
2458             }
2459             int finalHeight = ExpandableViewState.getFinalActualHeight(lastView);
2460             int finalBottom = finalTranslationY + finalHeight - lastView.getClipBottomAmount();
2461             if (mAnimateNextBackgroundBottom
2462                     || mBottomAnimator == null && mCurrentBounds.bottom == finalBottom
2463                     || mBottomAnimator != null && mEndAnimationRect.bottom == finalBottom) {
2464                 // we're ending up at the same location as we are now, lets just skip the animation
2465                 bottom = finalBottom;
2466             } else {
2467                 bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight()
2468                         - lastView.getClipBottomAmount());
2469             }
2470         } else {
2471             top = mTopPadding;
2472             bottom = top;
2473         }
2474         if (mStatusBarState != StatusBarState.KEYGUARD) {
2475             top = (int) Math.max(mTopPadding + mStackTranslation, top);
2476         } else {
2477             // otherwise the animation from the shade to the keyguard will jump as it's maxed
2478             top = Math.max(0, top);
2479         }
2480         mBackgroundBounds.top = top;
2481         mBackgroundBounds.bottom = Math.max(bottom, top);
2482     }
2483 
2484     private ActivatableNotificationView getFirstPinnedHeadsUp() {
2485         int childCount = getChildCount();
2486         for (int i = 0; i < childCount; i++) {
2487             View child = getChildAt(i);
2488             if (child.getVisibility() != View.GONE
2489                     && child instanceof ExpandableNotificationRow) {
2490                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2491                 if (row.isPinned()) {
2492                     return row;
2493                 }
2494             }
2495         }
2496         return null;
2497     }
2498 
2499     private ActivatableNotificationView getLastChildWithBackground() {
2500         int childCount = getChildCount();
2501         for (int i = childCount - 1; i >= 0; i--) {
2502             View child = getChildAt(i);
2503             if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
2504                     && child != mShelf) {
2505                 return (ActivatableNotificationView) child;
2506             }
2507         }
2508         return null;
2509     }
2510 
2511     private ActivatableNotificationView getFirstChildWithBackground() {
2512         int childCount = getChildCount();
2513         for (int i = 0; i < childCount; i++) {
2514             View child = getChildAt(i);
2515             if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
2516                     && child != mShelf) {
2517                 return (ActivatableNotificationView) child;
2518             }
2519         }
2520         return null;
2521     }
2522 
2523     /**
2524      * Fling the scroll view
2525      *
2526      * @param velocityY The initial velocity in the Y direction. Positive
2527      *                  numbers mean that the finger/cursor is moving down the screen,
2528      *                  which means we want to scroll towards the top.
2529      */
2530     protected void fling(int velocityY) {
2531         if (getChildCount() > 0) {
2532             int scrollRange = getScrollRange();
2533 
2534             float topAmount = getCurrentOverScrollAmount(true);
2535             float bottomAmount = getCurrentOverScrollAmount(false);
2536             if (velocityY < 0 && topAmount > 0) {
2537                 setOwnScrollY(mOwnScrollY - (int) topAmount);
2538                 mDontReportNextOverScroll = true;
2539                 setOverScrollAmount(0, true, false);
2540                 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
2541                         * mOverflingDistance + topAmount;
2542             } else if (velocityY > 0 && bottomAmount > 0) {
2543                 setOwnScrollY((int) (mOwnScrollY + bottomAmount));
2544                 setOverScrollAmount(0, false, false);
2545                 mMaxOverScroll = Math.abs(velocityY) / 1000f
2546                         * getRubberBandFactor(false /* onTop */) * mOverflingDistance
2547                         +  bottomAmount;
2548             } else {
2549                 // it will be set once we reach the boundary
2550                 mMaxOverScroll = 0.0f;
2551             }
2552             int minScrollY = Math.max(0, scrollRange);
2553             if (mExpandedInThisMotion) {
2554                 minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
2555             }
2556             mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0,
2557                     mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
2558 
2559             animateScroll();
2560         }
2561     }
2562 
2563     /**
2564      * @return Whether a fling performed on the top overscroll edge lead to the expanded
2565      * overScroll view (i.e QS).
2566      */
2567     private boolean shouldOverScrollFling(int initialVelocity) {
2568         float topOverScroll = getCurrentOverScrollAmount(true);
2569         return mScrolledToTopOnFirstDown
2570                 && !mExpandedInThisMotion
2571                 && topOverScroll > mMinTopOverScrollToEscape
2572                 && initialVelocity > 0;
2573     }
2574 
2575     /**
2576      * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
2577      * account.
2578      *
2579      * @param qsHeight the top padding imposed by the quick settings panel
2580      * @param animate whether to animate the change
2581      * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
2582      *                               {@code qsHeight} is the final top padding
2583      */
2584     public void updateTopPadding(float qsHeight, boolean animate,
2585             boolean ignoreIntrinsicPadding) {
2586         int topPadding = (int) qsHeight;
2587         int minStackHeight = getLayoutMinHeight();
2588         if (topPadding + minStackHeight > getHeight()) {
2589             mTopPaddingOverflow = topPadding + minStackHeight - getHeight();
2590         } else {
2591             mTopPaddingOverflow = 0;
2592         }
2593         setTopPadding(ignoreIntrinsicPadding ? topPadding : clampPadding(topPadding),
2594                 animate);
2595         setExpandedHeight(mExpandedHeight);
2596     }
2597 
2598     public void setMaxTopPadding(int maxTopPadding) {
2599         mMaxTopPadding = maxTopPadding;
2600     }
2601 
2602     public int getLayoutMinHeight() {
2603         if (isHeadsUpTransition()) {
2604             return getTopHeadsUpPinnedHeight();
2605         }
2606         return mShelf.getVisibility() == GONE ? 0 : mShelf.getIntrinsicHeight();
2607     }
2608 
2609     public int getFirstChildIntrinsicHeight() {
2610         final ExpandableView firstChild = getFirstChildNotGone();
2611         int firstChildMinHeight = firstChild != null
2612                 ? firstChild.getIntrinsicHeight()
2613                 : mEmptyShadeView != null
2614                         ? mEmptyShadeView.getIntrinsicHeight()
2615                         : mCollapsedSize;
2616         if (mOwnScrollY > 0) {
2617             firstChildMinHeight = Math.max(firstChildMinHeight - mOwnScrollY, mCollapsedSize);
2618         }
2619         return firstChildMinHeight;
2620     }
2621 
2622     public float getTopPaddingOverflow() {
2623         return mTopPaddingOverflow;
2624     }
2625 
2626     public int getPeekHeight() {
2627         final ExpandableView firstChild = getFirstChildNotGone();
2628         final int firstChildMinHeight = firstChild != null ? firstChild.getCollapsedHeight()
2629                 : mCollapsedSize;
2630         int shelfHeight = 0;
2631         if (mLastVisibleBackgroundChild != null && mShelf.getVisibility() != GONE) {
2632             shelfHeight = mShelf.getIntrinsicHeight();
2633         }
2634         return mIntrinsicPadding + firstChildMinHeight + shelfHeight;
2635     }
2636 
2637     private int clampPadding(int desiredPadding) {
2638         return Math.max(desiredPadding, mIntrinsicPadding);
2639     }
2640 
2641     private float getRubberBandFactor(boolean onTop) {
2642         if (!onTop) {
2643             return RUBBER_BAND_FACTOR_NORMAL;
2644         }
2645         if (mExpandedInThisMotion) {
2646             return RUBBER_BAND_FACTOR_AFTER_EXPAND;
2647         } else if (mIsExpansionChanging || mPanelTracking) {
2648             return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
2649         } else if (mScrolledToTopOnFirstDown) {
2650             return 1.0f;
2651         }
2652         return RUBBER_BAND_FACTOR_NORMAL;
2653     }
2654 
2655     /**
2656      * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
2657      * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
2658      * overscroll view (e.g. expand QS).
2659      */
2660     private boolean isRubberbanded(boolean onTop) {
2661         return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking
2662                 || !mScrolledToTopOnFirstDown;
2663     }
2664 
2665     private void endDrag() {
2666         setIsBeingDragged(false);
2667 
2668         recycleVelocityTracker();
2669 
2670         if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
2671             setOverScrollAmount(0, true /* onTop */, true /* animate */);
2672         }
2673         if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
2674             setOverScrollAmount(0, false /* onTop */, true /* animate */);
2675         }
2676     }
2677 
2678     private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
2679         ev.offsetLocation(sourceView.getX(), sourceView.getY());
2680         ev.offsetLocation(-targetView.getX(), -targetView.getY());
2681     }
2682 
2683     @Override
2684     public boolean onInterceptTouchEvent(MotionEvent ev) {
2685         initDownStates(ev);
2686         handleEmptySpaceClick(ev);
2687         boolean expandWantsIt = false;
2688         if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) {
2689             expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
2690         }
2691         boolean scrollWantsIt = false;
2692         if (!mSwipingInProgress && !mExpandingNotification) {
2693             scrollWantsIt = onInterceptTouchEventScroll(ev);
2694         }
2695         boolean swipeWantsIt = false;
2696         if (!mIsBeingDragged
2697                 && !mExpandingNotification
2698                 && !mExpandedInThisMotion
2699                 && !mOnlyScrollingInThisMotion
2700                 && !mDisallowDismissInThisMotion) {
2701             swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
2702         }
2703         // Check if we need to clear any snooze leavebehinds
2704         boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
2705         NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts();
2706         if (!isTouchInView(ev, guts) && isUp && !swipeWantsIt && !expandWantsIt
2707                 && !scrollWantsIt) {
2708             mCheckForLeavebehind = false;
2709             mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */,
2710                     false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
2711                     false /* resetMenu */);
2712         }
2713         if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
2714             mCheckForLeavebehind = true;
2715         }
2716         return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
2717     }
2718 
2719     private void handleEmptySpaceClick(MotionEvent ev) {
2720         switch (ev.getActionMasked()) {
2721             case MotionEvent.ACTION_MOVE:
2722                 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
2723                         || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) {
2724                     mTouchIsClick = false;
2725                 }
2726                 break;
2727             case MotionEvent.ACTION_UP:
2728                 if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
2729                         isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
2730                     mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
2731                 }
2732                 break;
2733         }
2734     }
2735 
2736     private void initDownStates(MotionEvent ev) {
2737         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
2738             mExpandedInThisMotion = false;
2739             mOnlyScrollingInThisMotion = !mScroller.isFinished();
2740             mDisallowScrollingInThisMotion = false;
2741             mDisallowDismissInThisMotion = false;
2742             mTouchIsClick = true;
2743             mInitialTouchX = ev.getX();
2744             mInitialTouchY = ev.getY();
2745         }
2746     }
2747 
2748     public void setChildTransferInProgress(boolean childTransferInProgress) {
2749         mChildTransferInProgress = childTransferInProgress;
2750     }
2751 
2752     @Override
2753     public void onViewRemoved(View child) {
2754         super.onViewRemoved(child);
2755         // we only call our internal methods if this is actually a removal and not just a
2756         // notification which becomes a child notification
2757         if (!mChildTransferInProgress) {
2758             onViewRemovedInternal(child, this);
2759         }
2760     }
2761 
2762     @Override
2763     public void cleanUpViewState(View child) {
2764         if (child == mTranslatingParentView) {
2765             mTranslatingParentView = null;
2766         }
2767         mCurrentStackScrollState.removeViewStateForView(child);
2768     }
2769 
2770     @Override
2771     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
2772         super.requestDisallowInterceptTouchEvent(disallowIntercept);
2773         if (disallowIntercept) {
2774             cancelLongPress();
2775         }
2776     }
2777 
2778     private void onViewRemovedInternal(View child, ViewGroup container) {
2779         if (mChangePositionInProgress) {
2780             // This is only a position change, don't do anything special
2781             return;
2782         }
2783         ExpandableView expandableView = (ExpandableView) child;
2784         expandableView.setOnHeightChangedListener(null);
2785         mCurrentStackScrollState.removeViewStateForView(child);
2786         updateScrollStateForRemovedChild(expandableView);
2787         boolean animationGenerated = generateRemoveAnimation(child);
2788         if (animationGenerated) {
2789             if (!mSwipedOutViews.contains(child)
2790                     || Math.abs(expandableView.getTranslation()) != expandableView.getWidth()) {
2791                 container.addTransientView(child, 0);
2792                 expandableView.setTransientContainer(container);
2793             }
2794         } else {
2795             mSwipedOutViews.remove(child);
2796         }
2797         updateAnimationState(false, child);
2798 
2799         focusNextViewIfFocused(child);
2800     }
2801 
2802     private void focusNextViewIfFocused(View view) {
2803         if (view instanceof ExpandableNotificationRow) {
2804             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
2805             if (row.shouldRefocusOnDismiss()) {
2806                 View nextView = row.getChildAfterViewWhenDismissed();
2807                 if (nextView == null) {
2808                     View groupParentWhenDismissed = row.getGroupParentWhenDismissed();
2809                     nextView = getFirstChildBelowTranlsationY(groupParentWhenDismissed != null
2810                             ? groupParentWhenDismissed.getTranslationY()
2811                             : view.getTranslationY(), true /* ignoreChildren */);
2812                 }
2813                 if (nextView != null) {
2814                     nextView.requestAccessibilityFocus();
2815                 }
2816             }
2817         }
2818 
2819     }
2820 
2821     private boolean isChildInGroup(View child) {
2822         return child instanceof ExpandableNotificationRow
2823                 && mGroupManager.isChildInGroupWithSummary(
2824                         ((ExpandableNotificationRow) child).getStatusBarNotification());
2825     }
2826 
2827     /**
2828      * Generate a remove animation for a child view.
2829      *
2830      * @param child The view to generate the remove animation for.
2831      * @return Whether an animation was generated.
2832      */
2833     private boolean generateRemoveAnimation(View child) {
2834         if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
2835             mAddedHeadsUpChildren.remove(child);
2836             return false;
2837         }
2838         if (isClickedHeadsUp(child)) {
2839             // An animation is already running, add it transiently
2840             mClearTransientViewsWhenFinished.add((ExpandableView) child);
2841             return true;
2842         }
2843         if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
2844             if (!mChildrenToAddAnimated.contains(child)) {
2845                 // Generate Animations
2846                 mChildrenToRemoveAnimated.add(child);
2847                 mNeedsAnimation = true;
2848                 return true;
2849             } else {
2850                 mChildrenToAddAnimated.remove(child);
2851                 mFromMoreCardAdditions.remove(child);
2852                 return false;
2853             }
2854         }
2855         return false;
2856     }
2857 
2858     private boolean isClickedHeadsUp(View child) {
2859         return HeadsUpUtil.isClickedHeadsUpNotification(child);
2860     }
2861 
2862     /**
2863      * Remove a removed child view from the heads up animations if it was just added there
2864      *
2865      * @return whether any child was removed from the list to animate
2866      */
2867     private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
2868         boolean hasAddEvent = false;
2869         for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
2870             ExpandableNotificationRow row = eventPair.first;
2871             boolean isHeadsUp = eventPair.second;
2872             if (child == row) {
2873                 mTmpList.add(eventPair);
2874                 hasAddEvent |= isHeadsUp;
2875             }
2876         }
2877         if (hasAddEvent) {
2878             // This child was just added lets remove all events.
2879             mHeadsUpChangeAnimations.removeAll(mTmpList);
2880             ((ExpandableNotificationRow ) child).setHeadsUpAnimatingAway(false);
2881         }
2882         mTmpList.clear();
2883         return hasAddEvent;
2884     }
2885 
2886     /**
2887      * @param child the child to query
2888      * @return whether a view is not a top level child but a child notification and that group is
2889      *         not expanded
2890      */
2891     private boolean isChildInInvisibleGroup(View child) {
2892         if (child instanceof ExpandableNotificationRow) {
2893             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2894             ExpandableNotificationRow groupSummary =
2895                     mGroupManager.getGroupSummary(row.getStatusBarNotification());
2896             if (groupSummary != null && groupSummary != row) {
2897                 return row.getVisibility() == View.INVISIBLE;
2898             }
2899         }
2900         return false;
2901     }
2902 
2903     /**
2904      * Updates the scroll position when a child was removed
2905      *
2906      * @param removedChild the removed child
2907      */
2908     private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
2909         int startingPosition = getPositionInLinearLayout(removedChild);
2910         float increasedPaddingAmount = removedChild.getIncreasedPaddingAmount();
2911         int padding;
2912         if (increasedPaddingAmount >= 0) {
2913             padding = (int) NotificationUtils.interpolate(
2914                     mPaddingBetweenElements,
2915                     mIncreasedPaddingBetweenElements,
2916                     increasedPaddingAmount);
2917         } else {
2918             padding = (int) NotificationUtils.interpolate(
2919                     0,
2920                     mPaddingBetweenElements,
2921                     1.0f + increasedPaddingAmount);
2922         }
2923         int childHeight = getIntrinsicHeight(removedChild) + padding;
2924         int endPosition = startingPosition + childHeight;
2925         if (endPosition <= mOwnScrollY) {
2926             // This child is fully scrolled of the top, so we have to deduct its height from the
2927             // scrollPosition
2928             setOwnScrollY(mOwnScrollY - childHeight);
2929         } else if (startingPosition < mOwnScrollY) {
2930             // This child is currently being scrolled into, set the scroll position to the start of
2931             // this child
2932             setOwnScrollY(startingPosition);
2933         }
2934     }
2935 
2936     private int getIntrinsicHeight(View view) {
2937         if (view instanceof ExpandableView) {
2938             ExpandableView expandableView = (ExpandableView) view;
2939             return expandableView.getIntrinsicHeight();
2940         }
2941         return view.getHeight();
2942     }
2943 
2944     public int getPositionInLinearLayout(View requestedView) {
2945         ExpandableNotificationRow childInGroup = null;
2946         ExpandableNotificationRow requestedRow = null;
2947         if (isChildInGroup(requestedView)) {
2948             // We're asking for a child in a group. Calculate the position of the parent first,
2949             // then within the parent.
2950             childInGroup = (ExpandableNotificationRow) requestedView;
2951             requestedView = requestedRow = childInGroup.getNotificationParent();
2952         }
2953         int position = 0;
2954         float previousPaddingRequest = mPaddingBetweenElements;
2955         float previousPaddingAmount = 0.0f;
2956         for (int i = 0; i < getChildCount(); i++) {
2957             ExpandableView child = (ExpandableView) getChildAt(i);
2958             boolean notGone = child.getVisibility() != View.GONE;
2959             if (notGone && !child.hasNoContentHeight()) {
2960                 float increasedPaddingAmount = child.getIncreasedPaddingAmount();
2961                 float padding;
2962                 if (increasedPaddingAmount >= 0.0f) {
2963                     padding = (int) NotificationUtils.interpolate(
2964                             previousPaddingRequest,
2965                             mIncreasedPaddingBetweenElements,
2966                             increasedPaddingAmount);
2967                     previousPaddingRequest = (int) NotificationUtils.interpolate(
2968                             mPaddingBetweenElements,
2969                             mIncreasedPaddingBetweenElements,
2970                             increasedPaddingAmount);
2971                 } else {
2972                     int ownPadding = (int) NotificationUtils.interpolate(
2973                             0,
2974                             mPaddingBetweenElements,
2975                             1.0f + increasedPaddingAmount);
2976                     if (previousPaddingAmount > 0.0f) {
2977                         padding = (int) NotificationUtils.interpolate(
2978                                 ownPadding,
2979                                 mIncreasedPaddingBetweenElements,
2980                                 previousPaddingAmount);
2981                     } else {
2982                         padding = ownPadding;
2983                     }
2984                     previousPaddingRequest = ownPadding;
2985                 }
2986                 if (position != 0) {
2987                     position += padding;
2988                 }
2989                 previousPaddingAmount = increasedPaddingAmount;
2990             }
2991             if (child == requestedView) {
2992                 if (requestedRow != null) {
2993                     position += requestedRow.getPositionOfChild(childInGroup);
2994                 }
2995                 return position;
2996             }
2997             if (notGone) {
2998                 position += getIntrinsicHeight(child);
2999             }
3000         }
3001         return 0;
3002     }
3003 
3004     @Override
3005     public void onViewAdded(View child) {
3006         super.onViewAdded(child);
3007         onViewAddedInternal(child);
3008     }
3009 
3010     private void updateFirstAndLastBackgroundViews() {
3011         ActivatableNotificationView firstChild = getFirstChildWithBackground();
3012         ActivatableNotificationView lastChild = getLastChildWithBackground();
3013         if (mAnimationsEnabled && mIsExpanded) {
3014             mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild;
3015             mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild;
3016         } else {
3017             mAnimateNextBackgroundTop = false;
3018             mAnimateNextBackgroundBottom = false;
3019         }
3020         mFirstVisibleBackgroundChild = firstChild;
3021         mLastVisibleBackgroundChild = lastChild;
3022         mAmbientState.setLastVisibleBackgroundChild(lastChild);
3023         mRoundnessManager.setFirstAndLastBackgroundChild(mFirstVisibleBackgroundChild,
3024                 mLastVisibleBackgroundChild);
3025         invalidate();
3026     }
3027 
3028     private void onViewAddedInternal(View child) {
3029         updateHideSensitiveForChild(child);
3030         ((ExpandableView) child).setOnHeightChangedListener(this);
3031         generateAddAnimation(child, false /* fromMoreCard */);
3032         updateAnimationState(child);
3033         updateChronometerForChild(child);
3034     }
3035 
3036     private void updateHideSensitiveForChild(View child) {
3037         if (child instanceof ExpandableView) {
3038             ExpandableView expandableView = (ExpandableView) child;
3039             expandableView.setHideSensitiveForIntrinsicHeight(mAmbientState.isHideSensitive());
3040         }
3041     }
3042 
3043     @Override
3044     public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) {
3045         onViewRemovedInternal(row, childrenContainer);
3046     }
3047 
3048     @Override
3049     public void notifyGroupChildAdded(View row) {
3050         onViewAddedInternal(row);
3051     }
3052 
3053     public void setAnimationsEnabled(boolean animationsEnabled) {
3054         mAnimationsEnabled = animationsEnabled;
3055         updateNotificationAnimationStates();
3056         if (!animationsEnabled) {
3057             mSwipedOutViews.clear();
3058             mChildrenToRemoveAnimated.clear();
3059             clearTemporaryViewsInGroup(this);
3060         }
3061     }
3062 
3063     private void updateNotificationAnimationStates() {
3064         boolean running = mAnimationsEnabled || hasPulsingNotifications();
3065         mShelf.setAnimationsEnabled(running);
3066         int childCount = getChildCount();
3067         for (int i = 0; i < childCount; i++) {
3068             View child = getChildAt(i);
3069             running &= mIsExpanded || isPinnedHeadsUp(child);
3070             updateAnimationState(running, child);
3071         }
3072     }
3073 
3074     private void updateAnimationState(View child) {
3075         updateAnimationState((mAnimationsEnabled || hasPulsingNotifications())
3076                 && (mIsExpanded || isPinnedHeadsUp(child)), child);
3077     }
3078 
3079     @Override
3080     public void setExpandingNotification(ExpandableNotificationRow row) {
3081         mAmbientState.setExpandingNotification(row);
3082         requestChildrenUpdate();
3083     }
3084 
3085     @Override
3086     public void bindRow(ExpandableNotificationRow row) {
3087         row.setHeadsUpAnimatingAwayListener(animatingAway -> {
3088             mRoundnessManager.onHeadsupAnimatingAwayChanged(row, animatingAway);
3089             mHeadsUpAppearanceController.updateHeader(row.getEntry());
3090         });
3091     }
3092 
3093     @Override
3094     public void applyExpandAnimationParams(ExpandAnimationParameters params) {
3095         mAmbientState.setExpandAnimationTopChange(params == null ? 0 : params.getTopChange());
3096         requestChildrenUpdate();
3097     }
3098 
3099     private void updateAnimationState(boolean running, View child) {
3100         if (child instanceof ExpandableNotificationRow) {
3101             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
3102             row.setIconAnimationRunning(running);
3103         }
3104     }
3105 
3106     public boolean isAddOrRemoveAnimationPending() {
3107         return mNeedsAnimation
3108                 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
3109     }
3110 
3111     @Override
3112     public void generateAddAnimation(View child, boolean fromMoreCard) {
3113         if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
3114             // Generate Animations
3115             mChildrenToAddAnimated.add(child);
3116             if (fromMoreCard) {
3117                 mFromMoreCardAdditions.add(child);
3118             }
3119             mNeedsAnimation = true;
3120         }
3121         if (isHeadsUp(child) && mAnimationsEnabled && !mChangePositionInProgress) {
3122             mAddedHeadsUpChildren.add(child);
3123             mChildrenToAddAnimated.remove(child);
3124         }
3125     }
3126 
3127     @Override
3128     public void changeViewPosition(View child, int newIndex) {
3129         int currentIndex = indexOfChild(child);
3130 
3131         if (currentIndex == -1) {
3132             boolean isTransient = false;
3133             if (child instanceof ExpandableNotificationRow
3134                     && ((ExpandableNotificationRow)child).getTransientContainer() != null) {
3135                 isTransient = true;
3136             }
3137             Log.e(TAG, "Attempting to re-position "
3138                     + (isTransient ? "transient" : "")
3139                     + " view {"
3140                     + child
3141                     + "}");
3142             return;
3143         }
3144 
3145         if (child != null && child.getParent() == this && currentIndex != newIndex) {
3146             mChangePositionInProgress = true;
3147             ((ExpandableView)child).setChangingPosition(true);
3148             removeView(child);
3149             addView(child, newIndex);
3150             ((ExpandableView)child).setChangingPosition(false);
3151             mChangePositionInProgress = false;
3152             if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
3153                 mChildrenChangingPositions.add(child);
3154                 mNeedsAnimation = true;
3155             }
3156         }
3157     }
3158 
3159     private void startAnimationToState() {
3160         if (mNeedsAnimation) {
3161             generateChildHierarchyEvents();
3162             mNeedsAnimation = false;
3163         }
3164         if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
3165             setAnimationRunning(true);
3166             mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState,
3167                     mGoToFullShadeDelay);
3168             mAnimationEvents.clear();
3169             updateBackground();
3170             updateViewShadows();
3171             updateClippingToTopRoundedCorner();
3172         } else {
3173             applyCurrentState();
3174         }
3175         mGoToFullShadeDelay = 0;
3176     }
3177 
3178     private void generateChildHierarchyEvents() {
3179         generateHeadsUpAnimationEvents();
3180         generateChildRemovalEvents();
3181         generateChildAdditionEvents();
3182         generatePositionChangeEvents();
3183         generateSnapBackEvents();
3184         generateDragEvents();
3185         generateTopPaddingEvent();
3186         generateActivateEvent();
3187         generateDimmedEvent();
3188         generateHideSensitiveEvent();
3189         generateDarkEvent();
3190         generateGoToFullShadeEvent();
3191         generateViewResizeEvent();
3192         generateGroupExpansionEvent();
3193         generateAnimateEverythingEvent();
3194         generatePulsingAnimationEvent();
3195         mNeedsAnimation = false;
3196     }
3197 
3198     private void generateHeadsUpAnimationEvents() {
3199         for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
3200             ExpandableNotificationRow row = eventPair.first;
3201             boolean isHeadsUp = eventPair.second;
3202             int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
3203             boolean onBottom = false;
3204             boolean pinnedAndClosed = row.isPinned() && !mIsExpanded;
3205             if (!mIsExpanded && !isHeadsUp) {
3206                 type = row.wasJustClicked()
3207                         ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
3208                         : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
3209                 if (row.isChildInGroup()) {
3210                     // We can otherwise get stuck in there if it was just isolated
3211                     row.setHeadsUpAnimatingAway(false);
3212                     continue;
3213                 }
3214             } else {
3215                 ExpandableViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
3216                 if (viewState == null) {
3217                     // A view state was never generated for this view, so we don't need to animate
3218                     // this. This may happen with notification children.
3219                     continue;
3220                 }
3221                 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
3222                     if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
3223                         // Our custom add animation
3224                         type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
3225                     } else {
3226                         // Normal add animation
3227                         type = AnimationEvent.ANIMATION_TYPE_ADD;
3228                     }
3229                     onBottom = !pinnedAndClosed;
3230                 }
3231             }
3232             AnimationEvent event = new AnimationEvent(row, type);
3233             event.headsUpFromBottom = onBottom;
3234             mAnimationEvents.add(event);
3235         }
3236         mHeadsUpChangeAnimations.clear();
3237         mAddedHeadsUpChildren.clear();
3238     }
3239 
3240     private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) {
3241         if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
3242             return false;
3243         }
3244         return true;
3245     }
3246 
3247     private void generateGroupExpansionEvent() {
3248         // Generate a group expansion/collapsing event if there is such a group at all
3249         if (mExpandedGroupView != null) {
3250             mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
3251                     AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
3252             mExpandedGroupView = null;
3253         }
3254     }
3255 
3256     private void generateViewResizeEvent() {
3257         if (mNeedViewResizeAnimation) {
3258             mAnimationEvents.add(
3259                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
3260         }
3261         mNeedViewResizeAnimation = false;
3262     }
3263 
3264     private void generateSnapBackEvents() {
3265         for (View child : mSnappedBackChildren) {
3266             mAnimationEvents.add(new AnimationEvent(child,
3267                     AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
3268         }
3269         mSnappedBackChildren.clear();
3270     }
3271 
3272     private void generateDragEvents() {
3273         for (View child : mDragAnimPendingChildren) {
3274             mAnimationEvents.add(new AnimationEvent(child,
3275                     AnimationEvent.ANIMATION_TYPE_START_DRAG));
3276         }
3277         mDragAnimPendingChildren.clear();
3278     }
3279 
3280     private void generateChildRemovalEvents() {
3281         for (View child : mChildrenToRemoveAnimated) {
3282             boolean childWasSwipedOut = mSwipedOutViews.contains(child);
3283 
3284             // we need to know the view after this one
3285             float removedTranslation = child.getTranslationY();
3286             boolean ignoreChildren = true;
3287             if (child instanceof ExpandableNotificationRow) {
3288                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
3289                 if (row.isRemoved() && row.wasChildInGroupWhenRemoved()) {
3290                     removedTranslation = row.getTranslationWhenRemoved();
3291                     ignoreChildren = false;
3292                 }
3293                 childWasSwipedOut |= Math.abs(row.getTranslation()) == row.getWidth();
3294             }
3295             if (!childWasSwipedOut) {
3296                 Rect clipBounds = child.getClipBounds();
3297                 childWasSwipedOut = clipBounds != null && clipBounds.height() == 0;
3298 
3299                 if (childWasSwipedOut && child instanceof ExpandableView) {
3300                     // Clean up any potential transient views if the child has already been swiped
3301                     // out, as we won't be animating it further (due to its height already being
3302                     // clipped to 0.
3303                     ViewGroup transientContainer = ((ExpandableView) child).getTransientContainer();
3304                     if (transientContainer != null) {
3305                         transientContainer.removeTransientView(child);
3306                     }
3307                 }
3308             }
3309             int animationType = childWasSwipedOut
3310                     ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
3311                     : AnimationEvent.ANIMATION_TYPE_REMOVE;
3312             AnimationEvent event = new AnimationEvent(child, animationType);
3313             event.viewAfterChangingView = getFirstChildBelowTranlsationY(removedTranslation,
3314                     ignoreChildren);
3315             mAnimationEvents.add(event);
3316             mSwipedOutViews.remove(child);
3317         }
3318         mChildrenToRemoveAnimated.clear();
3319     }
3320 
3321     private void generatePositionChangeEvents() {
3322         for (View child : mChildrenChangingPositions) {
3323             mAnimationEvents.add(new AnimationEvent(child,
3324                     AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
3325         }
3326         mChildrenChangingPositions.clear();
3327         if (mGenerateChildOrderChangedEvent) {
3328             mAnimationEvents.add(new AnimationEvent(null,
3329                     AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
3330             mGenerateChildOrderChangedEvent = false;
3331         }
3332     }
3333 
3334     private void generateChildAdditionEvents() {
3335         for (View child : mChildrenToAddAnimated) {
3336             if (mFromMoreCardAdditions.contains(child)) {
3337                 mAnimationEvents.add(new AnimationEvent(child,
3338                         AnimationEvent.ANIMATION_TYPE_ADD,
3339                         StackStateAnimator.ANIMATION_DURATION_STANDARD));
3340             } else {
3341                 mAnimationEvents.add(new AnimationEvent(child,
3342                         AnimationEvent.ANIMATION_TYPE_ADD));
3343             }
3344         }
3345         mChildrenToAddAnimated.clear();
3346         mFromMoreCardAdditions.clear();
3347     }
3348 
3349     private void generateTopPaddingEvent() {
3350         if (mTopPaddingNeedsAnimation) {
3351             AnimationEvent event;
3352             if (mAmbientState.isDark()) {
3353                 event = new AnimationEvent(null /* view */,
3354                         AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED,
3355                         KeyguardSliceView.DEFAULT_ANIM_DURATION);
3356             } else {
3357                 event = new AnimationEvent(null /* view */,
3358                         AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED);
3359             }
3360             mAnimationEvents.add(event);
3361         }
3362         mTopPaddingNeedsAnimation = false;
3363     }
3364 
3365     private void generateActivateEvent() {
3366         if (mActivateNeedsAnimation) {
3367             mAnimationEvents.add(
3368                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
3369         }
3370         mActivateNeedsAnimation = false;
3371     }
3372 
3373     private void generateAnimateEverythingEvent() {
3374         if (mEverythingNeedsAnimation) {
3375             mAnimationEvents.add(
3376                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
3377         }
3378         mEverythingNeedsAnimation = false;
3379     }
3380 
3381     private void generateDimmedEvent() {
3382         if (mDimmedNeedsAnimation) {
3383             mAnimationEvents.add(
3384                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
3385         }
3386         mDimmedNeedsAnimation = false;
3387     }
3388 
3389     private void generateHideSensitiveEvent() {
3390         if (mHideSensitiveNeedsAnimation) {
3391             mAnimationEvents.add(
3392                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
3393         }
3394         mHideSensitiveNeedsAnimation = false;
3395     }
3396 
3397     private void generateDarkEvent() {
3398         if (mDarkNeedsAnimation) {
3399             AnimationEvent ev = new AnimationEvent(null,
3400                     AnimationEvent.ANIMATION_TYPE_DARK,
3401                     new AnimationFilter()
3402                             .animateDark()
3403                             .animateY(mShelf));
3404             ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
3405             mAnimationEvents.add(ev);
3406             startDarkAmountAnimation();
3407         }
3408         mDarkNeedsAnimation = false;
3409     }
3410 
3411     private void generateGoToFullShadeEvent() {
3412         if (mGoToFullShadeNeedsAnimation) {
3413             mAnimationEvents.add(
3414                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
3415         }
3416         mGoToFullShadeNeedsAnimation = false;
3417     }
3418 
3419     private boolean onInterceptTouchEventScroll(MotionEvent ev) {
3420         if (!isScrollingEnabled()) {
3421             return false;
3422         }
3423         /*
3424          * This method JUST determines whether we want to intercept the motion.
3425          * If we return true, onMotionEvent will be called and we do the actual
3426          * scrolling there.
3427          */
3428 
3429         /*
3430         * Shortcut the most recurring case: the user is in the dragging
3431         * state and is moving their finger.  We want to intercept this
3432         * motion.
3433         */
3434         final int action = ev.getAction();
3435         if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
3436             return true;
3437         }
3438 
3439         switch (action & MotionEvent.ACTION_MASK) {
3440             case MotionEvent.ACTION_MOVE: {
3441                 /*
3442                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
3443                  * whether the user has moved far enough from the original down touch.
3444                  */
3445 
3446                 /*
3447                 * Locally do absolute value. mLastMotionY is set to the y value
3448                 * of the down event.
3449                 */
3450                 final int activePointerId = mActivePointerId;
3451                 if (activePointerId == INVALID_POINTER) {
3452                     // If we don't have a valid id, the touch down wasn't on content.
3453                     break;
3454                 }
3455 
3456                 final int pointerIndex = ev.findPointerIndex(activePointerId);
3457                 if (pointerIndex == -1) {
3458                     Log.e(TAG, "Invalid pointerId=" + activePointerId
3459                             + " in onInterceptTouchEvent");
3460                     break;
3461                 }
3462 
3463                 final int y = (int) ev.getY(pointerIndex);
3464                 final int x = (int) ev.getX(pointerIndex);
3465                 final int yDiff = Math.abs(y - mLastMotionY);
3466                 final int xDiff = Math.abs(x - mDownX);
3467                 if (yDiff > mTouchSlop && yDiff > xDiff) {
3468                     setIsBeingDragged(true);
3469                     mLastMotionY = y;
3470                     mDownX = x;
3471                     initVelocityTrackerIfNotExists();
3472                     mVelocityTracker.addMovement(ev);
3473                 }
3474                 break;
3475             }
3476 
3477             case MotionEvent.ACTION_DOWN: {
3478                 final int y = (int) ev.getY();
3479                 mScrolledToTopOnFirstDown = isScrolledToTop();
3480                 if (getChildAtPosition(ev.getX(), y, false /* requireMinHeight */) == null) {
3481                     setIsBeingDragged(false);
3482                     recycleVelocityTracker();
3483                     break;
3484                 }
3485 
3486                 /*
3487                  * Remember location of down touch.
3488                  * ACTION_DOWN always refers to pointer index 0.
3489                  */
3490                 mLastMotionY = y;
3491                 mDownX = (int) ev.getX();
3492                 mActivePointerId = ev.getPointerId(0);
3493 
3494                 initOrResetVelocityTracker();
3495                 mVelocityTracker.addMovement(ev);
3496                 /*
3497                 * If being flinged and user touches the screen, initiate drag;
3498                 * otherwise don't.  mScroller.isFinished should be false when
3499                 * being flinged.
3500                 */
3501                 boolean isBeingDragged = !mScroller.isFinished();
3502                 setIsBeingDragged(isBeingDragged);
3503                 break;
3504             }
3505 
3506             case MotionEvent.ACTION_CANCEL:
3507             case MotionEvent.ACTION_UP:
3508                 /* Release the drag */
3509                 setIsBeingDragged(false);
3510                 mActivePointerId = INVALID_POINTER;
3511                 recycleVelocityTracker();
3512                 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
3513                     animateScroll();
3514                 }
3515                 break;
3516             case MotionEvent.ACTION_POINTER_UP:
3517                 onSecondaryPointerUp(ev);
3518                 break;
3519         }
3520 
3521         /*
3522         * The only time we want to intercept motion events is if we are in the
3523         * drag mode.
3524         */
3525         return mIsBeingDragged;
3526     }
3527 
3528     protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) {
3529         return new StackScrollAlgorithm(context);
3530     }
3531 
3532     /**
3533      * @return Whether the specified motion event is actually happening over the content.
3534      */
3535     private boolean isInContentBounds(MotionEvent event) {
3536         return isInContentBounds(event.getY());
3537     }
3538 
3539     /**
3540      * @return Whether a y coordinate is inside the content.
3541      */
3542     public boolean isInContentBounds(float y) {
3543         return y < getHeight() - getEmptyBottomMargin();
3544     }
3545 
3546     private void setIsBeingDragged(boolean isDragged) {
3547         mIsBeingDragged = isDragged;
3548         if (isDragged) {
3549             requestDisallowInterceptTouchEvent(true);
3550             cancelLongPress();
3551         }
3552     }
3553 
3554     @Override
3555     public void onWindowFocusChanged(boolean hasWindowFocus) {
3556         super.onWindowFocusChanged(hasWindowFocus);
3557         if (!hasWindowFocus) {
3558             cancelLongPress();
3559         }
3560     }
3561 
3562     @Override
3563     public void clearChildFocus(View child) {
3564         super.clearChildFocus(child);
3565         if (mForcedScroll == child) {
3566             mForcedScroll = null;
3567         }
3568     }
3569 
3570     public void requestDisallowLongPress() {
3571         cancelLongPress();
3572     }
3573 
3574     public void requestDisallowDismiss() {
3575         mDisallowDismissInThisMotion = true;
3576     }
3577 
3578     public void cancelLongPress() {
3579         mSwipeHelper.cancelLongPress();
3580     }
3581 
3582     @Override
3583     public boolean isScrolledToTop() {
3584         return mOwnScrollY == 0;
3585     }
3586 
3587     @Override
3588     public boolean isScrolledToBottom() {
3589         return mOwnScrollY >= getScrollRange();
3590     }
3591 
3592     @Override
3593     public View getHostView() {
3594         return this;
3595     }
3596 
3597     public int getEmptyBottomMargin() {
3598         return Math.max(mMaxLayoutHeight - mContentHeight, 0);
3599     }
3600 
3601     public void checkSnoozeLeavebehind() {
3602         if (mCheckForLeavebehind) {
3603             mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */,
3604                     false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
3605                     false /* resetMenu */);
3606             mCheckForLeavebehind = false;
3607         }
3608     }
3609 
3610     public void resetCheckSnoozeLeavebehind() {
3611         mCheckForLeavebehind = true;
3612     }
3613 
3614     public void onExpansionStarted() {
3615         mIsExpansionChanging = true;
3616         mAmbientState.setExpansionChanging(true);
3617         checkSnoozeLeavebehind();
3618     }
3619 
3620     public void onExpansionStopped() {
3621         mIsExpansionChanging = false;
3622         resetCheckSnoozeLeavebehind();
3623         mAmbientState.setExpansionChanging(false);
3624         if (!mIsExpanded) {
3625             setOwnScrollY(0);
3626             mStatusBar.resetUserExpandedStates();
3627             clearTemporaryViews();
3628             clearUserLockedViews();
3629         }
3630     }
3631 
3632     private void clearUserLockedViews() {
3633         for (int i = 0; i < getChildCount(); i++) {
3634             ExpandableView child = (ExpandableView) getChildAt(i);
3635             if (child instanceof ExpandableNotificationRow) {
3636                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
3637                 row.setUserLocked(false);
3638             }
3639         }
3640     }
3641 
3642     private void clearTemporaryViews() {
3643         // lets make sure nothing is transient anymore
3644         clearTemporaryViewsInGroup(this);
3645         for (int i = 0; i < getChildCount(); i++) {
3646             ExpandableView child = (ExpandableView) getChildAt(i);
3647             if (child instanceof ExpandableNotificationRow) {
3648                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
3649                 clearTemporaryViewsInGroup(row.getChildrenContainer());
3650             }
3651         }
3652     }
3653 
3654     private void clearTemporaryViewsInGroup(ViewGroup viewGroup) {
3655         while (viewGroup != null && viewGroup.getTransientViewCount() != 0) {
3656             viewGroup.removeTransientView(viewGroup.getTransientView(0));
3657         }
3658     }
3659 
3660     public void onPanelTrackingStarted() {
3661         mPanelTracking = true;
3662         mAmbientState.setPanelTracking(true);
3663     }
3664     public void onPanelTrackingStopped() {
3665         mPanelTracking = false;
3666         mAmbientState.setPanelTracking(false);
3667     }
3668 
3669     public void resetScrollPosition() {
3670         mScroller.abortAnimation();
3671         setOwnScrollY(0);
3672     }
3673 
3674     private void setIsExpanded(boolean isExpanded) {
3675         boolean changed = isExpanded != mIsExpanded;
3676         mIsExpanded = isExpanded;
3677         mStackScrollAlgorithm.setIsExpanded(isExpanded);
3678         if (changed) {
3679             if (!mIsExpanded) {
3680                 mGroupManager.collapseAllGroups();
3681                 mExpandHelper.cancelImmediately();
3682             }
3683             updateNotificationAnimationStates();
3684             updateChronometers();
3685             requestChildrenUpdate();
3686         }
3687     }
3688 
3689     private void updateChronometers() {
3690         int childCount = getChildCount();
3691         for (int i = 0; i < childCount; i++) {
3692             updateChronometerForChild(getChildAt(i));
3693         }
3694     }
3695 
3696     private void updateChronometerForChild(View child) {
3697         if (child instanceof ExpandableNotificationRow) {
3698             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
3699             row.setChronometerRunning(mIsExpanded);
3700         }
3701     }
3702 
3703     @Override
3704     public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
3705         updateContentHeight();
3706         updateScrollPositionOnExpandInBottom(view);
3707         clampScrollPosition();
3708         notifyHeightChangeListener(view, needsAnimation);
3709         ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
3710                 ? (ExpandableNotificationRow) view
3711                 : null;
3712         if (row != null && (row == mFirstVisibleBackgroundChild
3713                 || row.getNotificationParent() == mFirstVisibleBackgroundChild)) {
3714             updateAlgorithmLayoutMinHeight();
3715         }
3716         if (needsAnimation) {
3717             requestAnimationOnViewResize(row);
3718         }
3719         requestChildrenUpdate();
3720     }
3721 
3722     @Override
3723     public void onReset(ExpandableView view) {
3724         updateAnimationState(view);
3725         updateChronometerForChild(view);
3726     }
3727 
3728     private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
3729         if (view instanceof ExpandableNotificationRow && !onKeyguard()) {
3730             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
3731             if (row.isUserLocked() && row != getFirstChildNotGone()) {
3732                 if (row.isSummaryWithChildren()) {
3733                     return;
3734                 }
3735                 // We are actually expanding this view
3736                 float endPosition = row.getTranslationY() + row.getActualHeight();
3737                 if (row.isChildInGroup()) {
3738                     endPosition += row.getNotificationParent().getTranslationY();
3739                 }
3740                 int layoutEnd = mMaxLayoutHeight + (int) mStackTranslation;
3741                 if (row != mLastVisibleBackgroundChild && mShelf.getVisibility() != GONE) {
3742                     layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
3743                 }
3744                 if (endPosition > layoutEnd) {
3745                     setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
3746                     mDisallowScrollingInThisMotion = true;
3747                 }
3748             }
3749         }
3750     }
3751 
3752     public void setOnHeightChangedListener(
3753             ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
3754         this.mOnHeightChangedListener = mOnHeightChangedListener;
3755     }
3756 
3757     public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
3758         mOnEmptySpaceClickListener = listener;
3759     }
3760 
3761     public void onChildAnimationFinished() {
3762         setAnimationRunning(false);
3763         requestChildrenUpdate();
3764         runAnimationFinishedRunnables();
3765         clearTransient();
3766         clearHeadsUpDisappearRunning();
3767     }
3768 
3769     private void clearHeadsUpDisappearRunning() {
3770         for (int i = 0; i < getChildCount(); i++) {
3771             View view = getChildAt(i);
3772             if (view instanceof ExpandableNotificationRow) {
3773                 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
3774                 row.setHeadsUpAnimatingAway(false);
3775                 if (row.isSummaryWithChildren()) {
3776                     for (ExpandableNotificationRow child : row.getNotificationChildren()) {
3777                         child.setHeadsUpAnimatingAway(false);
3778                     }
3779                 }
3780             }
3781         }
3782     }
3783 
3784     private void clearTransient() {
3785         for (ExpandableView view : mClearTransientViewsWhenFinished) {
3786             StackStateAnimator.removeTransientView(view);
3787         }
3788         mClearTransientViewsWhenFinished.clear();
3789     }
3790 
3791     private void runAnimationFinishedRunnables() {
3792         for (Runnable runnable : mAnimationFinishedRunnables) {
3793             runnable.run();
3794         }
3795         mAnimationFinishedRunnables.clear();
3796     }
3797 
3798     /**
3799      * See {@link AmbientState#setDimmed}.
3800      */
3801     public void setDimmed(boolean dimmed, boolean animate) {
3802         dimmed &= onKeyguard();
3803         mAmbientState.setDimmed(dimmed);
3804         if (animate && mAnimationsEnabled) {
3805             mDimmedNeedsAnimation = true;
3806             mNeedsAnimation =  true;
3807             animateDimmed(dimmed);
3808         } else {
3809             setDimAmount(dimmed ? 1.0f : 0.0f);
3810         }
3811         requestChildrenUpdate();
3812     }
3813 
3814     @VisibleForTesting
3815     boolean isDimmed() {
3816         return mAmbientState.isDimmed();
3817     }
3818 
3819     private void setDimAmount(float dimAmount) {
3820         mDimAmount = dimAmount;
3821         updateBackgroundDimming();
3822     }
3823 
3824     private void animateDimmed(boolean dimmed) {
3825         if (mDimAnimator != null) {
3826             mDimAnimator.cancel();
3827         }
3828         float target = dimmed ? 1.0f : 0.0f;
3829         if (target == mDimAmount) {
3830             return;
3831         }
3832         mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target);
3833         mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED);
3834         mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
3835         mDimAnimator.addListener(mDimEndListener);
3836         mDimAnimator.addUpdateListener(mDimUpdateListener);
3837         mDimAnimator.start();
3838     }
3839 
3840     public void setHideSensitive(boolean hideSensitive, boolean animate) {
3841         if (hideSensitive != mAmbientState.isHideSensitive()) {
3842             int childCount = getChildCount();
3843             for (int i = 0; i < childCount; i++) {
3844                 ExpandableView v = (ExpandableView) getChildAt(i);
3845                 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
3846             }
3847             mAmbientState.setHideSensitive(hideSensitive);
3848             if (animate && mAnimationsEnabled) {
3849                 mHideSensitiveNeedsAnimation = true;
3850                 mNeedsAnimation =  true;
3851             }
3852             updateContentHeight();
3853             requestChildrenUpdate();
3854         }
3855     }
3856 
3857     /**
3858      * See {@link AmbientState#setActivatedChild}.
3859      */
3860     public void setActivatedChild(ActivatableNotificationView activatedChild) {
3861         mAmbientState.setActivatedChild(activatedChild);
3862         if (mAnimationsEnabled) {
3863             mActivateNeedsAnimation = true;
3864             mNeedsAnimation =  true;
3865         }
3866         requestChildrenUpdate();
3867     }
3868 
3869     public ActivatableNotificationView getActivatedChild() {
3870         return mAmbientState.getActivatedChild();
3871     }
3872 
3873     private void applyCurrentState() {
3874         mCurrentStackScrollState.apply();
3875         if (mListener != null) {
3876             mListener.onChildLocationsChanged();
3877         }
3878         runAnimationFinishedRunnables();
3879         setAnimationRunning(false);
3880         updateBackground();
3881         updateViewShadows();
3882         updateClippingToTopRoundedCorner();
3883     }
3884 
3885     private void updateViewShadows() {
3886         // we need to work around an issue where the shadow would not cast between siblings when
3887         // their z difference is between 0 and 0.1
3888 
3889         // Lefts first sort by Z difference
3890         for (int i = 0; i < getChildCount(); i++) {
3891             ExpandableView child = (ExpandableView) getChildAt(i);
3892             if (child.getVisibility() != GONE) {
3893                 mTmpSortedChildren.add(child);
3894             }
3895         }
3896         Collections.sort(mTmpSortedChildren, mViewPositionComparator);
3897 
3898         // Now lets update the shadow for the views
3899         ExpandableView previous = null;
3900         for (int i = 0; i < mTmpSortedChildren.size(); i++) {
3901             ExpandableView expandableView = mTmpSortedChildren.get(i);
3902             float translationZ = expandableView.getTranslationZ();
3903             float otherZ = previous == null ? translationZ : previous.getTranslationZ();
3904             float diff = otherZ - translationZ;
3905             if (diff <= 0.0f || diff >= FakeShadowView.SHADOW_SIBLING_TRESHOLD) {
3906                 // There is no fake shadow to be drawn
3907                 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
3908             } else {
3909                 float yLocation = previous.getTranslationY() + previous.getActualHeight() -
3910                         expandableView.getTranslationY() - previous.getExtraBottomPadding();
3911                 expandableView.setFakeShadowIntensity(
3912                         diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD,
3913                         previous.getOutlineAlpha(), (int) yLocation,
3914                         previous.getOutlineTranslation());
3915             }
3916             previous = expandableView;
3917         }
3918 
3919         mTmpSortedChildren.clear();
3920     }
3921 
3922     /**
3923      * Update colors of "dismiss" and "empty shade" views.
3924      *
3925      * @param lightTheme True if light theme should be used.
3926      */
3927     public void updateDecorViews(boolean lightTheme) {
3928         if (lightTheme == mUsingLightTheme) {
3929             return;
3930         }
3931         mUsingLightTheme = lightTheme;
3932         Context context = new ContextThemeWrapper(mContext,
3933                 lightTheme ? R.style.Theme_SystemUI_Light : R.style.Theme_SystemUI);
3934         final int textColor = Utils.getColorAttr(context, R.attr.wallpaperTextColor);
3935         mFooterView.setTextColor(textColor);
3936         mEmptyShadeView.setTextColor(textColor);
3937     }
3938 
3939     public void goToFullShade(long delay) {
3940         mGoToFullShadeNeedsAnimation = true;
3941         mGoToFullShadeDelay = delay;
3942         mNeedsAnimation = true;
3943         requestChildrenUpdate();
3944     }
3945 
3946     public void cancelExpandHelper() {
3947         mExpandHelper.cancel();
3948     }
3949 
3950     public void setIntrinsicPadding(int intrinsicPadding) {
3951         mIntrinsicPadding = intrinsicPadding;
3952         mAmbientState.setIntrinsicPadding(intrinsicPadding);
3953     }
3954 
3955     public int getIntrinsicPadding() {
3956         return mIntrinsicPadding;
3957     }
3958 
3959     /**
3960      * @return the y position of the first notification
3961      */
3962     public float getNotificationsTopY() {
3963         return mTopPadding + getStackTranslation();
3964     }
3965 
3966     @Override
3967     public boolean shouldDelayChildPressedState() {
3968         return true;
3969     }
3970 
3971     /**
3972      * See {@link AmbientState#setDark}.
3973      */
3974     public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) {
3975         if (mAmbientState.isDark() == dark) {
3976             return;
3977         }
3978         mAmbientState.setDark(dark);
3979         if (animate && mAnimationsEnabled) {
3980             mDarkNeedsAnimation = true;
3981             mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
3982             mNeedsAnimation =  true;
3983         } else {
3984             if (mDarkAmountAnimator != null) {
3985                 mDarkAmountAnimator.cancel();
3986             }
3987             setDarkAmount(dark ? 1f : 0f);
3988             updateBackground();
3989         }
3990         requestChildrenUpdate();
3991         applyCurrentBackgroundBounds();
3992         updateWillNotDraw();
3993         notifyHeightChangeListener(mShelf);
3994     }
3995 
3996     private void updateAntiBurnInTranslation() {
3997         setTranslationX(mAntiBurnInOffsetX * mDarkAmount);
3998     }
3999 
4000     /**
4001      * Updates whether or not this Layout will perform its own custom drawing (i.e. whether or
4002      * not {@link #onDraw(Canvas)} is called). This method should be called whenever the
4003      * {@link #mAmbientState}'s dark mode is toggled.
4004      */
4005     private void updateWillNotDraw() {
4006         boolean willDraw = mShouldDrawNotificationBackground || DEBUG;
4007         setWillNotDraw(!willDraw);
4008     }
4009 
4010     private void setDarkAmount(float darkAmount) {
4011         mDarkAmount = darkAmount;
4012         boolean wasFullyDark = mAmbientState.isFullyDark();
4013         mAmbientState.setDarkAmount(darkAmount);
4014         if (mAmbientState.isFullyDark() != wasFullyDark) {
4015             updateContentHeight();
4016             DozeParameters dozeParameters = DozeParameters.getInstance(mContext);
4017             if (mAmbientState.isFullyDark() && dozeParameters.shouldControlScreenOff()) {
4018                 mShelf.fadeInTranslating();
4019             }
4020         }
4021         updateAlgorithmHeightAndPadding();
4022         updateBackgroundDimming();
4023         updateAntiBurnInTranslation();
4024         requestChildrenUpdate();
4025     }
4026 
4027     public float getDarkAmount() {
4028         return mDarkAmount;
4029     }
4030 
4031     private void startDarkAmountAnimation() {
4032         ObjectAnimator darkAnimator = ObjectAnimator.ofFloat(this, DARK_AMOUNT, mDarkAmount,
4033                 mAmbientState.isDark() ? 1f : 0);
4034         darkAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP);
4035         darkAnimator.setInterpolator(Interpolators.ALPHA_IN);
4036         darkAnimator.addListener(new AnimatorListenerAdapter() {
4037             @Override
4038             public void onAnimationEnd(Animator animation) {
4039                 mDarkAmountAnimator = null;
4040             }
4041         });
4042         if (mDarkAmountAnimator != null) {
4043             mDarkAmountAnimator.cancel();
4044         }
4045         mDarkAmountAnimator = darkAnimator;
4046         mDarkAmountAnimator.start();
4047     }
4048 
4049     private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
4050         if (screenLocation == null || screenLocation.y < mTopPadding) {
4051             return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
4052         }
4053         if (screenLocation.y > getBottomMostNotificationBottom()) {
4054             return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW;
4055         }
4056         View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y);
4057         if (child != null) {
4058             return getNotGoneIndex(child);
4059         } else {
4060             return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
4061         }
4062     }
4063 
4064     private int getNotGoneIndex(View child) {
4065         int count = getChildCount();
4066         int notGoneIndex = 0;
4067         for (int i = 0; i < count; i++) {
4068             View v = getChildAt(i);
4069             if (child == v) {
4070                 return notGoneIndex;
4071             }
4072             if (v.getVisibility() != View.GONE) {
4073                 notGoneIndex++;
4074             }
4075         }
4076         return -1;
4077     }
4078 
4079     public void setFooterView(@NonNull FooterView footerView) {
4080         int index = -1;
4081         if (mFooterView != null) {
4082             index = indexOfChild(mFooterView);
4083             removeView(mFooterView);
4084         }
4085         mFooterView = footerView;
4086         addView(mFooterView, index);
4087     }
4088 
4089     public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
4090         int index = -1;
4091         if (mEmptyShadeView != null) {
4092             index = indexOfChild(mEmptyShadeView);
4093             removeView(mEmptyShadeView);
4094         }
4095         mEmptyShadeView = emptyShadeView;
4096         addView(mEmptyShadeView, index);
4097     }
4098 
4099     public void updateEmptyShadeView(boolean visible) {
4100         mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
4101 
4102         int oldTextRes = mEmptyShadeView.getTextResource();
4103         int newTextRes = mStatusBar.areNotificationsHidden()
4104                 ? R.string.dnd_suppressing_shade_text : R.string.empty_shade_text;
4105         if (oldTextRes != newTextRes) {
4106             mEmptyShadeView.setText(newTextRes);
4107         }
4108     }
4109 
4110     public void updateFooterView(boolean visible, boolean showDismissView) {
4111         if (mFooterView == null) {
4112             return;
4113         }
4114         boolean animate = mIsExpanded && mAnimationsEnabled;
4115         mFooterView.setVisible(visible, animate);
4116         mFooterView.setSecondaryVisible(showDismissView, animate);
4117     }
4118 
4119     public void setDismissAllInProgress(boolean dismissAllInProgress) {
4120         mDismissAllInProgress = dismissAllInProgress;
4121         mAmbientState.setDismissAllInProgress(dismissAllInProgress);
4122         handleDismissAllClipping();
4123     }
4124 
4125     private void handleDismissAllClipping() {
4126         final int count = getChildCount();
4127         boolean previousChildWillBeDismissed = false;
4128         for (int i = 0; i < count; i++) {
4129             ExpandableView child = (ExpandableView) getChildAt(i);
4130             if (child.getVisibility() == GONE) {
4131                 continue;
4132             }
4133             if (mDismissAllInProgress && previousChildWillBeDismissed) {
4134                 child.setMinClipTopAmount(child.getClipTopAmount());
4135             } else {
4136                 child.setMinClipTopAmount(0);
4137             }
4138             previousChildWillBeDismissed = canChildBeDismissed(child);
4139         }
4140     }
4141 
4142     public boolean isFooterViewNotGone() {
4143         return mFooterView != null
4144                 && mFooterView.getVisibility() != View.GONE
4145                 && !mFooterView.willBeGone();
4146     }
4147 
4148     public boolean isFooterViewContentVisible() {
4149         return mFooterView != null && mFooterView.isContentVisible();
4150     }
4151 
4152     public int getFooterViewHeight() {
4153         return mFooterView == null ? 0 : mFooterView.getHeight() + mPaddingBetweenElements;
4154     }
4155 
4156     public int getEmptyShadeViewHeight() {
4157         return mEmptyShadeView.getHeight();
4158     }
4159 
4160     public float getBottomMostNotificationBottom() {
4161         final int count = getChildCount();
4162         float max = 0;
4163         for (int childIdx = 0; childIdx < count; childIdx++) {
4164             ExpandableView child = (ExpandableView) getChildAt(childIdx);
4165             if (child.getVisibility() == GONE) {
4166                 continue;
4167             }
4168             float bottom = child.getTranslationY() + child.getActualHeight()
4169                     - child.getClipBottomAmount();
4170             if (bottom > max) {
4171                 max = bottom;
4172             }
4173         }
4174         return max + getStackTranslation();
4175     }
4176 
4177     public void setStatusBar(StatusBar statusBar) {
4178         this.mStatusBar = statusBar;
4179     }
4180 
4181     public void setGroupManager(NotificationGroupManager groupManager) {
4182         this.mGroupManager = groupManager;
4183     }
4184 
4185     public void onGoToKeyguard() {
4186         requestAnimateEverything();
4187     }
4188 
4189     private void requestAnimateEverything() {
4190         if (mIsExpanded && mAnimationsEnabled) {
4191             mEverythingNeedsAnimation = true;
4192             mNeedsAnimation = true;
4193             requestChildrenUpdate();
4194         }
4195     }
4196 
4197     public boolean isBelowLastNotification(float touchX, float touchY) {
4198         int childCount = getChildCount();
4199         for (int i = childCount - 1; i >= 0; i--) {
4200             ExpandableView child = (ExpandableView) getChildAt(i);
4201             if (child.getVisibility() != View.GONE) {
4202                 float childTop = child.getY();
4203                 if (childTop > touchY) {
4204                     // we are above a notification entirely let's abort
4205                     return false;
4206                 }
4207                 boolean belowChild = touchY > childTop + child.getActualHeight()
4208                         - child.getClipBottomAmount();
4209                 if (child == mFooterView) {
4210                     if(!belowChild && !mFooterView.isOnEmptySpace(touchX - mFooterView.getX(),
4211                                     touchY - childTop)) {
4212                         // We clicked on the dismiss button
4213                         return false;
4214                     }
4215                 } else if (child == mEmptyShadeView) {
4216                     // We arrived at the empty shade view, for which we accept all clicks
4217                     return true;
4218                 } else if (!belowChild){
4219                     // We are on a child
4220                     return false;
4221                 }
4222             }
4223         }
4224         return touchY > mTopPadding + mStackTranslation;
4225     }
4226 
4227     @Override
4228     public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
4229         boolean animated = !mGroupExpandedForMeasure && mAnimationsEnabled
4230                 && (mIsExpanded || changedRow.isPinned());
4231         if (animated) {
4232             mExpandedGroupView = changedRow;
4233             mNeedsAnimation = true;
4234         }
4235         changedRow.setChildrenExpanded(expanded, animated);
4236         if (!mGroupExpandedForMeasure) {
4237             onHeightChanged(changedRow, false /* needsAnimation */);
4238         }
4239         runAfterAnimationFinished(new Runnable() {
4240             @Override
4241             public void run() {
4242                 changedRow.onFinishedExpansionChange();
4243             }
4244         });
4245     }
4246 
4247     @Override
4248     public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
4249         mStatusBar.requestNotificationUpdate();
4250     }
4251 
4252     /** @hide */
4253     @Override
4254     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
4255         super.onInitializeAccessibilityEventInternal(event);
4256         event.setScrollable(mScrollable);
4257         event.setScrollX(mScrollX);
4258         event.setScrollY(mOwnScrollY);
4259         event.setMaxScrollX(mScrollX);
4260         event.setMaxScrollY(getScrollRange());
4261     }
4262 
4263     @Override
4264     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
4265         super.onInitializeAccessibilityNodeInfoInternal(info);
4266         if (mScrollable) {
4267             info.setScrollable(true);
4268             if (mBackwardScrollable) {
4269                 info.addAction(
4270                         AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
4271                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
4272             }
4273             if (mForwardScrollable) {
4274                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
4275                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_DOWN);
4276             }
4277         }
4278         // Talkback only listenes to scroll events of certain classes, let's make us a scrollview
4279         info.setClassName(ScrollView.class.getName());
4280     }
4281 
4282     /** @hide */
4283     @Override
4284     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
4285         if (super.performAccessibilityActionInternal(action, arguments)) {
4286             return true;
4287         }
4288         if (!isEnabled()) {
4289             return false;
4290         }
4291         int direction = -1;
4292         switch (action) {
4293             case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
4294                 // fall through
4295             case android.R.id.accessibilityActionScrollDown:
4296                 direction = 1;
4297                 // fall through
4298             case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
4299                 // fall through
4300             case android.R.id.accessibilityActionScrollUp:
4301                 final int viewportHeight = getHeight() - mPaddingBottom - mTopPadding - mPaddingTop
4302                         - mShelf.getIntrinsicHeight();
4303                 final int targetScrollY = Math.max(0,
4304                         Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
4305                 if (targetScrollY != mOwnScrollY) {
4306                     mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScrollY - mOwnScrollY);
4307                     animateScroll();
4308                     return true;
4309                 }
4310                 break;
4311         }
4312         return false;
4313     }
4314 
4315     @Override
4316     public void onGroupsChanged() {
4317         mStatusBar.requestNotificationUpdate();
4318     }
4319 
4320     public void generateChildOrderChangedEvent() {
4321         if (mIsExpanded && mAnimationsEnabled) {
4322             mGenerateChildOrderChangedEvent = true;
4323             mNeedsAnimation = true;
4324             requestChildrenUpdate();
4325         }
4326     }
4327 
4328     @Override
4329     public int getContainerChildCount() {
4330         return getChildCount();
4331     }
4332 
4333     @Override
4334     public View getContainerChildAt(int i) {
4335         return getChildAt(i);
4336     }
4337 
4338     @Override
4339     public void removeContainerView(View v) {
4340         removeView(v);
4341     }
4342 
4343     @Override
4344     public void addContainerView(View v) {
4345         addView(v);
4346     }
4347 
4348     public void runAfterAnimationFinished(Runnable runnable) {
4349         mAnimationFinishedRunnables.add(runnable);
4350     }
4351 
4352     public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
4353         mHeadsUpManager = headsUpManager;
4354         mAmbientState.setHeadsUpManager(headsUpManager);
4355         mHeadsUpManager.addListener(mRoundnessManager);
4356     }
4357 
4358     public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
4359         if (mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed)) {
4360             mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
4361             mNeedsAnimation = true;
4362             if (!mIsExpanded && !isHeadsUp) {
4363                 row.setHeadsUpAnimatingAway(true);
4364             }
4365             requestChildrenUpdate();
4366         }
4367     }
4368 
4369     public void setShadeExpanded(boolean shadeExpanded) {
4370         mAmbientState.setShadeExpanded(shadeExpanded);
4371         mStateAnimator.setShadeExpanded(shadeExpanded);
4372     }
4373 
4374     /**
4375      * Set the boundary for the bottom heads up position. The heads up will always be above this
4376      * position.
4377      *
4378      * @param height the height of the screen
4379      * @param bottomBarHeight the height of the bar on the bottom
4380      */
4381     public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
4382         mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
4383         mStateAnimator.setHeadsUpAppearHeightBottom(height);
4384         requestChildrenUpdate();
4385     }
4386 
4387     public void setTrackingHeadsUp(ExpandableNotificationRow row) {
4388         mTrackingHeadsUp = row != null;
4389         mRoundnessManager.setTrackingHeadsUp(row);
4390     }
4391 
4392     public void setScrimController(ScrimController scrimController) {
4393         mScrimController = scrimController;
4394         mScrimController.setScrimBehindChangeRunnable(this::updateBackgroundDimming);
4395     }
4396 
4397     public void forceNoOverlappingRendering(boolean force) {
4398         mForceNoOverlappingRendering = force;
4399     }
4400 
4401     @Override
4402     public boolean hasOverlappingRendering() {
4403         return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
4404     }
4405 
4406     public void setAnimationRunning(boolean animationRunning) {
4407         if (animationRunning != mAnimationRunning) {
4408             if (animationRunning) {
4409                 getViewTreeObserver().addOnPreDrawListener(mRunningAnimationUpdater);
4410             } else {
4411                 getViewTreeObserver().removeOnPreDrawListener(mRunningAnimationUpdater);
4412             }
4413             mAnimationRunning = animationRunning;
4414             updateContinuousShadowDrawing();
4415         }
4416     }
4417 
4418     public boolean isExpanded() {
4419         return mIsExpanded;
4420     }
4421 
4422     public void setPulsing(boolean pulsing, boolean animated) {
4423         if (!mPulsing && !pulsing) {
4424             return;
4425         }
4426         mPulsing = pulsing;
4427         mNeedingPulseAnimation = animated ? getFirstChildNotGone() : null;
4428         mAmbientState.setPulsing(pulsing);
4429         updateNotificationAnimationStates();
4430         updateAlgorithmHeightAndPadding();
4431         updateContentHeight();
4432         requestChildrenUpdate();
4433         notifyHeightChangeListener(null, animated);
4434         mNeedsAnimation |= animated;
4435     }
4436 
4437     private void generatePulsingAnimationEvent() {
4438         if (mNeedingPulseAnimation != null) {
4439             int type = mPulsing ? AnimationEvent.ANIMATION_TYPE_PULSE_APPEAR
4440                     : AnimationEvent.ANIMATION_TYPE_PULSE_DISAPPEAR;
4441             mAnimationEvents.add(new AnimationEvent(mNeedingPulseAnimation, type));
4442             mNeedingPulseAnimation = null;
4443         }
4444     }
4445 
4446     public void setFadingOut(boolean fadingOut) {
4447         if (fadingOut != mFadingOut) {
4448             mFadingOut = fadingOut;
4449             updateFadingState();
4450         }
4451     }
4452 
4453     public void setParentNotFullyVisible(boolean parentNotFullyVisible) {
4454         if (mScrimController == null) {
4455             // we're not set up yet.
4456             return;
4457         }
4458         if (parentNotFullyVisible != mParentNotFullyVisible) {
4459             mParentNotFullyVisible = parentNotFullyVisible;
4460             updateFadingState();
4461         }
4462     }
4463 
4464     private void updateFadingState() {
4465         applyCurrentBackgroundBounds();
4466         updateSrcDrawing();
4467     }
4468 
4469     @Override
4470     public void setAlpha(@FloatRange(from = 0.0, to = 1.0) float alpha) {
4471         super.setAlpha(alpha);
4472         setFadingOut(alpha != 1.0f);
4473     }
4474 
4475     public void setQsExpanded(boolean qsExpanded) {
4476         mQsExpanded = qsExpanded;
4477         updateAlgorithmLayoutMinHeight();
4478         updateScrollability();
4479     }
4480 
4481     public void setQsExpansionFraction(float qsExpansionFraction) {
4482         mQsExpansionFraction = qsExpansionFraction;
4483     }
4484 
4485     public void setOwnScrollY(int ownScrollY) {
4486         if (ownScrollY != mOwnScrollY) {
4487             // We still want to call the normal scrolled changed for accessibility reasons
4488             onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY);
4489             mOwnScrollY = ownScrollY;
4490             updateForwardAndBackwardScrollability();
4491             requestChildrenUpdate();
4492         }
4493     }
4494 
4495     public void setShelf(NotificationShelf shelf) {
4496         int index = -1;
4497         if (mShelf != null) {
4498             index = indexOfChild(mShelf);
4499             removeView(mShelf);
4500         }
4501         mShelf = shelf;
4502         addView(mShelf, index);
4503         mAmbientState.setShelf(shelf);
4504         mStateAnimator.setShelf(shelf);
4505         shelf.bind(mAmbientState, this);
4506     }
4507 
4508     public NotificationShelf getNotificationShelf() {
4509         return mShelf;
4510     }
4511 
4512     public void setMaxDisplayedNotifications(int maxDisplayedNotifications) {
4513         if (mMaxDisplayedNotifications != maxDisplayedNotifications) {
4514             mMaxDisplayedNotifications = maxDisplayedNotifications;
4515             updateContentHeight();
4516             notifyHeightChangeListener(mShelf);
4517         }
4518     }
4519 
4520     public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) {
4521         mShouldShowShelfOnly =  shouldShowShelfOnly;
4522         updateAlgorithmLayoutMinHeight();
4523     }
4524 
4525     public int getMinExpansionHeight() {
4526         return mShelf.getIntrinsicHeight() - (mShelf.getIntrinsicHeight() - mStatusBarHeight) / 2;
4527     }
4528 
4529     public void setInHeadsUpPinnedMode(boolean inHeadsUpPinnedMode) {
4530         mInHeadsUpPinnedMode = inHeadsUpPinnedMode;
4531         updateClipping();
4532     }
4533 
4534     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
4535         mHeadsUpAnimatingAway = headsUpAnimatingAway;
4536         updateClipping();
4537     }
4538 
4539     public void setStatusBarState(int statusBarState) {
4540         mStatusBarState = statusBarState;
4541         mAmbientState.setStatusBarState(statusBarState);
4542     }
4543 
4544     public void setExpandingVelocity(float expandingVelocity) {
4545         mAmbientState.setExpandingVelocity(expandingVelocity);
4546     }
4547 
4548     public float getOpeningHeight() {
4549         if (mEmptyShadeView.getVisibility() == GONE) {
4550             return getMinExpansionHeight();
4551         } else {
4552             return getAppearEndPosition();
4553         }
4554     }
4555 
4556     public void setIsFullWidth(boolean isFullWidth) {
4557         mAmbientState.setPanelFullWidth(isFullWidth);
4558     }
4559 
4560     public void setUnlockHintRunning(boolean running) {
4561         mAmbientState.setUnlockHintRunning(running);
4562     }
4563 
4564     public void setQsCustomizerShowing(boolean isShowing) {
4565         mAmbientState.setQsCustomizerShowing(isShowing);
4566         requestChildrenUpdate();
4567     }
4568 
4569     public void setHeadsUpGoingAwayAnimationsAllowed(boolean headsUpGoingAwayAnimationsAllowed) {
4570         mHeadsUpGoingAwayAnimationsAllowed = headsUpGoingAwayAnimationsAllowed;
4571     }
4572 
4573     public void setAntiBurnInOffsetX(int antiBurnInOffsetX) {
4574         mAntiBurnInOffsetX = antiBurnInOffsetX;
4575         updateAntiBurnInTranslation();
4576     }
4577 
4578     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
4579         pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s"
4580                         + " alpha:%f scrollY:%d maxTopPadding:%d showShelfOnly=%s"
4581                         + " qsExpandFraction=%f]",
4582                 this.getClass().getSimpleName(),
4583                 mPulsing ? "T":"f",
4584                 mAmbientState.isQsCustomizerShowing() ? "T":"f",
4585                 getVisibility() == View.VISIBLE ? "visible"
4586                         : getVisibility() == View.GONE ? "gone"
4587                                 : "invisible",
4588                 getAlpha(),
4589                 mAmbientState.getScrollY(),
4590                 mMaxTopPadding,
4591                 mShouldShowShelfOnly ? "T":"f",
4592                 mQsExpansionFraction));
4593     }
4594 
4595     public boolean isFullyDark() {
4596         return mAmbientState.isFullyDark();
4597     }
4598 
4599     /**
4600      * Add a listener whenever the expanded height changes. The first value passed as an argument
4601      * is the expanded height and the second one is the appearFraction.
4602      *
4603      * @param listener the listener to notify.
4604      */
4605     public void addOnExpandedHeightListener(BiConsumer<Float, Float> listener) {
4606         mExpandedHeightListeners.add(listener);
4607     }
4608 
4609     /**
4610      * Stop a listener from listening to the expandedHeight.
4611      */
4612     public void removeOnExpandedHeightListener(BiConsumer<Float, Float> listener) {
4613         mExpandedHeightListeners.remove(listener);
4614     }
4615 
4616     public void setHeadsUpAppearanceController(
4617             HeadsUpAppearanceController headsUpAppearanceController) {
4618         mHeadsUpAppearanceController = headsUpAppearanceController;
4619     }
4620 
4621     /**
4622      * A listener that is notified when the empty space below the notifications is clicked on
4623      */
4624     public interface OnEmptySpaceClickListener {
4625         void onEmptySpaceClicked(float x, float y);
4626     }
4627 
4628     /**
4629      * A listener that gets notified when the overscroll at the top has changed.
4630      */
4631     public interface OnOverscrollTopChangedListener {
4632 
4633         /**
4634          * Notifies a listener that the overscroll has changed.
4635          *
4636          * @param amount the amount of overscroll, in pixels
4637          * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
4638          *                     unrubberbanded motion to directly expand overscroll view (e.g expand
4639          *                     QS)
4640          */
4641         void onOverscrollTopChanged(float amount, boolean isRubberbanded);
4642 
4643         /**
4644          * Notify a listener that the scroller wants to escape from the scrolling motion and
4645          * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
4646          *
4647          * @param velocity The velocity that the Scroller had when over flinging
4648          * @param open Should the fling open or close the overscroll view.
4649          */
4650         void flingTopOverscroll(float velocity, boolean open);
4651     }
4652 
4653     private class NotificationSwipeHelper extends SwipeHelper
4654             implements NotificationSwipeActionHelper {
4655         private static final long COVER_MENU_DELAY = 4000;
4656         private Runnable mFalsingCheck;
4657         private Handler mHandler;
4658 
4659         public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) {
4660             super(swipeDirection, callback, context);
4661             mHandler = new Handler();
4662             mFalsingCheck = new Runnable() {
4663                 @Override
4664                 public void run() {
4665                     resetExposedMenuView(true /* animate */, true /* force */);
4666                 }
4667             };
4668         }
4669 
4670         @Override
4671         public void onDownUpdate(View currView, MotionEvent ev) {
4672             mTranslatingParentView = currView;
4673             if (mCurrMenuRow != null) {
4674                 mCurrMenuRow.onTouchEvent(currView, ev, 0 /* velocity */);
4675             }
4676             mCurrMenuRow = null;
4677             mHandler.removeCallbacks(mFalsingCheck);
4678 
4679             // Slide back any notifications that might be showing a menu
4680             resetExposedMenuView(true /* animate */, false /* force */);
4681 
4682             if (currView instanceof ExpandableNotificationRow) {
4683                 ExpandableNotificationRow row = (ExpandableNotificationRow) currView;
4684                 mCurrMenuRow = row.createMenu();
4685                 mCurrMenuRow.setSwipeActionHelper(NotificationSwipeHelper.this);
4686                 mCurrMenuRow.setMenuClickListener(NotificationStackScrollLayout.this);
4687                 mCurrMenuRow.onTouchEvent(currView, ev, 0 /* velocity */);
4688             }
4689         }
4690 
4691         @Override
4692         public void onMoveUpdate(View view, MotionEvent ev, float translation, float delta) {
4693             mHandler.removeCallbacks(mFalsingCheck);
4694             if (mCurrMenuRow != null) {
4695                 mCurrMenuRow.onTouchEvent(view, ev, 0 /* velocity */);
4696             }
4697         }
4698 
4699         @Override
4700         public boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
4701                 float translation) {
4702             if (mCurrMenuRow != null) {
4703                 return mCurrMenuRow.onTouchEvent(animView, ev, velocity);
4704             }
4705             return false;
4706         }
4707 
4708         @Override
4709         public void dismissChild(final View view, float velocity,
4710                 boolean useAccelerateInterpolator) {
4711             super.dismissChild(view, velocity, useAccelerateInterpolator);
4712             if (mIsExpanded) {
4713                 // We don't want to quick-dismiss when it's a heads up as this might lead to closing
4714                 // of the panel early.
4715                 handleChildViewDismissed(view);
4716             }
4717             mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */,
4718                     false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
4719                     false /* resetMenu */);
4720             handleMenuCoveredOrDismissed();
4721         }
4722 
4723         @Override
4724         public void snapChild(final View animView, final float targetLeft, float velocity) {
4725             super.snapChild(animView, targetLeft, velocity);
4726             onDragCancelled(animView);
4727             if (targetLeft == 0) {
4728                 handleMenuCoveredOrDismissed();
4729             }
4730         }
4731 
4732         @Override
4733         public void snooze(StatusBarNotification sbn, SnoozeOption snoozeOption) {
4734             mStatusBar.setNotificationSnoozed(sbn, snoozeOption);
4735         }
4736 
4737         public boolean isFalseGesture(MotionEvent ev) {
4738             return super.isFalseGesture(ev);
4739         }
4740 
4741         private void handleMenuCoveredOrDismissed() {
4742             if (mMenuExposedView != null && mMenuExposedView == mTranslatingParentView) {
4743                 mMenuExposedView = null;
4744             }
4745         }
4746 
4747         @Override
4748         public Animator getViewTranslationAnimator(View v, float target,
4749                 AnimatorUpdateListener listener) {
4750             if (v instanceof ExpandableNotificationRow) {
4751                 return ((ExpandableNotificationRow) v).getTranslateViewAnimator(target, listener);
4752             } else {
4753                 return super.getViewTranslationAnimator(v, target, listener);
4754             }
4755         }
4756 
4757         @Override
4758         public void setTranslation(View v, float translate) {
4759             ((ExpandableView) v).setTranslation(translate);
4760         }
4761 
4762         @Override
4763         public float getTranslation(View v) {
4764             return ((ExpandableView) v).getTranslation();
4765         }
4766 
4767         @Override
4768         public void dismiss(View animView, float velocity) {
4769             dismissChild(animView, velocity,
4770                     !swipedFastEnough(0, 0) /* useAccelerateInterpolator */);
4771         }
4772 
4773         @Override
4774         public void snap(View animView, float targetLeft, float velocity) {
4775             snapChild(animView, targetLeft, velocity);
4776         }
4777 
4778         @Override
4779         public boolean swipedFarEnough(float translation, float viewSize) {
4780             return swipedFarEnough();
4781         }
4782 
4783         @Override
4784         public boolean swipedFastEnough(float translation, float velocity) {
4785             return swipedFastEnough();
4786         }
4787 
4788         @Override
4789         public float getMinDismissVelocity() {
4790             return getEscapeVelocity();
4791         }
4792 
4793         public void onMenuShown(View animView) {
4794             onDragCancelled(animView);
4795 
4796             // If we're on the lockscreen we want to false this.
4797             if (isAntiFalsingNeeded()) {
4798                 mHandler.removeCallbacks(mFalsingCheck);
4799                 mHandler.postDelayed(mFalsingCheck, COVER_MENU_DELAY);
4800             }
4801         }
4802 
4803         public void closeControlsIfOutsideTouch(MotionEvent ev) {
4804             NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts();
4805             View view = null;
4806             if (guts != null && !guts.getGutsContent().isLeavebehind()) {
4807                 // Only close visible guts if they're not a leavebehind.
4808                 view = guts;
4809             } else if (mCurrMenuRow != null && mCurrMenuRow.isMenuVisible()
4810                     && mTranslatingParentView != null) {
4811                 // Checking menu
4812                 view = mTranslatingParentView;
4813             }
4814             if (view != null && !isTouchInView(ev, view)) {
4815                 // Touch was outside visible guts / menu notification, close what's visible
4816                 mStatusBar.getGutsManager().closeAndSaveGuts(false /* removeLeavebehind */,
4817                         false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */,
4818                         false /* resetMenu */);
4819                 resetExposedMenuView(true /* animate */, true /* force */);
4820             }
4821         }
4822 
4823         public void resetExposedMenuView(boolean animate, boolean force) {
4824             if (mMenuExposedView == null
4825                     || (!force && mMenuExposedView == mTranslatingParentView)) {
4826                 // If no menu is showing or it's showing for this view we do nothing.
4827                 return;
4828             }
4829             final View prevMenuExposedView = mMenuExposedView;
4830             if (animate) {
4831                 Animator anim = getViewTranslationAnimator(prevMenuExposedView,
4832                         0 /* leftTarget */, null /* updateListener */);
4833                 if (anim != null) {
4834                     anim.start();
4835                 }
4836             } else if (mMenuExposedView instanceof ExpandableNotificationRow) {
4837                 ExpandableNotificationRow row = (ExpandableNotificationRow) mMenuExposedView;
4838                 if (!row.isRemoved()) {
4839                     row.resetTranslation();
4840                 }
4841             }
4842             mMenuExposedView = null;
4843         }
4844     }
4845 
4846     private boolean isTouchInView(MotionEvent ev, View view) {
4847         if (view == null) {
4848             return false;
4849         }
4850         final int height = (view instanceof ExpandableView)
4851                 ? ((ExpandableView) view).getActualHeight()
4852                 : view.getHeight();
4853         final int rx = (int) ev.getRawX();
4854         final int ry = (int) ev.getRawY();
4855         view.getLocationOnScreen(mTempInt2);
4856         final int x = mTempInt2[0];
4857         final int y = mTempInt2[1];
4858         Rect rect = new Rect(x, y, x + view.getWidth(), y + height);
4859         boolean ret = rect.contains(rx, ry);
4860         return ret;
4861     }
4862 
4863     private void updateContinuousShadowDrawing() {
4864         boolean continuousShadowUpdate = mAnimationRunning
4865                 || !mAmbientState.getDraggedViews().isEmpty();
4866         if (continuousShadowUpdate != mContinuousShadowUpdate) {
4867             if (continuousShadowUpdate) {
4868                 getViewTreeObserver().addOnPreDrawListener(mShadowUpdater);
4869             } else {
4870                 getViewTreeObserver().removeOnPreDrawListener(mShadowUpdater);
4871             }
4872             mContinuousShadowUpdate = continuousShadowUpdate;
4873         }
4874     }
4875 
4876     @Override
4877     public void resetExposedMenuView(boolean animate, boolean force) {
4878         mSwipeHelper.resetExposedMenuView(animate, force);
4879     }
4880 
4881     public void closeControlsIfOutsideTouch(MotionEvent ev) {
4882         mSwipeHelper.closeControlsIfOutsideTouch(ev);
4883     }
4884 
4885     static class AnimationEvent {
4886 
4887         static AnimationFilter[] FILTERS = new AnimationFilter[] {
4888 
4889                 // ANIMATION_TYPE_ADD
4890                 new AnimationFilter()
4891                         .animateShadowAlpha()
4892                         .animateHeight()
4893                         .animateTopInset()
4894                         .animateY()
4895                         .animateZ()
4896                         .hasDelays(),
4897 
4898                 // ANIMATION_TYPE_REMOVE
4899                 new AnimationFilter()
4900                         .animateShadowAlpha()
4901                         .animateHeight()
4902                         .animateTopInset()
4903                         .animateY()
4904                         .animateZ()
4905                         .hasDelays(),
4906 
4907                 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
4908                 new AnimationFilter()
4909                         .animateShadowAlpha()
4910                         .animateHeight()
4911                         .animateTopInset()
4912                         .animateY()
4913                         .animateZ()
4914                         .hasDelays(),
4915 
4916                 // ANIMATION_TYPE_TOP_PADDING_CHANGED
4917                 new AnimationFilter()
4918                         .animateShadowAlpha()
4919                         .animateHeight()
4920                         .animateTopInset()
4921                         .animateY()
4922                         .animateDimmed()
4923                         .animateZ(),
4924 
4925                 // ANIMATION_TYPE_START_DRAG
4926                 new AnimationFilter()
4927                         .animateShadowAlpha(),
4928 
4929                 // ANIMATION_TYPE_SNAP_BACK
4930                 new AnimationFilter()
4931                         .animateShadowAlpha()
4932                         .animateHeight(),
4933 
4934                 // ANIMATION_TYPE_ACTIVATED_CHILD
4935                 new AnimationFilter()
4936                         .animateZ(),
4937 
4938                 // ANIMATION_TYPE_DIMMED
4939                 new AnimationFilter()
4940                         .animateDimmed(),
4941 
4942                 // ANIMATION_TYPE_CHANGE_POSITION
4943                 new AnimationFilter()
4944                         .animateAlpha() // maybe the children change positions
4945                         .animateShadowAlpha()
4946                         .animateHeight()
4947                         .animateTopInset()
4948                         .animateY()
4949                         .animateZ(),
4950 
4951                 // ANIMATION_TYPE_DARK
4952                 null, // Unused
4953 
4954                 // ANIMATION_TYPE_GO_TO_FULL_SHADE
4955                 new AnimationFilter()
4956                         .animateShadowAlpha()
4957                         .animateHeight()
4958                         .animateTopInset()
4959                         .animateY()
4960                         .animateDimmed()
4961                         .animateZ()
4962                         .hasDelays(),
4963 
4964                 // ANIMATION_TYPE_HIDE_SENSITIVE
4965                 new AnimationFilter()
4966                         .animateHideSensitive(),
4967 
4968                 // ANIMATION_TYPE_VIEW_RESIZE
4969                 new AnimationFilter()
4970                         .animateShadowAlpha()
4971                         .animateHeight()
4972                         .animateTopInset()
4973                         .animateY()
4974                         .animateZ(),
4975 
4976                 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
4977                 new AnimationFilter()
4978                         .animateAlpha()
4979                         .animateShadowAlpha()
4980                         .animateHeight()
4981                         .animateTopInset()
4982                         .animateY()
4983                         .animateZ(),
4984 
4985                 // ANIMATION_TYPE_HEADS_UP_APPEAR
4986                 new AnimationFilter()
4987                         .animateShadowAlpha()
4988                         .animateHeight()
4989                         .animateTopInset()
4990                         .animateY()
4991                         .animateZ(),
4992 
4993                 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
4994                 new AnimationFilter()
4995                         .animateShadowAlpha()
4996                         .animateHeight()
4997                         .animateTopInset()
4998                         .animateY()
4999                         .animateZ()
5000                         .hasDelays(),
5001 
5002                 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
5003                 new AnimationFilter()
5004                         .animateShadowAlpha()
5005                         .animateHeight()
5006                         .animateTopInset()
5007                         .animateY()
5008                         .animateZ()
5009                         .hasDelays(),
5010 
5011                 // ANIMATION_TYPE_HEADS_UP_OTHER
5012                 new AnimationFilter()
5013                         .animateShadowAlpha()
5014                         .animateHeight()
5015                         .animateTopInset()
5016                         .animateY()
5017                         .animateZ(),
5018 
5019                 // ANIMATION_TYPE_EVERYTHING
5020                 new AnimationFilter()
5021                         .animateAlpha()
5022                         .animateShadowAlpha()
5023                         .animateDark()
5024                         .animateDimmed()
5025                         .animateHideSensitive()
5026                         .animateHeight()
5027                         .animateTopInset()
5028                         .animateY()
5029                         .animateZ(),
5030 
5031                 // ANIMATION_TYPE_PULSE_APPEAR
5032                 new AnimationFilter()
5033                         .animateAlpha()
5034                         .animateY(),
5035 
5036                 // ANIMATION_TYPE_PULSE_DISAPPEAR
5037                 new AnimationFilter()
5038                         .animateAlpha()
5039                         .animateY(),
5040         };
5041 
5042         static int[] LENGTHS = new int[] {
5043 
5044                 // ANIMATION_TYPE_ADD
5045                 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
5046 
5047                 // ANIMATION_TYPE_REMOVE
5048                 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
5049 
5050                 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
5051                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
5052 
5053                 // ANIMATION_TYPE_TOP_PADDING_CHANGED
5054                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
5055 
5056                 // ANIMATION_TYPE_START_DRAG
5057                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
5058 
5059                 // ANIMATION_TYPE_SNAP_BACK
5060                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
5061 
5062                 // ANIMATION_TYPE_ACTIVATED_CHILD
5063                 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
5064 
5065                 // ANIMATION_TYPE_DIMMED
5066                 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
5067 
5068                 // ANIMATION_TYPE_CHANGE_POSITION
5069                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
5070 
5071                 // ANIMATION_TYPE_DARK
5072                 StackStateAnimator.ANIMATION_DURATION_WAKEUP,
5073 
5074                 // ANIMATION_TYPE_GO_TO_FULL_SHADE
5075                 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
5076 
5077                 // ANIMATION_TYPE_HIDE_SENSITIVE
5078                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
5079 
5080                 // ANIMATION_TYPE_VIEW_RESIZE
5081                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
5082 
5083                 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
5084                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
5085 
5086                 // ANIMATION_TYPE_HEADS_UP_APPEAR
5087                 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
5088 
5089                 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
5090                 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
5091 
5092                 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
5093                 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
5094 
5095                 // ANIMATION_TYPE_HEADS_UP_OTHER
5096                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
5097 
5098                 // ANIMATION_TYPE_EVERYTHING
5099                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
5100 
5101                 // ANIMATION_TYPE_PULSE_APPEAR
5102                 KeyguardSliceView.DEFAULT_ANIM_DURATION,
5103 
5104                 // ANIMATION_TYPE_PULSE_DISAPPEAR
5105                 KeyguardSliceView.DEFAULT_ANIM_DURATION / 2,
5106         };
5107 
5108         static final int ANIMATION_TYPE_ADD = 0;
5109         static final int ANIMATION_TYPE_REMOVE = 1;
5110         static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
5111         static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
5112         static final int ANIMATION_TYPE_START_DRAG = 4;
5113         static final int ANIMATION_TYPE_SNAP_BACK = 5;
5114         static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
5115         static final int ANIMATION_TYPE_DIMMED = 7;
5116         static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
5117         static final int ANIMATION_TYPE_DARK = 9;
5118         static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
5119         static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
5120         static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
5121         static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13;
5122         static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14;
5123         static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15;
5124         static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 16;
5125         static final int ANIMATION_TYPE_HEADS_UP_OTHER = 17;
5126         static final int ANIMATION_TYPE_EVERYTHING = 18;
5127         static final int ANIMATION_TYPE_PULSE_APPEAR = 19;
5128         static final int ANIMATION_TYPE_PULSE_DISAPPEAR = 20;
5129 
5130         static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
5131         static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
5132 
5133         final long eventStartTime;
5134         final View changingView;
5135         final int animationType;
5136         final AnimationFilter filter;
5137         final long length;
5138         View viewAfterChangingView;
5139         int darkAnimationOriginIndex;
5140         boolean headsUpFromBottom;
5141 
5142         AnimationEvent(View view, int type) {
5143             this(view, type, LENGTHS[type]);
5144         }
5145 
5146         AnimationEvent(View view, int type, AnimationFilter filter) {
5147             this(view, type, LENGTHS[type], filter);
5148         }
5149 
5150         AnimationEvent(View view, int type, long length) {
5151             this(view, type, length, FILTERS[type]);
5152         }
5153 
5154         AnimationEvent(View view, int type, long length, AnimationFilter filter) {
5155             eventStartTime = AnimationUtils.currentAnimationTimeMillis();
5156             changingView = view;
5157             animationType = type;
5158             this.length = length;
5159             this.filter = filter;
5160         }
5161 
5162         /**
5163          * Combines the length of several animation events into a single value.
5164          *
5165          * @param events The events of the lengths to combine.
5166          * @return The combined length. Depending on the event types, this might be the maximum of
5167          *         all events or the length of a specific event.
5168          */
5169         static long combineLength(ArrayList<AnimationEvent> events) {
5170             long length = 0;
5171             int size = events.size();
5172             for (int i = 0; i < size; i++) {
5173                 AnimationEvent event = events.get(i);
5174                 length = Math.max(length, event.length);
5175                 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
5176                     return event.length;
5177                 }
5178             }
5179             return length;
5180         }
5181     }
5182 }
5183