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