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.annotation.Nullable;
20 import android.content.Context;
21 import android.content.res.Configuration;
22 import android.graphics.Canvas;
23 import android.graphics.Paint;
24 import android.graphics.PointF;
25 import android.util.AttributeSet;
26 import android.util.Log;
27 import android.util.Pair;
28 import android.view.MotionEvent;
29 import android.view.VelocityTracker;
30 import android.view.View;
31 import android.view.ViewConfiguration;
32 import android.view.ViewGroup;
33 import android.view.ViewTreeObserver;
34 import android.view.animation.AnimationUtils;
35 import android.widget.OverScroller;
36 
37 import com.android.systemui.ExpandHelper;
38 import com.android.systemui.R;
39 import com.android.systemui.SwipeHelper;
40 import com.android.systemui.statusbar.ActivatableNotificationView;
41 import com.android.systemui.statusbar.DismissView;
42 import com.android.systemui.statusbar.EmptyShadeView;
43 import com.android.systemui.statusbar.ExpandableNotificationRow;
44 import com.android.systemui.statusbar.ExpandableView;
45 import com.android.systemui.statusbar.NotificationData;
46 import com.android.systemui.statusbar.NotificationOverflowContainer;
47 import com.android.systemui.statusbar.SpeedBumpView;
48 import com.android.systemui.statusbar.StackScrollerDecorView;
49 import com.android.systemui.statusbar.StatusBarState;
50 import com.android.systemui.statusbar.phone.NotificationGroupManager;
51 import com.android.systemui.statusbar.phone.PhoneStatusBar;
52 import com.android.systemui.statusbar.phone.ScrimController;
53 import com.android.systemui.statusbar.policy.HeadsUpManager;
54 import com.android.systemui.statusbar.policy.ScrollAdapter;
55 
56 import java.util.ArrayList;
57 import java.util.HashSet;
58 
59 /**
60  * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
61  */
62 public class NotificationStackScrollLayout extends ViewGroup
63         implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
64         ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener {
65 
66     private static final String TAG = "NotificationStackScrollLayout";
67     private static final boolean DEBUG = false;
68     private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
69     private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
70     private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
71 
72     /**
73      * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
74      */
75     private static final int INVALID_POINTER = -1;
76 
77     private ExpandHelper mExpandHelper;
78     private SwipeHelper mSwipeHelper;
79     private boolean mSwipingInProgress;
80     private int mCurrentStackHeight = Integer.MAX_VALUE;
81 
82     /**
83      * mCurrentStackHeight is the actual stack height, mLastSetStackHeight is the stack height set
84      * externally from {@link #setStackHeight}
85      */
86     private float mLastSetStackHeight;
87     private int mOwnScrollY;
88     private int mMaxLayoutHeight;
89 
90     private VelocityTracker mVelocityTracker;
91     private OverScroller mScroller;
92     private int mTouchSlop;
93     private int mMinimumVelocity;
94     private int mMaximumVelocity;
95     private int mOverflingDistance;
96     private float mMaxOverScroll;
97     private boolean mIsBeingDragged;
98     private int mLastMotionY;
99     private int mDownX;
100     private int mActivePointerId;
101     private boolean mTouchIsClick;
102     private float mInitialTouchX;
103     private float mInitialTouchY;
104 
105     private int mSidePaddings;
106     private Paint mDebugPaint;
107     private int mContentHeight;
108     private int mCollapsedSize;
109     private int mBottomStackSlowDownHeight;
110     private int mBottomStackPeekSize;
111     private int mPaddingBetweenElements;
112     private int mPaddingBetweenElementsDimmed;
113     private int mPaddingBetweenElementsNormal;
114     private int mTopPadding;
115     private int mCollapseSecondCardPadding;
116 
117     /**
118      * The algorithm which calculates the properties for our children
119      */
120     private StackScrollAlgorithm mStackScrollAlgorithm;
121 
122     /**
123      * The current State this Layout is in
124      */
125     private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
126     private AmbientState mAmbientState = new AmbientState();
127     private NotificationGroupManager mGroupManager;
128     private ArrayList<View> mChildrenToAddAnimated = new ArrayList<>();
129     private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
130     private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
131     private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
132     private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>();
133     private ArrayList<View> mChildrenChangingPositions = new ArrayList<>();
134     private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
135     private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
136     private ArrayList<View> mSwipedOutViews = new ArrayList<>();
137     private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
138     private boolean mAnimationsEnabled;
139     private boolean mChangePositionInProgress;
140 
141     /**
142      * The raw amount of the overScroll on the top, which is not rubber-banded.
143      */
144     private float mOverScrolledTopPixels;
145 
146     /**
147      * The raw amount of the overScroll on the bottom, which is not rubber-banded.
148      */
149     private float mOverScrolledBottomPixels;
150     private OnChildLocationsChangedListener mListener;
151     private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
152     private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
153     private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
154     private boolean mNeedsAnimation;
155     private boolean mTopPaddingNeedsAnimation;
156     private boolean mDimmedNeedsAnimation;
157     private boolean mHideSensitiveNeedsAnimation;
158     private boolean mDarkNeedsAnimation;
159     private int mDarkAnimationOriginIndex;
160     private boolean mActivateNeedsAnimation;
161     private boolean mGoToFullShadeNeedsAnimation;
162     private boolean mIsExpanded = true;
163     private boolean mChildrenUpdateRequested;
164     private SpeedBumpView mSpeedBumpView;
165     private boolean mIsExpansionChanging;
166     private boolean mPanelTracking;
167     private boolean mExpandingNotification;
168     private boolean mExpandedInThisMotion;
169     private boolean mScrollingEnabled;
170     private DismissView mDismissView;
171     private EmptyShadeView mEmptyShadeView;
172     private boolean mDismissAllInProgress;
173 
174     /**
175      * Was the scroller scrolled to the top when the down motion was observed?
176      */
177     private boolean mScrolledToTopOnFirstDown;
178     /**
179      * The minimal amount of over scroll which is needed in order to switch to the quick settings
180      * when over scrolling on a expanded card.
181      */
182     private float mMinTopOverScrollToEscape;
183     private int mIntrinsicPadding;
184     private int mNotificationTopPadding;
185     private float mStackTranslation;
186     private float mTopPaddingOverflow;
187     private boolean mDontReportNextOverScroll;
188     private boolean mRequestViewResizeAnimationOnLayout;
189     private boolean mNeedViewResizeAnimation;
190     private View mExpandedGroupView;
191     private boolean mEverythingNeedsAnimation;
192 
193     /**
194      * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
195      * This is needed to avoid scrolling too far after the notification was collapsed in the same
196      * motion.
197      */
198     private int mMaxScrollAfterExpand;
199     private SwipeHelper.LongPressListener mLongPressListener;
200 
201     /**
202      * Should in this touch motion only be scrolling allowed? It's true when the scroller was
203      * animating.
204      */
205     private boolean mOnlyScrollingInThisMotion;
206     private ViewGroup mScrollView;
207     private boolean mInterceptDelegateEnabled;
208     private boolean mDelegateToScrollView;
209     private boolean mDisallowScrollingInThisMotion;
210     private long mGoToFullShadeDelay;
211     private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
212             = new ViewTreeObserver.OnPreDrawListener() {
213         @Override
214         public boolean onPreDraw() {
215             updateChildren();
216             mChildrenUpdateRequested = false;
217             getViewTreeObserver().removeOnPreDrawListener(this);
218             return true;
219         }
220     };
221     private PhoneStatusBar mPhoneStatusBar;
222     private int[] mTempInt2 = new int[2];
223     private boolean mGenerateChildOrderChangedEvent;
224     private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
225     private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>();
226     private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
227             = new HashSet<>();
228     private HeadsUpManager mHeadsUpManager;
229     private boolean mTrackingHeadsUp;
230     private ScrimController mScrimController;
231     private boolean mForceNoOverlappingRendering;
232     private NotificationOverflowContainer mOverflowContainer;
233     private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
234 
NotificationStackScrollLayout(Context context)235     public NotificationStackScrollLayout(Context context) {
236         this(context, null);
237     }
238 
NotificationStackScrollLayout(Context context, AttributeSet attrs)239     public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
240         this(context, attrs, 0);
241     }
242 
NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr)243     public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
244         this(context, attrs, defStyleAttr, 0);
245     }
246 
NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)247     public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
248             int defStyleRes) {
249         super(context, attrs, defStyleAttr, defStyleRes);
250         int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
251         int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
252         mExpandHelper = new ExpandHelper(getContext(), this,
253                 minHeight, maxHeight);
254         mExpandHelper.setEventSource(this);
255         mExpandHelper.setScrollAdapter(this);
256 
257         mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext());
258         mSwipeHelper.setLongPressListener(mLongPressListener);
259         initView(context);
260         if (DEBUG) {
261             setWillNotDraw(false);
262             mDebugPaint = new Paint();
263             mDebugPaint.setColor(0xffff0000);
264             mDebugPaint.setStrokeWidth(2);
265             mDebugPaint.setStyle(Paint.Style.STROKE);
266         }
267     }
268 
269     @Override
onDraw(Canvas canvas)270     protected void onDraw(Canvas canvas) {
271         if (DEBUG) {
272             int y = mCollapsedSize;
273             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
274             y = (int) (getLayoutHeight() - mBottomStackPeekSize
275                     - mBottomStackSlowDownHeight);
276             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
277             y = (int) (getLayoutHeight() - mBottomStackPeekSize);
278             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
279             y = (int) getLayoutHeight();
280             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
281             y = getHeight() - getEmptyBottomMargin();
282             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
283         }
284     }
285 
initView(Context context)286     private void initView(Context context) {
287         mScroller = new OverScroller(getContext());
288         setFocusable(true);
289         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
290         setClipChildren(false);
291         final ViewConfiguration configuration = ViewConfiguration.get(context);
292         mTouchSlop = configuration.getScaledTouchSlop();
293         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
294         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
295         mOverflingDistance = configuration.getScaledOverflingDistance();
296 
297         mSidePaddings = context.getResources()
298                 .getDimensionPixelSize(R.dimen.notification_side_padding);
299         mCollapsedSize = context.getResources()
300                 .getDimensionPixelSize(R.dimen.notification_min_height);
301         mBottomStackPeekSize = context.getResources()
302                 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
303         mStackScrollAlgorithm = new StackScrollAlgorithm(context);
304         mStackScrollAlgorithm.setDimmed(mAmbientState.isDimmed());
305         mPaddingBetweenElementsDimmed = context.getResources()
306                 .getDimensionPixelSize(R.dimen.notification_padding_dimmed);
307         mPaddingBetweenElementsNormal = context.getResources()
308                 .getDimensionPixelSize(R.dimen.notification_padding);
309         updatePadding(mAmbientState.isDimmed());
310         mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
311                 R.dimen.min_top_overscroll_to_qs);
312         mNotificationTopPadding = getResources().getDimensionPixelSize(
313                 R.dimen.notifications_top_padding);
314         mCollapseSecondCardPadding = getResources().getDimensionPixelSize(
315                 R.dimen.notification_collapse_second_card_padding);
316     }
317 
updatePadding(boolean dimmed)318     private void updatePadding(boolean dimmed) {
319         mPaddingBetweenElements = dimmed && mStackScrollAlgorithm.shouldScaleDimmed()
320                 ? mPaddingBetweenElementsDimmed
321                 : mPaddingBetweenElementsNormal;
322         mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength();
323         updateContentHeight();
324         notifyHeightChangeListener(null);
325     }
326 
notifyHeightChangeListener(ExpandableView view)327     private void notifyHeightChangeListener(ExpandableView view) {
328         if (mOnHeightChangedListener != null) {
329             mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */);
330         }
331     }
332 
333     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)334     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
335         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
336         int mode = MeasureSpec.getMode(widthMeasureSpec);
337         int size = MeasureSpec.getSize(widthMeasureSpec);
338         int childMeasureSpec = MeasureSpec.makeMeasureSpec(size - 2 * mSidePaddings, mode);
339         measureChildren(childMeasureSpec, heightMeasureSpec);
340     }
341 
342     @Override
onLayout(boolean changed, int l, int t, int r, int b)343     protected void onLayout(boolean changed, int l, int t, int r, int b) {
344 
345         // we layout all our children centered on the top
346         float centerX = getWidth() / 2.0f;
347         for (int i = 0; i < getChildCount(); i++) {
348             View child = getChildAt(i);
349             if (child.getVisibility() == GONE) {
350                 continue;
351             }
352             float width = child.getMeasuredWidth();
353             float height = child.getMeasuredHeight();
354             child.layout((int) (centerX - width / 2.0f),
355                     0,
356                     (int) (centerX + width / 2.0f),
357                     (int) height);
358         }
359         setMaxLayoutHeight(getHeight());
360         updateContentHeight();
361         clampScrollPosition();
362         if (mRequestViewResizeAnimationOnLayout) {
363             requestAnimationOnViewResize();
364             mRequestViewResizeAnimationOnLayout = false;
365         }
366         requestChildrenUpdate();
367     }
368 
requestAnimationOnViewResize()369     private void requestAnimationOnViewResize() {
370         if (mIsExpanded && mAnimationsEnabled) {
371             mNeedViewResizeAnimation = true;
372             mNeedsAnimation = true;
373         }
374     }
375 
updateSpeedBumpIndex(int newIndex)376     public void updateSpeedBumpIndex(int newIndex) {
377         int currentIndex = indexOfChild(mSpeedBumpView);
378 
379         // If we are currently layouted before the new speed bump index, we have to decrease it.
380         boolean validIndex = newIndex > 0;
381         if (newIndex > getChildCount() - 1) {
382             validIndex = false;
383             newIndex = -1;
384         }
385         if (validIndex && currentIndex != newIndex) {
386             changeViewPosition(mSpeedBumpView, newIndex);
387         }
388         updateSpeedBump(validIndex);
389         mAmbientState.setSpeedBumpIndex(newIndex);
390     }
391 
setChildLocationsChangedListener(OnChildLocationsChangedListener listener)392     public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
393         mListener = listener;
394     }
395 
396     /**
397      * Returns the location the given child is currently rendered at.
398      *
399      * @param child the child to get the location for
400      * @return one of {@link StackViewState}'s <code>LOCATION_*</code> constants
401      */
getChildLocation(View child)402     public int getChildLocation(View child) {
403         StackViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
404         if (childViewState == null) {
405             return StackViewState.LOCATION_UNKNOWN;
406         }
407         if (childViewState.gone) {
408             return StackViewState.LOCATION_GONE;
409         }
410         return childViewState.location;
411     }
412 
setMaxLayoutHeight(int maxLayoutHeight)413     private void setMaxLayoutHeight(int maxLayoutHeight) {
414         mMaxLayoutHeight = maxLayoutHeight;
415         updateAlgorithmHeightAndPadding();
416     }
417 
updateAlgorithmHeightAndPadding()418     private void updateAlgorithmHeightAndPadding() {
419         mAmbientState.setLayoutHeight(getLayoutHeight());
420         mAmbientState.setTopPadding(mTopPadding);
421     }
422 
423     /**
424      * @return whether the height of the layout needs to be adapted, in order to ensure that the
425      *         last child is not in the bottom stack.
426      */
needsHeightAdaption()427     private boolean needsHeightAdaption() {
428         return getNotGoneChildCount() > 1;
429     }
430 
431     /**
432      * Updates the children views according to the stack scroll algorithm. Call this whenever
433      * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
434      */
updateChildren()435     private void updateChildren() {
436         mAmbientState.setScrollY(mOwnScrollY);
437         mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
438         if (!isCurrentlyAnimating() && !mNeedsAnimation) {
439             applyCurrentState();
440         } else {
441             startAnimationToState();
442         }
443     }
444 
requestChildrenUpdate()445     private void requestChildrenUpdate() {
446         if (!mChildrenUpdateRequested) {
447             getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
448             mChildrenUpdateRequested = true;
449             invalidate();
450         }
451     }
452 
isCurrentlyAnimating()453     private boolean isCurrentlyAnimating() {
454         return mStateAnimator.isRunning();
455     }
456 
clampScrollPosition()457     private void clampScrollPosition() {
458         int scrollRange = getScrollRange();
459         if (scrollRange < mOwnScrollY) {
460             mOwnScrollY = scrollRange;
461         }
462     }
463 
getTopPadding()464     public int getTopPadding() {
465         return mTopPadding;
466     }
467 
setTopPadding(int topPadding, boolean animate)468     private void setTopPadding(int topPadding, boolean animate) {
469         if (mTopPadding != topPadding) {
470             mTopPadding = topPadding;
471             updateAlgorithmHeightAndPadding();
472             updateContentHeight();
473             if (animate && mAnimationsEnabled && mIsExpanded) {
474                 mTopPaddingNeedsAnimation = true;
475                 mNeedsAnimation =  true;
476             }
477             requestChildrenUpdate();
478             notifyHeightChangeListener(null);
479         }
480     }
481 
482     /**
483      * Update the height of the stack to a new height.
484      *
485      * @param height the new height of the stack
486      */
setStackHeight(float height)487     public void setStackHeight(float height) {
488         mLastSetStackHeight = height;
489         setIsExpanded(height > 0.0f);
490         int newStackHeight = (int) height;
491         int minStackHeight = getMinStackHeight();
492         int stackHeight;
493         float paddingOffset;
494         boolean trackingHeadsUp = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp();
495         int normalUnfoldPositionStart = trackingHeadsUp ? mHeadsUpManager.getTopHeadsUpHeight()
496                 : minStackHeight;
497         if (newStackHeight - mTopPadding - mTopPaddingOverflow >= normalUnfoldPositionStart
498                 || getNotGoneChildCount() == 0) {
499             paddingOffset = mTopPaddingOverflow;
500             stackHeight = newStackHeight;
501         } else {
502 
503             // We did not reach the position yet where we actually start growing,
504             // so we translate the stack upwards.
505             int translationY = (newStackHeight - minStackHeight);
506             // A slight parallax effect is introduced in order for the stack to catch up with
507             // the top card.
508             float partiallyThere = (newStackHeight - mTopPadding - mTopPaddingOverflow)
509                     / minStackHeight;
510             partiallyThere = Math.max(0, partiallyThere);
511             if (!trackingHeadsUp) {
512                 translationY += (1 - partiallyThere) * (mBottomStackPeekSize +
513                         mCollapseSecondCardPadding);
514             } else {
515                 translationY = (int) (height - mHeadsUpManager.getTopHeadsUpHeight());
516             }
517             paddingOffset = translationY - mTopPadding;
518             stackHeight = (int) (height - (translationY - mTopPadding));
519         }
520         if (stackHeight != mCurrentStackHeight) {
521             mCurrentStackHeight = stackHeight;
522             updateAlgorithmHeightAndPadding();
523             requestChildrenUpdate();
524         }
525         setStackTranslation(paddingOffset);
526     }
527 
getStackTranslation()528     public float getStackTranslation() {
529         return mStackTranslation;
530     }
531 
setStackTranslation(float stackTranslation)532     private void setStackTranslation(float stackTranslation) {
533         if (stackTranslation != mStackTranslation) {
534             mStackTranslation = stackTranslation;
535             mAmbientState.setStackTranslation(stackTranslation);
536             requestChildrenUpdate();
537         }
538     }
539 
540     /**
541      * Get the current height of the view. This is at most the msize of the view given by a the
542      * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
543      *
544      * @return either the layout height or the externally defined height, whichever is smaller
545      */
getLayoutHeight()546     private int getLayoutHeight() {
547         return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
548     }
549 
getItemHeight()550     public int getItemHeight() {
551         return mCollapsedSize;
552     }
553 
getBottomStackPeekSize()554     public int getBottomStackPeekSize() {
555         return mBottomStackPeekSize;
556     }
557 
getCollapseSecondCardPadding()558     public int getCollapseSecondCardPadding() {
559         return mCollapseSecondCardPadding;
560     }
561 
setLongPressListener(SwipeHelper.LongPressListener listener)562     public void setLongPressListener(SwipeHelper.LongPressListener listener) {
563         mSwipeHelper.setLongPressListener(listener);
564         mLongPressListener = listener;
565     }
566 
setScrollView(ViewGroup scrollView)567     public void setScrollView(ViewGroup scrollView) {
568         mScrollView = scrollView;
569     }
570 
setInterceptDelegateEnabled(boolean interceptDelegateEnabled)571     public void setInterceptDelegateEnabled(boolean interceptDelegateEnabled) {
572         mInterceptDelegateEnabled = interceptDelegateEnabled;
573     }
574 
onChildDismissed(View v)575     public void onChildDismissed(View v) {
576         if (mDismissAllInProgress) {
577             return;
578         }
579         setSwipingInProgress(false);
580         if (mDragAnimPendingChildren.contains(v)) {
581             // We start the swipe and finish it in the same frame, we don't want any animation
582             // for the drag
583             mDragAnimPendingChildren.remove(v);
584         }
585         mSwipedOutViews.add(v);
586         mAmbientState.onDragFinished(v);
587         if (v instanceof ExpandableNotificationRow) {
588             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
589             if (row.isHeadsUp()) {
590                 mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
591             }
592         }
593         final View veto = v.findViewById(R.id.veto);
594         if (veto != null && veto.getVisibility() != View.GONE) {
595             veto.performClick();
596         }
597         if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
598     }
599 
600     @Override
onChildSnappedBack(View animView)601     public void onChildSnappedBack(View animView) {
602         mAmbientState.onDragFinished(animView);
603         if (!mDragAnimPendingChildren.contains(animView)) {
604             if (mAnimationsEnabled) {
605                 mSnappedBackChildren.add(animView);
606                 mNeedsAnimation = true;
607             }
608             requestChildrenUpdate();
609         } else {
610             // We start the swipe and snap back in the same frame, we don't want any animation
611             mDragAnimPendingChildren.remove(animView);
612         }
613     }
614 
615     @Override
updateSwipeProgress(View animView, boolean dismissable, float swipeProgress)616     public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
617         if (!mIsExpanded && isPinnedHeadsUp(animView) && canChildBeDismissed(animView)) {
618             mScrimController.setTopHeadsUpDragAmount(animView,
619                     Math.min(Math.abs(swipeProgress - 1.0f), 1.0f));
620         }
621         return false;
622     }
623 
onBeginDrag(View v)624     public void onBeginDrag(View v) {
625         setSwipingInProgress(true);
626         mAmbientState.onBeginDrag(v);
627         if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
628             mDragAnimPendingChildren.add(v);
629             mNeedsAnimation = true;
630         }
631         requestChildrenUpdate();
632     }
633 
isPinnedHeadsUp(View v)634     public static boolean isPinnedHeadsUp(View v) {
635         if (v instanceof ExpandableNotificationRow) {
636             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
637             return row.isHeadsUp() && row.isPinned();
638         }
639         return false;
640     }
641 
isHeadsUp(View v)642     private boolean isHeadsUp(View v) {
643         if (v instanceof ExpandableNotificationRow) {
644             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
645             return row.isHeadsUp();
646         }
647         return false;
648     }
649 
onDragCancelled(View v)650     public void onDragCancelled(View v) {
651         setSwipingInProgress(false);
652     }
653 
654     @Override
getFalsingThresholdFactor()655     public float getFalsingThresholdFactor() {
656         return mPhoneStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f;
657     }
658 
getChildAtPosition(MotionEvent ev)659     public View getChildAtPosition(MotionEvent ev) {
660         return getChildAtPosition(ev.getX(), ev.getY());
661     }
662 
getClosestChildAtRawPosition(float touchX, float touchY)663     public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
664         getLocationOnScreen(mTempInt2);
665         float localTouchY = touchY - mTempInt2[1];
666 
667         ExpandableView closestChild = null;
668         float minDist = Float.MAX_VALUE;
669 
670         // find the view closest to the location, accounting for GONE views
671         final int count = getChildCount();
672         for (int childIdx = 0; childIdx < count; childIdx++) {
673             ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
674             if (slidingChild.getVisibility() == GONE
675                     || slidingChild instanceof StackScrollerDecorView
676                     || slidingChild == mSpeedBumpView) {
677                 continue;
678             }
679             float childTop = slidingChild.getTranslationY();
680             float top = childTop + slidingChild.getClipTopAmount();
681             float bottom = childTop + slidingChild.getActualHeight();
682 
683             float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
684             if (dist < minDist) {
685                 closestChild = slidingChild;
686                 minDist = dist;
687             }
688         }
689         return closestChild;
690     }
691 
getChildAtRawPosition(float touchX, float touchY)692     public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
693         getLocationOnScreen(mTempInt2);
694         return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
695     }
696 
getChildAtPosition(float touchX, float touchY)697     public ExpandableView getChildAtPosition(float touchX, float touchY) {
698         // find the view under the pointer, accounting for GONE views
699         final int count = getChildCount();
700         for (int childIdx = 0; childIdx < count; childIdx++) {
701             ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
702             if (slidingChild.getVisibility() == GONE
703                     || slidingChild instanceof StackScrollerDecorView
704                     || slidingChild == mSpeedBumpView) {
705                 continue;
706             }
707             float childTop = slidingChild.getTranslationY();
708             float top = childTop + slidingChild.getClipTopAmount();
709             float bottom = childTop + slidingChild.getActualHeight();
710 
711             // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
712             // camera affordance).
713             int left = 0;
714             int right = getWidth();
715 
716             if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
717                 if (slidingChild instanceof ExpandableNotificationRow) {
718                     ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
719                     if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
720                             && mHeadsUpManager.getTopEntry().entry.row != row) {
721                         continue;
722                     }
723                     return row.getViewAtPosition(touchY - childTop);
724                 }
725                 return slidingChild;
726             }
727         }
728         return null;
729     }
730 
canChildBeExpanded(View v)731     public boolean canChildBeExpanded(View v) {
732         return v instanceof ExpandableNotificationRow
733                 && ((ExpandableNotificationRow) v).isExpandable()
734                 && !((ExpandableNotificationRow) v).isHeadsUp();
735     }
736 
setUserExpandedChild(View v, boolean userExpanded)737     public void setUserExpandedChild(View v, boolean userExpanded) {
738         if (v instanceof ExpandableNotificationRow) {
739             ((ExpandableNotificationRow) v).setUserExpanded(userExpanded);
740         }
741     }
742 
setUserLockedChild(View v, boolean userLocked)743     public void setUserLockedChild(View v, boolean userLocked) {
744         if (v instanceof ExpandableNotificationRow) {
745             ((ExpandableNotificationRow) v).setUserLocked(userLocked);
746         }
747         removeLongPressCallback();
748         requestDisallowInterceptTouchEvent(true);
749     }
750 
751     @Override
expansionStateChanged(boolean isExpanding)752     public void expansionStateChanged(boolean isExpanding) {
753         mExpandingNotification = isExpanding;
754         if (!mExpandedInThisMotion) {
755             mMaxScrollAfterExpand = mOwnScrollY;
756             mExpandedInThisMotion = true;
757         }
758     }
759 
setScrollingEnabled(boolean enable)760     public void setScrollingEnabled(boolean enable) {
761         mScrollingEnabled = enable;
762     }
763 
setExpandingEnabled(boolean enable)764     public void setExpandingEnabled(boolean enable) {
765         mExpandHelper.setEnabled(enable);
766     }
767 
isScrollingEnabled()768     private boolean isScrollingEnabled() {
769         return mScrollingEnabled;
770     }
771 
getChildContentView(View v)772     public View getChildContentView(View v) {
773         return v;
774     }
775 
canChildBeDismissed(View v)776     public boolean canChildBeDismissed(View v) {
777         return StackScrollAlgorithm.canChildBeDismissed(v);
778     }
779 
780     @Override
isAntiFalsingNeeded()781     public boolean isAntiFalsingNeeded() {
782         return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD;
783     }
784 
setSwipingInProgress(boolean isSwiped)785     private void setSwipingInProgress(boolean isSwiped) {
786         mSwipingInProgress = isSwiped;
787         if(isSwiped) {
788             requestDisallowInterceptTouchEvent(true);
789         }
790     }
791 
792     @Override
onConfigurationChanged(Configuration newConfig)793     protected void onConfigurationChanged(Configuration newConfig) {
794         super.onConfigurationChanged(newConfig);
795         float densityScale = getResources().getDisplayMetrics().density;
796         mSwipeHelper.setDensityScale(densityScale);
797         float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
798         mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
799         initView(getContext());
800     }
801 
dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration)802     public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
803         mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration);
804     }
805 
806     @Override
onTouchEvent(MotionEvent ev)807     public boolean onTouchEvent(MotionEvent ev) {
808         boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
809                 || ev.getActionMasked()== MotionEvent.ACTION_UP;
810         if (mDelegateToScrollView) {
811             if (isCancelOrUp) {
812                 mDelegateToScrollView = false;
813             }
814             transformTouchEvent(ev, this, mScrollView);
815             return mScrollView.onTouchEvent(ev);
816         }
817         handleEmptySpaceClick(ev);
818         boolean expandWantsIt = false;
819         if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) {
820             if (isCancelOrUp) {
821                 mExpandHelper.onlyObserveMovements(false);
822             }
823             boolean wasExpandingBefore = mExpandingNotification;
824             expandWantsIt = mExpandHelper.onTouchEvent(ev);
825             if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
826                     && !mDisallowScrollingInThisMotion) {
827                 dispatchDownEventToScroller(ev);
828             }
829         }
830         boolean scrollerWantsIt = false;
831         if (mIsExpanded && !mSwipingInProgress && !mExpandingNotification
832                 && !mDisallowScrollingInThisMotion) {
833             scrollerWantsIt = onScrollTouch(ev);
834         }
835         boolean horizontalSwipeWantsIt = false;
836         if (!mIsBeingDragged
837                 && !mExpandingNotification
838                 && !mExpandedInThisMotion
839                 && !mOnlyScrollingInThisMotion) {
840             horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
841         }
842         return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
843     }
844 
dispatchDownEventToScroller(MotionEvent ev)845     private void dispatchDownEventToScroller(MotionEvent ev) {
846         MotionEvent downEvent = MotionEvent.obtain(ev);
847         downEvent.setAction(MotionEvent.ACTION_DOWN);
848         onScrollTouch(downEvent);
849         downEvent.recycle();
850     }
851 
onScrollTouch(MotionEvent ev)852     private boolean onScrollTouch(MotionEvent ev) {
853         if (!isScrollingEnabled()) {
854             return false;
855         }
856         initVelocityTrackerIfNotExists();
857         mVelocityTracker.addMovement(ev);
858 
859         final int action = ev.getAction();
860 
861         switch (action & MotionEvent.ACTION_MASK) {
862             case MotionEvent.ACTION_DOWN: {
863                 if (getChildCount() == 0 || !isInContentBounds(ev)) {
864                     return false;
865                 }
866                 boolean isBeingDragged = !mScroller.isFinished();
867                 setIsBeingDragged(isBeingDragged);
868 
869                 /*
870                  * If being flinged and user touches, stop the fling. isFinished
871                  * will be false if being flinged.
872                  */
873                 if (!mScroller.isFinished()) {
874                     mScroller.forceFinished(true);
875                 }
876 
877                 // Remember where the motion event started
878                 mLastMotionY = (int) ev.getY();
879                 mDownX = (int) ev.getX();
880                 mActivePointerId = ev.getPointerId(0);
881                 break;
882             }
883             case MotionEvent.ACTION_MOVE:
884                 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
885                 if (activePointerIndex == -1) {
886                     Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
887                     break;
888                 }
889 
890                 final int y = (int) ev.getY(activePointerIndex);
891                 final int x = (int) ev.getX(activePointerIndex);
892                 int deltaY = mLastMotionY - y;
893                 final int xDiff = Math.abs(x - mDownX);
894                 final int yDiff = Math.abs(deltaY);
895                 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
896                     setIsBeingDragged(true);
897                     if (deltaY > 0) {
898                         deltaY -= mTouchSlop;
899                     } else {
900                         deltaY += mTouchSlop;
901                     }
902                 }
903                 if (mIsBeingDragged) {
904                     // Scroll to follow the motion event
905                     mLastMotionY = y;
906                     int range = getScrollRange();
907                     if (mExpandedInThisMotion) {
908                         range = Math.min(range, mMaxScrollAfterExpand);
909                     }
910 
911                     float scrollAmount;
912                     if (deltaY < 0) {
913                         scrollAmount = overScrollDown(deltaY);
914                     } else {
915                         scrollAmount = overScrollUp(deltaY, range);
916                     }
917 
918                     // Calling overScrollBy will call onOverScrolled, which
919                     // calls onScrollChanged if applicable.
920                     if (scrollAmount != 0.0f) {
921                         // The scrolling motion could not be compensated with the
922                         // existing overScroll, we have to scroll the view
923                         overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY,
924                                 0, range, 0, getHeight() / 2, true);
925                     }
926                 }
927                 break;
928             case MotionEvent.ACTION_UP:
929                 if (mIsBeingDragged) {
930                     final VelocityTracker velocityTracker = mVelocityTracker;
931                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
932                     int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
933 
934                     if (shouldOverScrollFling(initialVelocity)) {
935                         onOverScrollFling(true, initialVelocity);
936                     } else {
937                         if (getChildCount() > 0) {
938                             if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
939                                 float currentOverScrollTop = getCurrentOverScrollAmount(true);
940                                 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
941                                     fling(-initialVelocity);
942                                 } else {
943                                     onOverScrollFling(false, initialVelocity);
944                                 }
945                             } else {
946                                 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
947                                         getScrollRange())) {
948                                     postInvalidateOnAnimation();
949                                 }
950                             }
951                         }
952                     }
953 
954                     mActivePointerId = INVALID_POINTER;
955                     endDrag();
956                 }
957 
958                 break;
959             case MotionEvent.ACTION_CANCEL:
960                 if (mIsBeingDragged && getChildCount() > 0) {
961                     if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
962                         postInvalidateOnAnimation();
963                     }
964                     mActivePointerId = INVALID_POINTER;
965                     endDrag();
966                 }
967                 break;
968             case MotionEvent.ACTION_POINTER_DOWN: {
969                 final int index = ev.getActionIndex();
970                 mLastMotionY = (int) ev.getY(index);
971                 mDownX = (int) ev.getX(index);
972                 mActivePointerId = ev.getPointerId(index);
973                 break;
974             }
975             case MotionEvent.ACTION_POINTER_UP:
976                 onSecondaryPointerUp(ev);
977                 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
978                 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
979                 break;
980         }
981         return true;
982     }
983 
onOverScrollFling(boolean open, int initialVelocity)984     private void onOverScrollFling(boolean open, int initialVelocity) {
985         if (mOverscrollTopChangedListener != null) {
986             mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
987         }
988         mDontReportNextOverScroll = true;
989         setOverScrollAmount(0.0f, true, false);
990     }
991 
992     /**
993      * Perform a scroll upwards and adapt the overscroll amounts accordingly
994      *
995      * @param deltaY The amount to scroll upwards, has to be positive.
996      * @return The amount of scrolling to be performed by the scroller,
997      *         not handled by the overScroll amount.
998      */
overScrollUp(int deltaY, int range)999     private float overScrollUp(int deltaY, int range) {
1000         deltaY = Math.max(deltaY, 0);
1001         float currentTopAmount = getCurrentOverScrollAmount(true);
1002         float newTopAmount = currentTopAmount - deltaY;
1003         if (currentTopAmount > 0) {
1004             setOverScrollAmount(newTopAmount, true /* onTop */,
1005                     false /* animate */);
1006         }
1007         // Top overScroll might not grab all scrolling motion,
1008         // we have to scroll as well.
1009         float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1010         float newScrollY = mOwnScrollY + scrollAmount;
1011         if (newScrollY > range) {
1012             if (!mExpandedInThisMotion) {
1013                 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1014                 // We overScroll on the top
1015                 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
1016                         false /* onTop */,
1017                         false /* animate */);
1018             }
1019             mOwnScrollY = range;
1020             scrollAmount = 0.0f;
1021         }
1022         return scrollAmount;
1023     }
1024 
1025     /**
1026      * Perform a scroll downward and adapt the overscroll amounts accordingly
1027      *
1028      * @param deltaY The amount to scroll downwards, has to be negative.
1029      * @return The amount of scrolling to be performed by the scroller,
1030      *         not handled by the overScroll amount.
1031      */
overScrollDown(int deltaY)1032     private float overScrollDown(int deltaY) {
1033         deltaY = Math.min(deltaY, 0);
1034         float currentBottomAmount = getCurrentOverScrollAmount(false);
1035         float newBottomAmount = currentBottomAmount + deltaY;
1036         if (currentBottomAmount > 0) {
1037             setOverScrollAmount(newBottomAmount, false /* onTop */,
1038                     false /* animate */);
1039         }
1040         // Bottom overScroll might not grab all scrolling motion,
1041         // we have to scroll as well.
1042         float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1043         float newScrollY = mOwnScrollY + scrollAmount;
1044         if (newScrollY < 0) {
1045             float currentTopPixels = getCurrentOverScrolledPixels(true);
1046             // We overScroll on the top
1047             setOverScrolledPixels(currentTopPixels - newScrollY,
1048                     true /* onTop */,
1049                     false /* animate */);
1050             mOwnScrollY = 0;
1051             scrollAmount = 0.0f;
1052         }
1053         return scrollAmount;
1054     }
1055 
1056     private void onSecondaryPointerUp(MotionEvent ev) {
1057         final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1058                 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1059         final int pointerId = ev.getPointerId(pointerIndex);
1060         if (pointerId == mActivePointerId) {
1061             // This was our active pointer going up. Choose a new
1062             // active pointer and adjust accordingly.
1063             // TODO: Make this decision more intelligent.
1064             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1065             mLastMotionY = (int) ev.getY(newPointerIndex);
1066             mActivePointerId = ev.getPointerId(newPointerIndex);
1067             if (mVelocityTracker != null) {
1068                 mVelocityTracker.clear();
1069             }
1070         }
1071     }
1072 
initVelocityTrackerIfNotExists()1073     private void initVelocityTrackerIfNotExists() {
1074         if (mVelocityTracker == null) {
1075             mVelocityTracker = VelocityTracker.obtain();
1076         }
1077     }
1078 
recycleVelocityTracker()1079     private void recycleVelocityTracker() {
1080         if (mVelocityTracker != null) {
1081             mVelocityTracker.recycle();
1082             mVelocityTracker = null;
1083         }
1084     }
1085 
initOrResetVelocityTracker()1086     private void initOrResetVelocityTracker() {
1087         if (mVelocityTracker == null) {
1088             mVelocityTracker = VelocityTracker.obtain();
1089         } else {
1090             mVelocityTracker.clear();
1091         }
1092     }
1093 
1094     @Override
computeScroll()1095     public void computeScroll() {
1096         if (mScroller.computeScrollOffset()) {
1097             // This is called at drawing time by ViewGroup.
1098             int oldX = mScrollX;
1099             int oldY = mOwnScrollY;
1100             int x = mScroller.getCurrX();
1101             int y = mScroller.getCurrY();
1102 
1103             if (oldX != x || oldY != y) {
1104                 final int range = getScrollRange();
1105                 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
1106                     float currVelocity = mScroller.getCurrVelocity();
1107                     if (currVelocity >= mMinimumVelocity) {
1108                         mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
1109                     }
1110                 }
1111 
1112                 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
1113                         0, (int) (mMaxOverScroll), false);
1114                 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1115             }
1116 
1117             // Keep on drawing until the animation has finished.
1118             postInvalidateOnAnimation();
1119         }
1120     }
1121 
1122     @Override
overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent)1123     protected boolean overScrollBy(int deltaX, int deltaY,
1124             int scrollX, int scrollY,
1125             int scrollRangeX, int scrollRangeY,
1126             int maxOverScrollX, int maxOverScrollY,
1127             boolean isTouchEvent) {
1128 
1129         int newScrollY = scrollY + deltaY;
1130 
1131         final int top = -maxOverScrollY;
1132         final int bottom = maxOverScrollY + scrollRangeY;
1133 
1134         boolean clampedY = false;
1135         if (newScrollY > bottom) {
1136             newScrollY = bottom;
1137             clampedY = true;
1138         } else if (newScrollY < top) {
1139             newScrollY = top;
1140             clampedY = true;
1141         }
1142 
1143         onOverScrolled(0, newScrollY, false, clampedY);
1144 
1145         return clampedY;
1146     }
1147 
1148     /**
1149      * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
1150      * overscroll effect based on numPixels. By default this will also cancel animations on the
1151      * same overScroll edge.
1152      *
1153      * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
1154      *                  the rubber-banding logic.
1155      * @param onTop Should the effect be applied on top of the scroller.
1156      * @param animate Should an animation be performed.
1157      */
setOverScrolledPixels(float numPixels, boolean onTop, boolean animate)1158     public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
1159         setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
1160     }
1161 
1162     /**
1163      * Set the effective overScroll amount which will be directly reflected in the layout.
1164      * By default this will also cancel animations on the same overScroll edge.
1165      *
1166      * @param amount The amount to overScroll by.
1167      * @param onTop Should the effect be applied on top of the scroller.
1168      * @param animate Should an animation be performed.
1169      */
setOverScrollAmount(float amount, boolean onTop, boolean animate)1170     public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
1171         setOverScrollAmount(amount, onTop, animate, true);
1172     }
1173 
1174     /**
1175      * Set the effective overScroll amount which will be directly reflected in the layout.
1176      *
1177      * @param amount The amount to overScroll by.
1178      * @param onTop Should the effect be applied on top of the scroller.
1179      * @param animate Should an animation be performed.
1180      * @param cancelAnimators Should running animations be cancelled.
1181      */
setOverScrollAmount(float amount, boolean onTop, boolean animate, boolean cancelAnimators)1182     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1183             boolean cancelAnimators) {
1184         setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
1185     }
1186 
1187     /**
1188      * Set the effective overScroll amount which will be directly reflected in the layout.
1189      *
1190      * @param amount The amount to overScroll by.
1191      * @param onTop Should the effect be applied on top of the scroller.
1192      * @param animate Should an animation be performed.
1193      * @param cancelAnimators Should running animations be cancelled.
1194      * @param isRubberbanded The value which will be passed to
1195      *                     {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
1196      */
setOverScrollAmount(float amount, boolean onTop, boolean animate, boolean cancelAnimators, boolean isRubberbanded)1197     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1198             boolean cancelAnimators, boolean isRubberbanded) {
1199         if (cancelAnimators) {
1200             mStateAnimator.cancelOverScrollAnimators(onTop);
1201         }
1202         setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
1203     }
1204 
setOverScrollAmountInternal(float amount, boolean onTop, boolean animate, boolean isRubberbanded)1205     private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
1206             boolean isRubberbanded) {
1207         amount = Math.max(0, amount);
1208         if (animate) {
1209             mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
1210         } else {
1211             setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
1212             mAmbientState.setOverScrollAmount(amount, onTop);
1213             if (onTop) {
1214                 notifyOverscrollTopListener(amount, isRubberbanded);
1215             }
1216             requestChildrenUpdate();
1217         }
1218     }
1219 
notifyOverscrollTopListener(float amount, boolean isRubberbanded)1220     private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
1221         mExpandHelper.onlyObserveMovements(amount > 1.0f);
1222         if (mDontReportNextOverScroll) {
1223             mDontReportNextOverScroll = false;
1224             return;
1225         }
1226         if (mOverscrollTopChangedListener != null) {
1227             mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
1228         }
1229     }
1230 
setOverscrollTopChangedListener( OnOverscrollTopChangedListener overscrollTopChangedListener)1231     public void setOverscrollTopChangedListener(
1232             OnOverscrollTopChangedListener overscrollTopChangedListener) {
1233         mOverscrollTopChangedListener = overscrollTopChangedListener;
1234     }
1235 
getCurrentOverScrollAmount(boolean top)1236     public float getCurrentOverScrollAmount(boolean top) {
1237         return mAmbientState.getOverScrollAmount(top);
1238     }
1239 
getCurrentOverScrolledPixels(boolean top)1240     public float getCurrentOverScrolledPixels(boolean top) {
1241         return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
1242     }
1243 
setOverScrolledPixels(float amount, boolean onTop)1244     private void setOverScrolledPixels(float amount, boolean onTop) {
1245         if (onTop) {
1246             mOverScrolledTopPixels = amount;
1247         } else {
1248             mOverScrolledBottomPixels = amount;
1249         }
1250     }
1251 
customScrollTo(int y)1252     private void customScrollTo(int y) {
1253         mOwnScrollY = y;
1254         updateChildren();
1255     }
1256 
1257     @Override
onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY)1258     protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
1259         // Treat animating scrolls differently; see #computeScroll() for why.
1260         if (!mScroller.isFinished()) {
1261             final int oldX = mScrollX;
1262             final int oldY = mOwnScrollY;
1263             mScrollX = scrollX;
1264             mOwnScrollY = scrollY;
1265             if (clampedY) {
1266                 springBack();
1267             } else {
1268                 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1269                 invalidateParentIfNeeded();
1270                 updateChildren();
1271                 float overScrollTop = getCurrentOverScrollAmount(true);
1272                 if (mOwnScrollY < 0) {
1273                     notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
1274                 } else {
1275                     notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
1276                 }
1277             }
1278         } else {
1279             customScrollTo(scrollY);
1280             scrollTo(scrollX, mScrollY);
1281         }
1282     }
1283 
springBack()1284     private void springBack() {
1285         int scrollRange = getScrollRange();
1286         boolean overScrolledTop = mOwnScrollY <= 0;
1287         boolean overScrolledBottom = mOwnScrollY >= scrollRange;
1288         if (overScrolledTop || overScrolledBottom) {
1289             boolean onTop;
1290             float newAmount;
1291             if (overScrolledTop) {
1292                 onTop = true;
1293                 newAmount = -mOwnScrollY;
1294                 mOwnScrollY = 0;
1295                 mDontReportNextOverScroll = true;
1296             } else {
1297                 onTop = false;
1298                 newAmount = mOwnScrollY - scrollRange;
1299                 mOwnScrollY = scrollRange;
1300             }
1301             setOverScrollAmount(newAmount, onTop, false);
1302             setOverScrollAmount(0.0f, onTop, true);
1303             mScroller.forceFinished(true);
1304         }
1305     }
1306 
getScrollRange()1307     private int getScrollRange() {
1308         int scrollRange = 0;
1309         ExpandableView firstChild = (ExpandableView) getFirstChildNotGone();
1310         if (firstChild != null) {
1311             int contentHeight = getContentHeight();
1312             int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
1313             scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
1314                     + mBottomStackSlowDownHeight);
1315             if (scrollRange > 0) {
1316                 View lastChild = getLastChildNotGone();
1317                 // We want to at least be able collapse the first item and not ending in a weird
1318                 // end state.
1319                 scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight - mCollapsedSize);
1320             }
1321         }
1322         return scrollRange;
1323     }
1324 
1325     /**
1326      * @return the first child which has visibility unequal to GONE
1327      */
getFirstChildNotGone()1328     private View getFirstChildNotGone() {
1329         int childCount = getChildCount();
1330         for (int i = 0; i < childCount; i++) {
1331             View child = getChildAt(i);
1332             if (child.getVisibility() != View.GONE) {
1333                 return child;
1334             }
1335         }
1336         return null;
1337     }
1338 
1339     /**
1340      * @return The first child which has visibility unequal to GONE which is currently below the
1341      *         given translationY or equal to it.
1342      */
getFirstChildBelowTranlsationY(float translationY)1343     private View getFirstChildBelowTranlsationY(float translationY) {
1344         int childCount = getChildCount();
1345         for (int i = 0; i < childCount; i++) {
1346             View child = getChildAt(i);
1347             if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) {
1348                 return child;
1349             }
1350         }
1351         return null;
1352     }
1353 
1354     /**
1355      * @return the last child which has visibility unequal to GONE
1356      */
getLastChildNotGone()1357     public View getLastChildNotGone() {
1358         int childCount = getChildCount();
1359         for (int i = childCount - 1; i >= 0; i--) {
1360             View child = getChildAt(i);
1361             if (child.getVisibility() != View.GONE) {
1362                 return child;
1363             }
1364         }
1365         return null;
1366     }
1367 
1368     /**
1369      * @return the number of children which have visibility unequal to GONE
1370      */
getNotGoneChildCount()1371     public int getNotGoneChildCount() {
1372         int childCount = getChildCount();
1373         int count = 0;
1374         for (int i = 0; i < childCount; i++) {
1375             ExpandableView child = (ExpandableView) getChildAt(i);
1376             if (child.getVisibility() != View.GONE && !child.willBeGone()) {
1377                 count++;
1378             }
1379         }
1380         return count;
1381     }
1382 
getMaxExpandHeight(View view)1383     private int getMaxExpandHeight(View view) {
1384         if (view instanceof ExpandableNotificationRow) {
1385             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
1386             return row.getIntrinsicHeight();
1387         }
1388         return view.getHeight();
1389     }
1390 
getContentHeight()1391     public int getContentHeight() {
1392         return mContentHeight;
1393     }
1394 
updateContentHeight()1395     private void updateContentHeight() {
1396         int height = 0;
1397         for (int i = 0; i < getChildCount(); i++) {
1398             View child = getChildAt(i);
1399             if (child.getVisibility() != View.GONE) {
1400                 if (height != 0) {
1401                     // add the padding before this element
1402                     height += mPaddingBetweenElements;
1403                 }
1404                 if (child instanceof ExpandableView) {
1405                     ExpandableView expandableView = (ExpandableView) child;
1406                     height += expandableView.getIntrinsicHeight();
1407                 }
1408             }
1409         }
1410         mContentHeight = height + mTopPadding;
1411     }
1412 
1413     /**
1414      * Fling the scroll view
1415      *
1416      * @param velocityY The initial velocity in the Y direction. Positive
1417      *                  numbers mean that the finger/cursor is moving down the screen,
1418      *                  which means we want to scroll towards the top.
1419      */
fling(int velocityY)1420     private void fling(int velocityY) {
1421         if (getChildCount() > 0) {
1422             int scrollRange = getScrollRange();
1423 
1424             float topAmount = getCurrentOverScrollAmount(true);
1425             float bottomAmount = getCurrentOverScrollAmount(false);
1426             if (velocityY < 0 && topAmount > 0) {
1427                 mOwnScrollY -= (int) topAmount;
1428                 mDontReportNextOverScroll = true;
1429                 setOverScrollAmount(0, true, false);
1430                 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
1431                         * mOverflingDistance + topAmount;
1432             } else if (velocityY > 0 && bottomAmount > 0) {
1433                 mOwnScrollY += bottomAmount;
1434                 setOverScrollAmount(0, false, false);
1435                 mMaxOverScroll = Math.abs(velocityY) / 1000f
1436                         * getRubberBandFactor(false /* onTop */) * mOverflingDistance
1437                         +  bottomAmount;
1438             } else {
1439                 // it will be set once we reach the boundary
1440                 mMaxOverScroll = 0.0f;
1441             }
1442             mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0,
1443                     Math.max(0, scrollRange), 0, Integer.MAX_VALUE / 2);
1444 
1445             postInvalidateOnAnimation();
1446         }
1447     }
1448 
1449     /**
1450      * @return Whether a fling performed on the top overscroll edge lead to the expanded
1451      * overScroll view (i.e QS).
1452      */
shouldOverScrollFling(int initialVelocity)1453     private boolean shouldOverScrollFling(int initialVelocity) {
1454         float topOverScroll = getCurrentOverScrollAmount(true);
1455         return mScrolledToTopOnFirstDown
1456                 && !mExpandedInThisMotion
1457                 && topOverScroll > mMinTopOverScrollToEscape
1458                 && initialVelocity > 0;
1459     }
1460 
1461     /**
1462      * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
1463      * account.
1464      *
1465      * @param qsHeight the top padding imposed by the quick settings panel
1466      * @param scrollY how much the notifications are scrolled inside the QS/notifications scroll
1467      *                container
1468      * @param animate whether to animate the change
1469      * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
1470      *                               {@code qsHeight} is the final top padding
1471      */
updateTopPadding(float qsHeight, int scrollY, boolean animate, boolean ignoreIntrinsicPadding)1472     public void updateTopPadding(float qsHeight, int scrollY, boolean animate,
1473             boolean ignoreIntrinsicPadding) {
1474         float start = qsHeight - scrollY + mNotificationTopPadding;
1475         float stackHeight = getHeight() - start;
1476         int minStackHeight = getMinStackHeight();
1477         if (stackHeight <= minStackHeight) {
1478             float overflow = minStackHeight - stackHeight;
1479             stackHeight = minStackHeight;
1480             start = getHeight() - stackHeight;
1481             mTopPaddingOverflow = overflow;
1482         } else {
1483             mTopPaddingOverflow = 0;
1484         }
1485         setTopPadding(ignoreIntrinsicPadding ? (int) start : clampPadding((int) start),
1486                 animate);
1487         setStackHeight(mLastSetStackHeight);
1488     }
1489 
getNotificationTopPadding()1490     public int getNotificationTopPadding() {
1491         return mNotificationTopPadding;
1492     }
1493 
getMinStackHeight()1494     public int getMinStackHeight() {
1495         return mCollapsedSize + mBottomStackPeekSize + mCollapseSecondCardPadding;
1496     }
1497 
getTopPaddingOverflow()1498     public float getTopPaddingOverflow() {
1499         return mTopPaddingOverflow;
1500     }
1501 
getPeekHeight()1502     public int getPeekHeight() {
1503         return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize
1504                 + mCollapseSecondCardPadding;
1505     }
1506 
clampPadding(int desiredPadding)1507     private int clampPadding(int desiredPadding) {
1508         return Math.max(desiredPadding, mIntrinsicPadding);
1509     }
1510 
getRubberBandFactor(boolean onTop)1511     private float getRubberBandFactor(boolean onTop) {
1512         if (!onTop) {
1513             return RUBBER_BAND_FACTOR_NORMAL;
1514         }
1515         if (mExpandedInThisMotion) {
1516             return RUBBER_BAND_FACTOR_AFTER_EXPAND;
1517         } else if (mIsExpansionChanging || mPanelTracking) {
1518             return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
1519         } else if (mScrolledToTopOnFirstDown) {
1520             return 1.0f;
1521         }
1522         return RUBBER_BAND_FACTOR_NORMAL;
1523     }
1524 
1525     /**
1526      * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
1527      * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
1528      * overscroll view (e.g. expand QS).
1529      */
isRubberbanded(boolean onTop)1530     private boolean isRubberbanded(boolean onTop) {
1531         return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking
1532                 || !mScrolledToTopOnFirstDown;
1533     }
1534 
endDrag()1535     private void endDrag() {
1536         setIsBeingDragged(false);
1537 
1538         recycleVelocityTracker();
1539 
1540         if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
1541             setOverScrollAmount(0, true /* onTop */, true /* animate */);
1542         }
1543         if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
1544             setOverScrollAmount(0, false /* onTop */, true /* animate */);
1545         }
1546     }
1547 
transformTouchEvent(MotionEvent ev, View sourceView, View targetView)1548     private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
1549         ev.offsetLocation(sourceView.getX(), sourceView.getY());
1550         ev.offsetLocation(-targetView.getX(), -targetView.getY());
1551     }
1552 
1553     @Override
onInterceptTouchEvent(MotionEvent ev)1554     public boolean onInterceptTouchEvent(MotionEvent ev) {
1555         if (mInterceptDelegateEnabled) {
1556             transformTouchEvent(ev, this, mScrollView);
1557             if (mScrollView.onInterceptTouchEvent(ev)) {
1558                 mDelegateToScrollView = true;
1559                 removeLongPressCallback();
1560                 return true;
1561             }
1562             transformTouchEvent(ev, mScrollView, this);
1563         }
1564         initDownStates(ev);
1565         handleEmptySpaceClick(ev);
1566         boolean expandWantsIt = false;
1567         if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) {
1568             expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
1569         }
1570         boolean scrollWantsIt = false;
1571         if (!mSwipingInProgress && !mExpandingNotification) {
1572             scrollWantsIt = onInterceptTouchEventScroll(ev);
1573         }
1574         boolean swipeWantsIt = false;
1575         if (!mIsBeingDragged
1576                 && !mExpandingNotification
1577                 && !mExpandedInThisMotion
1578                 && !mOnlyScrollingInThisMotion) {
1579             swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
1580         }
1581         return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
1582     }
1583 
handleEmptySpaceClick(MotionEvent ev)1584     private void handleEmptySpaceClick(MotionEvent ev) {
1585         switch (ev.getActionMasked()) {
1586             case MotionEvent.ACTION_MOVE:
1587                 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
1588                         || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) {
1589                     mTouchIsClick = false;
1590                 }
1591                 break;
1592             case MotionEvent.ACTION_UP:
1593                 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD && mTouchIsClick &&
1594                         isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
1595                     mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
1596                 }
1597                 break;
1598         }
1599     }
1600 
initDownStates(MotionEvent ev)1601     private void initDownStates(MotionEvent ev) {
1602         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
1603             mExpandedInThisMotion = false;
1604             mOnlyScrollingInThisMotion = !mScroller.isFinished();
1605             mDisallowScrollingInThisMotion = false;
1606             mTouchIsClick = true;
1607             mInitialTouchX = ev.getX();
1608             mInitialTouchY = ev.getY();
1609         }
1610     }
1611 
1612     @Override
onViewRemoved(View child)1613     public void onViewRemoved(View child) {
1614         super.onViewRemoved(child);
1615         // we only call our internal methods if this is actually a removal and not just a
1616         // notification which becomes a child notification
1617         if (!isChildInGroup(child)) {
1618             onViewRemovedInternal(child);
1619         }
1620     }
1621 
onViewRemovedInternal(View child)1622     private void onViewRemovedInternal(View child) {
1623         mStackScrollAlgorithm.notifyChildrenChanged(this);
1624         if (mChangePositionInProgress) {
1625             // This is only a position change, don't do anything special
1626             return;
1627         }
1628         ((ExpandableView) child).setOnHeightChangedListener(null);
1629         mCurrentStackScrollState.removeViewStateForView(child);
1630         updateScrollStateForRemovedChild(child);
1631         boolean animationGenerated = generateRemoveAnimation(child);
1632         if (animationGenerated && !mSwipedOutViews.contains(child)) {
1633             // Add this view to an overlay in order to ensure that it will still be temporary
1634             // drawn when removed
1635             getOverlay().add(child);
1636         }
1637         updateAnimationState(false, child);
1638 
1639         // Make sure the clipRect we might have set is removed
1640         ((ExpandableView) child).setClipTopOptimization(0);
1641     }
1642 
isChildInGroup(View child)1643     private boolean isChildInGroup(View child) {
1644         return child instanceof ExpandableNotificationRow
1645                 && mGroupManager.isChildInGroupWithSummary(
1646                         ((ExpandableNotificationRow) child).getStatusBarNotification());
1647     }
1648 
1649     /**
1650      * Generate a remove animation for a child view.
1651      *
1652      * @param child The view to generate the remove animation for.
1653      * @return Whether an animation was generated.
1654      */
generateRemoveAnimation(View child)1655     private boolean generateRemoveAnimation(View child) {
1656         if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
1657             mAddedHeadsUpChildren.remove(child);
1658             return false;
1659         }
1660         if (isClickedHeadsUp(child)) {
1661             // An animation is already running, add it to the Overlay
1662             mClearOverlayViewsWhenFinished.add(child);
1663             return true;
1664         }
1665         if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
1666             if (!mChildrenToAddAnimated.contains(child)) {
1667                 // Generate Animations
1668                 mChildrenToRemoveAnimated.add(child);
1669                 mNeedsAnimation = true;
1670                 return true;
1671             } else {
1672                 mChildrenToAddAnimated.remove(child);
1673                 mFromMoreCardAdditions.remove(child);
1674                 return false;
1675             }
1676         }
1677         return false;
1678     }
1679 
isClickedHeadsUp(View child)1680     private boolean isClickedHeadsUp(View child) {
1681         return HeadsUpManager.isClickedHeadsUpNotification(child);
1682     }
1683 
1684     /**
1685      * Remove a removed child view from the heads up animations if it was just added there
1686      *
1687      * @return whether any child was removed from the list to animate
1688      */
removeRemovedChildFromHeadsUpChangeAnimations(View child)1689     private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
1690         boolean hasAddEvent = false;
1691         for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
1692             ExpandableNotificationRow row = eventPair.first;
1693             boolean isHeadsUp = eventPair.second;
1694             if (child == row) {
1695                 mTmpList.add(eventPair);
1696                 hasAddEvent |= isHeadsUp;
1697             }
1698         }
1699         if (hasAddEvent) {
1700             // This child was just added lets remove all events.
1701             mHeadsUpChangeAnimations.removeAll(mTmpList);
1702         }
1703         mTmpList.clear();
1704         return hasAddEvent;
1705     }
1706 
1707     /**
1708      * @param child the child to query
1709      * @return whether a view is not a top level child but a child notification and that group is
1710      *         not expanded
1711      */
isChildInInvisibleGroup(View child)1712     private boolean isChildInInvisibleGroup(View child) {
1713         if (child instanceof ExpandableNotificationRow) {
1714             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1715             ExpandableNotificationRow groupSummary =
1716                     mGroupManager.getGroupSummary(row.getStatusBarNotification());
1717             if (groupSummary != null && groupSummary != row) {
1718                 return !groupSummary.areChildrenExpanded();
1719             }
1720         }
1721         return false;
1722     }
1723 
1724     /**
1725      * Updates the scroll position when a child was removed
1726      *
1727      * @param removedChild the removed child
1728      */
updateScrollStateForRemovedChild(View removedChild)1729     private void updateScrollStateForRemovedChild(View removedChild) {
1730         int startingPosition = getPositionInLinearLayout(removedChild);
1731         int childHeight = getIntrinsicHeight(removedChild) + mPaddingBetweenElements;
1732         int endPosition = startingPosition + childHeight;
1733         if (endPosition <= mOwnScrollY) {
1734             // This child is fully scrolled of the top, so we have to deduct its height from the
1735             // scrollPosition
1736             mOwnScrollY -= childHeight;
1737         } else if (startingPosition < mOwnScrollY) {
1738             // This child is currently being scrolled into, set the scroll position to the start of
1739             // this child
1740             mOwnScrollY = startingPosition;
1741         }
1742     }
1743 
getIntrinsicHeight(View view)1744     private int getIntrinsicHeight(View view) {
1745         if (view instanceof ExpandableView) {
1746             ExpandableView expandableView = (ExpandableView) view;
1747             return expandableView.getIntrinsicHeight();
1748         }
1749         return view.getHeight();
1750     }
1751 
getPositionInLinearLayout(View requestedChild)1752     private int getPositionInLinearLayout(View requestedChild) {
1753         int position = 0;
1754         for (int i = 0; i < getChildCount(); i++) {
1755             View child = getChildAt(i);
1756             if (child == requestedChild) {
1757                 return position;
1758             }
1759             if (child.getVisibility() != View.GONE) {
1760                 position += getIntrinsicHeight(child);
1761                 if (i < getChildCount()-1) {
1762                     position += mPaddingBetweenElements;
1763                 }
1764             }
1765         }
1766         return 0;
1767     }
1768 
1769     @Override
onViewAdded(View child)1770     public void onViewAdded(View child) {
1771         super.onViewAdded(child);
1772         onViewAddedInternal(child);
1773     }
1774 
onViewAddedInternal(View child)1775     private void onViewAddedInternal(View child) {
1776         updateHideSensitiveForChild(child);
1777         mStackScrollAlgorithm.notifyChildrenChanged(this);
1778         ((ExpandableView) child).setOnHeightChangedListener(this);
1779         generateAddAnimation(child, false /* fromMoreCard */);
1780         updateAnimationState(child);
1781         if (canChildBeDismissed(child)) {
1782             // Make sure the dismissButton is visible and not in the animated state.
1783             // We need to do this to avoid a race where a clearable notification is added after the
1784             // dismiss animation is finished
1785             mDismissView.showClearButton();
1786         }
1787     }
1788 
updateHideSensitiveForChild(View child)1789     private void updateHideSensitiveForChild(View child) {
1790         if (mAmbientState.isHideSensitive() && child instanceof ExpandableView) {
1791             ExpandableView expandableView = (ExpandableView) child;
1792             expandableView.setHideSensitiveForIntrinsicHeight(true);
1793         }
1794     }
1795 
notifyGroupChildRemoved(View row)1796     public void notifyGroupChildRemoved(View row) {
1797         onViewRemovedInternal(row);
1798     }
1799 
notifyGroupChildAdded(View row)1800     public void notifyGroupChildAdded(View row) {
1801         onViewAddedInternal(row);
1802     }
1803 
setAnimationsEnabled(boolean animationsEnabled)1804     public void setAnimationsEnabled(boolean animationsEnabled) {
1805         mAnimationsEnabled = animationsEnabled;
1806         updateNotificationAnimationStates();
1807     }
1808 
updateNotificationAnimationStates()1809     private void updateNotificationAnimationStates() {
1810         boolean running = mAnimationsEnabled;
1811         int childCount = getChildCount();
1812         for (int i = 0; i < childCount; i++) {
1813             View child = getChildAt(i);
1814             running &= mIsExpanded || isPinnedHeadsUp(child);
1815             updateAnimationState(running, child);
1816         }
1817     }
1818 
updateAnimationState(View child)1819     private void updateAnimationState(View child) {
1820         updateAnimationState((mAnimationsEnabled || isPinnedHeadsUp(child)) && mIsExpanded, child);
1821     }
1822 
1823 
updateAnimationState(boolean running, View child)1824     private void updateAnimationState(boolean running, View child) {
1825         if (child instanceof ExpandableNotificationRow) {
1826             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1827             row.setIconAnimationRunning(running);
1828         }
1829     }
1830 
isAddOrRemoveAnimationPending()1831     public boolean isAddOrRemoveAnimationPending() {
1832         return mNeedsAnimation
1833                 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
1834     }
1835     /**
1836      * Generate an animation for an added child view.
1837      *
1838      * @param child The view to be added.
1839      * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
1840      */
generateAddAnimation(View child, boolean fromMoreCard)1841     public void generateAddAnimation(View child, boolean fromMoreCard) {
1842         if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
1843             // Generate Animations
1844             mChildrenToAddAnimated.add(child);
1845             if (fromMoreCard) {
1846                 mFromMoreCardAdditions.add(child);
1847             }
1848             mNeedsAnimation = true;
1849         }
1850         if (isHeadsUp(child)) {
1851             mAddedHeadsUpChildren.add(child);
1852             mChildrenToAddAnimated.remove(child);
1853         }
1854     }
1855 
1856     /**
1857      * Change the position of child to a new location
1858      *
1859      * @param child the view to change the position for
1860      * @param newIndex the new index
1861      */
changeViewPosition(View child, int newIndex)1862     public void changeViewPosition(View child, int newIndex) {
1863         int currentIndex = indexOfChild(child);
1864         if (child != null && child.getParent() == this && currentIndex != newIndex) {
1865             mChangePositionInProgress = true;
1866             removeView(child);
1867             addView(child, newIndex);
1868             mChangePositionInProgress = false;
1869             if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
1870                 mChildrenChangingPositions.add(child);
1871                 mNeedsAnimation = true;
1872             }
1873         }
1874     }
1875 
startAnimationToState()1876     private void startAnimationToState() {
1877         if (mNeedsAnimation) {
1878             generateChildHierarchyEvents();
1879             mNeedsAnimation = false;
1880         }
1881         if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
1882             mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState,
1883                     mGoToFullShadeDelay);
1884             mAnimationEvents.clear();
1885         } else {
1886             applyCurrentState();
1887         }
1888         mGoToFullShadeDelay = 0;
1889     }
1890 
generateChildHierarchyEvents()1891     private void generateChildHierarchyEvents() {
1892         generateHeadsUpAnimationEvents();
1893         generateChildRemovalEvents();
1894         generateChildAdditionEvents();
1895         generatePositionChangeEvents();
1896         generateSnapBackEvents();
1897         generateDragEvents();
1898         generateTopPaddingEvent();
1899         generateActivateEvent();
1900         generateDimmedEvent();
1901         generateHideSensitiveEvent();
1902         generateDarkEvent();
1903         generateGoToFullShadeEvent();
1904         generateViewResizeEvent();
1905         generateGroupExpansionEvent();
1906         generateAnimateEverythingEvent();
1907         mNeedsAnimation = false;
1908     }
1909 
generateHeadsUpAnimationEvents()1910     private void generateHeadsUpAnimationEvents() {
1911         for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
1912             ExpandableNotificationRow row = eventPair.first;
1913             boolean isHeadsUp = eventPair.second;
1914             int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
1915             boolean onBottom = false;
1916             boolean pinnedAndClosed = row.isPinned() && !mIsExpanded;
1917             if (!mIsExpanded && !isHeadsUp) {
1918                 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
1919             } else {
1920                 StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
1921                 if (viewState == null) {
1922                     // A view state was never generated for this view, so we don't need to animate
1923                     // this. This may happen with notification children.
1924                     continue;
1925                 }
1926                 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
1927                     if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
1928                         // Our custom add animation
1929                         type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
1930                     } else {
1931                         // Normal add animation
1932                         type = AnimationEvent.ANIMATION_TYPE_ADD;
1933                     }
1934                     onBottom = !pinnedAndClosed;
1935                 }
1936             }
1937             AnimationEvent event = new AnimationEvent(row, type);
1938             event.headsUpFromBottom = onBottom;
1939             mAnimationEvents.add(event);
1940         }
1941         mHeadsUpChangeAnimations.clear();
1942         mAddedHeadsUpChildren.clear();
1943     }
1944 
shouldHunAppearFromBottom(StackViewState viewState)1945     private boolean shouldHunAppearFromBottom(StackViewState viewState) {
1946         if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
1947             return false;
1948         }
1949         return true;
1950     }
1951 
generateGroupExpansionEvent()1952     private void generateGroupExpansionEvent() {
1953         // Generate a group expansion/collapsing event if there is such a group at all
1954         if (mExpandedGroupView != null) {
1955             mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
1956                     AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
1957             mExpandedGroupView = null;
1958         }
1959     }
1960 
generateViewResizeEvent()1961     private void generateViewResizeEvent() {
1962         if (mNeedViewResizeAnimation) {
1963             mAnimationEvents.add(
1964                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
1965         }
1966         mNeedViewResizeAnimation = false;
1967     }
1968 
generateSnapBackEvents()1969     private void generateSnapBackEvents() {
1970         for (View child : mSnappedBackChildren) {
1971             mAnimationEvents.add(new AnimationEvent(child,
1972                     AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
1973         }
1974         mSnappedBackChildren.clear();
1975     }
1976 
generateDragEvents()1977     private void generateDragEvents() {
1978         for (View child : mDragAnimPendingChildren) {
1979             mAnimationEvents.add(new AnimationEvent(child,
1980                     AnimationEvent.ANIMATION_TYPE_START_DRAG));
1981         }
1982         mDragAnimPendingChildren.clear();
1983     }
1984 
generateChildRemovalEvents()1985     private void generateChildRemovalEvents() {
1986         for (View child : mChildrenToRemoveAnimated) {
1987             boolean childWasSwipedOut = mSwipedOutViews.contains(child);
1988             int animationType = childWasSwipedOut
1989                     ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
1990                     : AnimationEvent.ANIMATION_TYPE_REMOVE;
1991             AnimationEvent event = new AnimationEvent(child, animationType);
1992 
1993             // we need to know the view after this one
1994             event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY());
1995             mAnimationEvents.add(event);
1996         }
1997         mSwipedOutViews.clear();
1998         mChildrenToRemoveAnimated.clear();
1999     }
2000 
generatePositionChangeEvents()2001     private void generatePositionChangeEvents() {
2002         for (View child : mChildrenChangingPositions) {
2003             mAnimationEvents.add(new AnimationEvent(child,
2004                     AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2005         }
2006         mChildrenChangingPositions.clear();
2007         if (mGenerateChildOrderChangedEvent) {
2008             mAnimationEvents.add(new AnimationEvent(null,
2009                     AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2010             mGenerateChildOrderChangedEvent = false;
2011         }
2012     }
2013 
generateChildAdditionEvents()2014     private void generateChildAdditionEvents() {
2015         for (View child : mChildrenToAddAnimated) {
2016             if (mFromMoreCardAdditions.contains(child)) {
2017                 mAnimationEvents.add(new AnimationEvent(child,
2018                         AnimationEvent.ANIMATION_TYPE_ADD,
2019                         StackStateAnimator.ANIMATION_DURATION_STANDARD));
2020             } else {
2021                 mAnimationEvents.add(new AnimationEvent(child,
2022                         AnimationEvent.ANIMATION_TYPE_ADD));
2023             }
2024         }
2025         mChildrenToAddAnimated.clear();
2026         mFromMoreCardAdditions.clear();
2027     }
2028 
generateTopPaddingEvent()2029     private void generateTopPaddingEvent() {
2030         if (mTopPaddingNeedsAnimation) {
2031             mAnimationEvents.add(
2032                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED));
2033         }
2034         mTopPaddingNeedsAnimation = false;
2035     }
2036 
generateActivateEvent()2037     private void generateActivateEvent() {
2038         if (mActivateNeedsAnimation) {
2039             mAnimationEvents.add(
2040                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
2041         }
2042         mActivateNeedsAnimation = false;
2043     }
2044 
generateAnimateEverythingEvent()2045     private void generateAnimateEverythingEvent() {
2046         if (mEverythingNeedsAnimation) {
2047             mAnimationEvents.add(
2048                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
2049         }
2050         mEverythingNeedsAnimation = false;
2051     }
2052 
generateDimmedEvent()2053     private void generateDimmedEvent() {
2054         if (mDimmedNeedsAnimation) {
2055             mAnimationEvents.add(
2056                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
2057         }
2058         mDimmedNeedsAnimation = false;
2059     }
2060 
generateHideSensitiveEvent()2061     private void generateHideSensitiveEvent() {
2062         if (mHideSensitiveNeedsAnimation) {
2063             mAnimationEvents.add(
2064                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
2065         }
2066         mHideSensitiveNeedsAnimation = false;
2067     }
2068 
generateDarkEvent()2069     private void generateDarkEvent() {
2070         if (mDarkNeedsAnimation) {
2071             AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK);
2072             ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
2073             mAnimationEvents.add(ev);
2074         }
2075         mDarkNeedsAnimation = false;
2076     }
2077 
generateGoToFullShadeEvent()2078     private void generateGoToFullShadeEvent() {
2079         if (mGoToFullShadeNeedsAnimation) {
2080             mAnimationEvents.add(
2081                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
2082         }
2083         mGoToFullShadeNeedsAnimation = false;
2084     }
2085 
onInterceptTouchEventScroll(MotionEvent ev)2086     private boolean onInterceptTouchEventScroll(MotionEvent ev) {
2087         if (!isScrollingEnabled()) {
2088             return false;
2089         }
2090         /*
2091          * This method JUST determines whether we want to intercept the motion.
2092          * If we return true, onMotionEvent will be called and we do the actual
2093          * scrolling there.
2094          */
2095 
2096         /*
2097         * Shortcut the most recurring case: the user is in the dragging
2098         * state and he is moving his finger.  We want to intercept this
2099         * motion.
2100         */
2101         final int action = ev.getAction();
2102         if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
2103             return true;
2104         }
2105 
2106         switch (action & MotionEvent.ACTION_MASK) {
2107             case MotionEvent.ACTION_MOVE: {
2108                 /*
2109                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
2110                  * whether the user has moved far enough from his original down touch.
2111                  */
2112 
2113                 /*
2114                 * Locally do absolute value. mLastMotionY is set to the y value
2115                 * of the down event.
2116                 */
2117                 final int activePointerId = mActivePointerId;
2118                 if (activePointerId == INVALID_POINTER) {
2119                     // If we don't have a valid id, the touch down wasn't on content.
2120                     break;
2121                 }
2122 
2123                 final int pointerIndex = ev.findPointerIndex(activePointerId);
2124                 if (pointerIndex == -1) {
2125                     Log.e(TAG, "Invalid pointerId=" + activePointerId
2126                             + " in onInterceptTouchEvent");
2127                     break;
2128                 }
2129 
2130                 final int y = (int) ev.getY(pointerIndex);
2131                 final int x = (int) ev.getX(pointerIndex);
2132                 final int yDiff = Math.abs(y - mLastMotionY);
2133                 final int xDiff = Math.abs(x - mDownX);
2134                 if (yDiff > mTouchSlop && yDiff > xDiff) {
2135                     setIsBeingDragged(true);
2136                     mLastMotionY = y;
2137                     mDownX = x;
2138                     initVelocityTrackerIfNotExists();
2139                     mVelocityTracker.addMovement(ev);
2140                 }
2141                 break;
2142             }
2143 
2144             case MotionEvent.ACTION_DOWN: {
2145                 final int y = (int) ev.getY();
2146                 if (getChildAtPosition(ev.getX(), y) == null) {
2147                     setIsBeingDragged(false);
2148                     recycleVelocityTracker();
2149                     break;
2150                 }
2151 
2152                 /*
2153                  * Remember location of down touch.
2154                  * ACTION_DOWN always refers to pointer index 0.
2155                  */
2156                 mLastMotionY = y;
2157                 mDownX = (int) ev.getX();
2158                 mActivePointerId = ev.getPointerId(0);
2159                 mScrolledToTopOnFirstDown = isScrolledToTop();
2160 
2161                 initOrResetVelocityTracker();
2162                 mVelocityTracker.addMovement(ev);
2163                 /*
2164                 * If being flinged and user touches the screen, initiate drag;
2165                 * otherwise don't.  mScroller.isFinished should be false when
2166                 * being flinged.
2167                 */
2168                 boolean isBeingDragged = !mScroller.isFinished();
2169                 setIsBeingDragged(isBeingDragged);
2170                 break;
2171             }
2172 
2173             case MotionEvent.ACTION_CANCEL:
2174             case MotionEvent.ACTION_UP:
2175                 /* Release the drag */
2176                 setIsBeingDragged(false);
2177                 mActivePointerId = INVALID_POINTER;
2178                 recycleVelocityTracker();
2179                 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
2180                     postInvalidateOnAnimation();
2181                 }
2182                 break;
2183             case MotionEvent.ACTION_POINTER_UP:
2184                 onSecondaryPointerUp(ev);
2185                 break;
2186         }
2187 
2188         /*
2189         * The only time we want to intercept motion events is if we are in the
2190         * drag mode.
2191         */
2192         return mIsBeingDragged;
2193     }
2194 
2195     /**
2196      * @return Whether the specified motion event is actually happening over the content.
2197      */
isInContentBounds(MotionEvent event)2198     private boolean isInContentBounds(MotionEvent event) {
2199         return isInContentBounds(event.getY());
2200     }
2201 
2202     /**
2203      * @return Whether a y coordinate is inside the content.
2204      */
isInContentBounds(float y)2205     public boolean isInContentBounds(float y) {
2206         return y < getHeight() - getEmptyBottomMargin();
2207     }
2208 
setIsBeingDragged(boolean isDragged)2209     private void setIsBeingDragged(boolean isDragged) {
2210         mIsBeingDragged = isDragged;
2211         if (isDragged) {
2212             requestDisallowInterceptTouchEvent(true);
2213             removeLongPressCallback();
2214         }
2215     }
2216 
2217     @Override
onWindowFocusChanged(boolean hasWindowFocus)2218     public void onWindowFocusChanged(boolean hasWindowFocus) {
2219         super.onWindowFocusChanged(hasWindowFocus);
2220         if (!hasWindowFocus) {
2221             removeLongPressCallback();
2222         }
2223     }
2224 
removeLongPressCallback()2225     public void removeLongPressCallback() {
2226         mSwipeHelper.removeLongPressCallback();
2227     }
2228 
2229     @Override
isScrolledToTop()2230     public boolean isScrolledToTop() {
2231         return mOwnScrollY == 0;
2232     }
2233 
2234     @Override
isScrolledToBottom()2235     public boolean isScrolledToBottom() {
2236         return mOwnScrollY >= getScrollRange();
2237     }
2238 
2239     @Override
getHostView()2240     public View getHostView() {
2241         return this;
2242     }
2243 
getEmptyBottomMargin()2244     public int getEmptyBottomMargin() {
2245         int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize;
2246         if (needsHeightAdaption()) {
2247             emptyMargin -= mBottomStackSlowDownHeight;
2248         } else {
2249             emptyMargin -= mCollapseSecondCardPadding;
2250         }
2251         return Math.max(emptyMargin, 0);
2252     }
2253 
onExpansionStarted()2254     public void onExpansionStarted() {
2255         mIsExpansionChanging = true;
2256         mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
2257     }
2258 
onExpansionStopped()2259     public void onExpansionStopped() {
2260         mIsExpansionChanging = false;
2261         mStackScrollAlgorithm.onExpansionStopped();
2262         if (!mIsExpanded) {
2263             mOwnScrollY = 0;
2264 
2265             // lets make sure nothing is in the overlay anymore
2266             getOverlay().clear();
2267         }
2268     }
2269 
onPanelTrackingStarted()2270     public void onPanelTrackingStarted() {
2271         mPanelTracking = true;
2272     }
onPanelTrackingStopped()2273     public void onPanelTrackingStopped() {
2274         mPanelTracking = false;
2275     }
2276 
resetScrollPosition()2277     public void resetScrollPosition() {
2278         mScroller.abortAnimation();
2279         mOwnScrollY = 0;
2280     }
2281 
setIsExpanded(boolean isExpanded)2282     private void setIsExpanded(boolean isExpanded) {
2283         boolean changed = isExpanded != mIsExpanded;
2284         mIsExpanded = isExpanded;
2285         mStackScrollAlgorithm.setIsExpanded(isExpanded);
2286         if (changed) {
2287             updateNotificationAnimationStates();
2288         }
2289     }
2290 
2291     @Override
onHeightChanged(ExpandableView view, boolean needsAnimation)2292     public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
2293         updateContentHeight();
2294         updateScrollPositionOnExpandInBottom(view);
2295         clampScrollPosition();
2296         notifyHeightChangeListener(view);
2297         if (needsAnimation) {
2298             requestAnimationOnViewResize();
2299         }
2300         requestChildrenUpdate();
2301     }
2302 
2303     @Override
onReset(ExpandableView view)2304     public void onReset(ExpandableView view) {
2305         if (mIsExpanded && mAnimationsEnabled) {
2306             mRequestViewResizeAnimationOnLayout = true;
2307         }
2308         mStackScrollAlgorithm.onReset(view);
2309         updateAnimationState(view);
2310     }
2311 
updateScrollPositionOnExpandInBottom(ExpandableView view)2312     private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
2313         if (view instanceof ExpandableNotificationRow) {
2314             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
2315             if (row.isUserLocked() && row != getFirstChildNotGone()) {
2316                 // We are actually expanding this view
2317                 float endPosition = row.getTranslationY() + row.getActualHeight();
2318                 int stackEnd = mMaxLayoutHeight - mBottomStackPeekSize -
2319                         mBottomStackSlowDownHeight + (int) mStackTranslation;
2320                 if (endPosition > stackEnd) {
2321                     mOwnScrollY += endPosition - stackEnd;
2322                     mDisallowScrollingInThisMotion = true;
2323                 }
2324             }
2325         }
2326     }
2327 
setOnHeightChangedListener( ExpandableView.OnHeightChangedListener mOnHeightChangedListener)2328     public void setOnHeightChangedListener(
2329             ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
2330         this.mOnHeightChangedListener = mOnHeightChangedListener;
2331     }
2332 
setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener)2333     public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
2334         mOnEmptySpaceClickListener = listener;
2335     }
2336 
onChildAnimationFinished()2337     public void onChildAnimationFinished() {
2338         requestChildrenUpdate();
2339         runAnimationFinishedRunnables();
2340         clearViewOverlays();
2341     }
2342 
clearViewOverlays()2343     private void clearViewOverlays() {
2344         for (View view : mClearOverlayViewsWhenFinished) {
2345             getOverlay().remove(view);
2346         }
2347     }
2348 
runAnimationFinishedRunnables()2349     private void runAnimationFinishedRunnables() {
2350         for (Runnable runnable : mAnimationFinishedRunnables) {
2351             runnable.run();
2352         }
2353         mAnimationFinishedRunnables.clear();
2354     }
2355 
2356     /**
2357      * See {@link AmbientState#setDimmed}.
2358      */
setDimmed(boolean dimmed, boolean animate)2359     public void setDimmed(boolean dimmed, boolean animate) {
2360         mStackScrollAlgorithm.setDimmed(dimmed);
2361         mAmbientState.setDimmed(dimmed);
2362         updatePadding(dimmed);
2363         if (animate && mAnimationsEnabled) {
2364             mDimmedNeedsAnimation = true;
2365             mNeedsAnimation =  true;
2366         }
2367         requestChildrenUpdate();
2368     }
2369 
setHideSensitive(boolean hideSensitive, boolean animate)2370     public void setHideSensitive(boolean hideSensitive, boolean animate) {
2371         if (hideSensitive != mAmbientState.isHideSensitive()) {
2372             int childCount = getChildCount();
2373             for (int i = 0; i < childCount; i++) {
2374                 ExpandableView v = (ExpandableView) getChildAt(i);
2375                 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
2376             }
2377             mAmbientState.setHideSensitive(hideSensitive);
2378             if (animate && mAnimationsEnabled) {
2379                 mHideSensitiveNeedsAnimation = true;
2380                 mNeedsAnimation =  true;
2381             }
2382             requestChildrenUpdate();
2383         }
2384     }
2385 
2386     /**
2387      * See {@link AmbientState#setActivatedChild}.
2388      */
setActivatedChild(ActivatableNotificationView activatedChild)2389     public void setActivatedChild(ActivatableNotificationView activatedChild) {
2390         mAmbientState.setActivatedChild(activatedChild);
2391         if (mAnimationsEnabled) {
2392             mActivateNeedsAnimation = true;
2393             mNeedsAnimation =  true;
2394         }
2395         requestChildrenUpdate();
2396     }
2397 
getActivatedChild()2398     public ActivatableNotificationView getActivatedChild() {
2399         return mAmbientState.getActivatedChild();
2400     }
2401 
applyCurrentState()2402     private void applyCurrentState() {
2403         mCurrentStackScrollState.apply();
2404         if (mListener != null) {
2405             mListener.onChildLocationsChanged(this);
2406         }
2407         runAnimationFinishedRunnables();
2408     }
2409 
setSpeedBumpView(SpeedBumpView speedBumpView)2410     public void setSpeedBumpView(SpeedBumpView speedBumpView) {
2411         mSpeedBumpView = speedBumpView;
2412         addView(speedBumpView);
2413     }
2414 
updateSpeedBump(boolean visible)2415     private void updateSpeedBump(boolean visible) {
2416         boolean notGoneBefore = mSpeedBumpView.getVisibility() != GONE;
2417         if (visible != notGoneBefore) {
2418             int newVisibility = visible ? VISIBLE : GONE;
2419             mSpeedBumpView.setVisibility(newVisibility);
2420             if (visible) {
2421                 // Make invisible to ensure that the appear animation is played.
2422                 mSpeedBumpView.setInvisible();
2423             } else {
2424                 // TODO: This doesn't really work, because the view is already set to GONE above.
2425                 generateRemoveAnimation(mSpeedBumpView);
2426             }
2427         }
2428     }
2429 
goToFullShade(long delay)2430     public void goToFullShade(long delay) {
2431         updateSpeedBump(true /* visibility */);
2432         mDismissView.setInvisible();
2433         mEmptyShadeView.setInvisible();
2434         mGoToFullShadeNeedsAnimation = true;
2435         mGoToFullShadeDelay = delay;
2436         mNeedsAnimation = true;
2437         requestChildrenUpdate();
2438     }
2439 
cancelExpandHelper()2440     public void cancelExpandHelper() {
2441         mExpandHelper.cancel();
2442     }
2443 
setIntrinsicPadding(int intrinsicPadding)2444     public void setIntrinsicPadding(int intrinsicPadding) {
2445         mIntrinsicPadding = intrinsicPadding;
2446     }
2447 
getIntrinsicPadding()2448     public int getIntrinsicPadding() {
2449         return mIntrinsicPadding;
2450     }
2451 
2452     /**
2453      * @return the y position of the first notification
2454      */
getNotificationsTopY()2455     public float getNotificationsTopY() {
2456         return mTopPadding + getStackTranslation();
2457     }
2458 
2459     @Override
shouldDelayChildPressedState()2460     public boolean shouldDelayChildPressedState() {
2461         return true;
2462     }
2463 
2464     /**
2465      * See {@link AmbientState#setDark}.
2466      */
setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation)2467     public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) {
2468         mAmbientState.setDark(dark);
2469         if (animate && mAnimationsEnabled) {
2470             mDarkNeedsAnimation = true;
2471             mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
2472             mNeedsAnimation =  true;
2473         }
2474         requestChildrenUpdate();
2475     }
2476 
findDarkAnimationOriginIndex(@ullable PointF screenLocation)2477     private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
2478         if (screenLocation == null || screenLocation.y < mTopPadding + mTopPaddingOverflow) {
2479             return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
2480         }
2481         if (screenLocation.y > getBottomMostNotificationBottom()) {
2482             return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW;
2483         }
2484         View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y);
2485         if (child != null) {
2486             return getNotGoneIndex(child);
2487         } else {
2488             return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
2489         }
2490     }
2491 
getNotGoneIndex(View child)2492     private int getNotGoneIndex(View child) {
2493         int count = getChildCount();
2494         int notGoneIndex = 0;
2495         for (int i = 0; i < count; i++) {
2496             View v = getChildAt(i);
2497             if (child == v) {
2498                 return notGoneIndex;
2499             }
2500             if (v.getVisibility() != View.GONE) {
2501                 notGoneIndex++;
2502             }
2503         }
2504         return -1;
2505     }
2506 
setDismissView(DismissView dismissView)2507     public void setDismissView(DismissView dismissView) {
2508         mDismissView = dismissView;
2509         addView(mDismissView);
2510     }
2511 
setEmptyShadeView(EmptyShadeView emptyShadeView)2512     public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
2513         mEmptyShadeView = emptyShadeView;
2514         addView(mEmptyShadeView);
2515     }
2516 
updateEmptyShadeView(boolean visible)2517     public void updateEmptyShadeView(boolean visible) {
2518         int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility();
2519         int newVisibility = visible ? VISIBLE : GONE;
2520         if (oldVisibility != newVisibility) {
2521             if (newVisibility != GONE) {
2522                 if (mEmptyShadeView.willBeGone()) {
2523                     mEmptyShadeView.cancelAnimation();
2524                 } else {
2525                     mEmptyShadeView.setInvisible();
2526                 }
2527                 mEmptyShadeView.setVisibility(newVisibility);
2528                 mEmptyShadeView.setWillBeGone(false);
2529                 updateContentHeight();
2530                 notifyHeightChangeListener(mEmptyShadeView);
2531             } else {
2532                 Runnable onFinishedRunnable = new Runnable() {
2533                     @Override
2534                     public void run() {
2535                         mEmptyShadeView.setVisibility(GONE);
2536                         mEmptyShadeView.setWillBeGone(false);
2537                         updateContentHeight();
2538                         notifyHeightChangeListener(mEmptyShadeView);
2539                     }
2540                 };
2541                 if (mAnimationsEnabled) {
2542                     mEmptyShadeView.setWillBeGone(true);
2543                     mEmptyShadeView.performVisibilityAnimation(false, onFinishedRunnable);
2544                 } else {
2545                     mEmptyShadeView.setInvisible();
2546                     onFinishedRunnable.run();
2547                 }
2548             }
2549         }
2550     }
2551 
setOverflowContainer(NotificationOverflowContainer overFlowContainer)2552     public void setOverflowContainer(NotificationOverflowContainer overFlowContainer) {
2553         mOverflowContainer = overFlowContainer;
2554         addView(mOverflowContainer);
2555     }
2556 
updateOverflowContainerVisibility(boolean visible)2557     public void updateOverflowContainerVisibility(boolean visible) {
2558         int oldVisibility = mOverflowContainer.willBeGone() ? GONE
2559                 : mOverflowContainer.getVisibility();
2560         final int newVisibility = visible ? VISIBLE : GONE;
2561         if (oldVisibility != newVisibility) {
2562             Runnable onFinishedRunnable = new Runnable() {
2563                 @Override
2564                 public void run() {
2565                     mOverflowContainer.setVisibility(newVisibility);
2566                     mOverflowContainer.setWillBeGone(false);
2567                     updateContentHeight();
2568                     notifyHeightChangeListener(mOverflowContainer);
2569                 }
2570             };
2571             if (!mAnimationsEnabled || !mIsExpanded) {
2572                 mOverflowContainer.cancelAppearDrawing();
2573                 onFinishedRunnable.run();
2574             } else if (newVisibility != GONE) {
2575                 mOverflowContainer.performAddAnimation(0,
2576                         StackStateAnimator.ANIMATION_DURATION_STANDARD);
2577                 mOverflowContainer.setVisibility(newVisibility);
2578                 mOverflowContainer.setWillBeGone(false);
2579                 updateContentHeight();
2580                 notifyHeightChangeListener(mOverflowContainer);
2581             } else {
2582                 mOverflowContainer.performRemoveAnimation(
2583                         StackStateAnimator.ANIMATION_DURATION_STANDARD,
2584                         0.0f,
2585                         onFinishedRunnable);
2586                 mOverflowContainer.setWillBeGone(true);
2587             }
2588         }
2589     }
2590 
updateDismissView(boolean visible)2591     public void updateDismissView(boolean visible) {
2592         int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
2593         int newVisibility = visible ? VISIBLE : GONE;
2594         if (oldVisibility != newVisibility) {
2595             if (newVisibility != GONE) {
2596                 if (mDismissView.willBeGone()) {
2597                     mDismissView.cancelAnimation();
2598                 } else {
2599                     mDismissView.setInvisible();
2600                 }
2601                 mDismissView.setVisibility(newVisibility);
2602                 mDismissView.setWillBeGone(false);
2603                 updateContentHeight();
2604                 notifyHeightChangeListener(mDismissView);
2605             } else {
2606                 Runnable dimissHideFinishRunnable = new Runnable() {
2607                     @Override
2608                     public void run() {
2609                         mDismissView.setVisibility(GONE);
2610                         mDismissView.setWillBeGone(false);
2611                         updateContentHeight();
2612                         notifyHeightChangeListener(mDismissView);
2613                     }
2614                 };
2615                 if (mDismissView.isButtonVisible() && mIsExpanded && mAnimationsEnabled) {
2616                     mDismissView.setWillBeGone(true);
2617                     mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable);
2618                 } else {
2619                     dimissHideFinishRunnable.run();
2620                     mDismissView.showClearButton();
2621                 }
2622             }
2623         }
2624     }
2625 
setDismissAllInProgress(boolean dismissAllInProgress)2626     public void setDismissAllInProgress(boolean dismissAllInProgress) {
2627         mDismissAllInProgress = dismissAllInProgress;
2628         mDismissView.setDismissAllInProgress(dismissAllInProgress);
2629         mAmbientState.setDismissAllInProgress(dismissAllInProgress);
2630         if (dismissAllInProgress) {
2631             disableClipOptimization();
2632         }
2633         handleDismissAllClipping();
2634     }
2635 
handleDismissAllClipping()2636     private void handleDismissAllClipping() {
2637         final int count = getChildCount();
2638         boolean previousChildWillBeDismissed = false;
2639         for (int i = 0; i < count; i++) {
2640             ExpandableView child = (ExpandableView) getChildAt(i);
2641             if (child.getVisibility() == GONE) {
2642                 continue;
2643             }
2644             if (mDismissAllInProgress && previousChildWillBeDismissed) {
2645                 child.setMinClipTopAmount(child.getClipTopAmount());
2646             } else {
2647                 child.setMinClipTopAmount(0);
2648             }
2649             previousChildWillBeDismissed = canChildBeDismissed(child);
2650         }
2651     }
2652 
disableClipOptimization()2653     private void disableClipOptimization() {
2654         final int count = getChildCount();
2655         for (int i = 0; i < count; i++) {
2656             ExpandableView child = (ExpandableView) getChildAt(i);
2657             if (child.getVisibility() == GONE) {
2658                 continue;
2659             }
2660             child.setClipTopOptimization(0);
2661         }
2662     }
2663 
isDismissViewNotGone()2664     public boolean isDismissViewNotGone() {
2665         return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone();
2666     }
2667 
isDismissViewVisible()2668     public boolean isDismissViewVisible() {
2669         return mDismissView.isVisible();
2670     }
2671 
getDismissViewHeight()2672     public int getDismissViewHeight() {
2673         int height = mDismissView.getHeight() + mPaddingBetweenElementsNormal;
2674 
2675         // Hack: Accommodate for additional distance when we only have one notification and the
2676         // dismiss all button.
2677         if (getNotGoneChildCount() == 2 && getLastChildNotGone() == mDismissView
2678                 && getFirstChildNotGone() instanceof ActivatableNotificationView) {
2679             height += mCollapseSecondCardPadding;
2680         }
2681         return height;
2682     }
2683 
getEmptyShadeViewHeight()2684     public int getEmptyShadeViewHeight() {
2685         return mEmptyShadeView.getHeight();
2686     }
2687 
getBottomMostNotificationBottom()2688     public float getBottomMostNotificationBottom() {
2689         final int count = getChildCount();
2690         float max = 0;
2691         for (int childIdx = 0; childIdx < count; childIdx++) {
2692             ExpandableView child = (ExpandableView) getChildAt(childIdx);
2693             if (child.getVisibility() == GONE) {
2694                 continue;
2695             }
2696             float bottom = child.getTranslationY() + child.getActualHeight();
2697             if (bottom > max) {
2698                 max = bottom;
2699             }
2700         }
2701         return max + getStackTranslation();
2702     }
2703 
2704     /**
2705      * @param qsMinHeight The minimum height of the quick settings including padding
2706      *                    See {@link StackScrollAlgorithm#updateIsSmallScreen}.
2707      */
updateIsSmallScreen(int qsMinHeight)2708     public void updateIsSmallScreen(int qsMinHeight) {
2709         mStackScrollAlgorithm.updateIsSmallScreen(mMaxLayoutHeight - qsMinHeight);
2710     }
2711 
setPhoneStatusBar(PhoneStatusBar phoneStatusBar)2712     public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
2713         this.mPhoneStatusBar = phoneStatusBar;
2714     }
2715 
setGroupManager(NotificationGroupManager groupManager)2716     public void setGroupManager(NotificationGroupManager groupManager) {
2717         this.mGroupManager = groupManager;
2718     }
2719 
onGoToKeyguard()2720     public void onGoToKeyguard() {
2721         requestAnimateEverything();
2722     }
2723 
requestAnimateEverything()2724     private void requestAnimateEverything() {
2725         if (mIsExpanded && mAnimationsEnabled) {
2726             mEverythingNeedsAnimation = true;
2727             mNeedsAnimation = true;
2728             requestChildrenUpdate();
2729         }
2730     }
2731 
isBelowLastNotification(float touchX, float touchY)2732     public boolean isBelowLastNotification(float touchX, float touchY) {
2733         int childCount = getChildCount();
2734         for (int i = childCount - 1; i >= 0; i--) {
2735             ExpandableView child = (ExpandableView) getChildAt(i);
2736             if (child.getVisibility() != View.GONE) {
2737                 float childTop = child.getY();
2738                 if (childTop > touchY) {
2739                     // we are above a notification entirely let's abort
2740                     return false;
2741                 }
2742                 boolean belowChild = touchY > childTop + child.getActualHeight();
2743                 if (child == mDismissView) {
2744                     if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(),
2745                                     touchY - childTop)) {
2746                         // We clicked on the dismiss button
2747                         return false;
2748                     }
2749                 } else if (child == mEmptyShadeView) {
2750                     // We arrived at the empty shade view, for which we accept all clicks
2751                     return true;
2752                 } else if (!belowChild){
2753                     // We are on a child
2754                     return false;
2755                 }
2756             }
2757         }
2758         return touchY > mTopPadding + mStackTranslation;
2759     }
2760 
updateExpandButtons()2761     private void updateExpandButtons() {
2762         for (int i = 0; i < getChildCount(); i++) {
2763             View child = getChildAt(i);
2764             if (child instanceof ExpandableNotificationRow) {
2765                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2766                 row.updateExpandButton();
2767             }
2768         }
2769     }
2770 
2771     @Override
onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded)2772     public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
2773         boolean animated = mAnimationsEnabled && mIsExpanded;
2774         if (animated) {
2775             mExpandedGroupView = changedRow;
2776             mNeedsAnimation = true;
2777         }
2778         changedRow.setChildrenExpanded(expanded, animated);
2779         onHeightChanged(changedRow, false /* needsAnimation */);
2780     }
2781 
2782     @Override
onGroupsProhibitedChanged()2783     public void onGroupsProhibitedChanged() {
2784         updateExpandButtons();
2785     }
2786 
2787     @Override
onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group)2788     public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
2789         for (NotificationData.Entry entry : group.children) {
2790             ExpandableNotificationRow row = entry.row;
2791             if (indexOfChild(row) != -1) {
2792                 removeView(row);
2793                 group.summary.row.addChildNotification(row);
2794             }
2795         }
2796     }
2797 
generateChildOrderChangedEvent()2798     public void generateChildOrderChangedEvent() {
2799         if (mIsExpanded && mAnimationsEnabled) {
2800             mGenerateChildOrderChangedEvent = true;
2801             mNeedsAnimation = true;
2802             requestChildrenUpdate();
2803         }
2804     }
2805 
runAfterAnimationFinished(Runnable runnable)2806     public void runAfterAnimationFinished(Runnable runnable) {
2807         mAnimationFinishedRunnables.add(runnable);
2808     }
2809 
setHeadsUpManager(HeadsUpManager headsUpManager)2810     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
2811         mHeadsUpManager = headsUpManager;
2812         mAmbientState.setHeadsUpManager(headsUpManager);
2813         mStackScrollAlgorithm.setHeadsUpManager(headsUpManager);
2814     }
2815 
generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp)2816     public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
2817         if (mAnimationsEnabled) {
2818             mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
2819             mNeedsAnimation = true;
2820             requestChildrenUpdate();
2821         }
2822     }
2823 
setShadeExpanded(boolean shadeExpanded)2824     public void setShadeExpanded(boolean shadeExpanded) {
2825         mAmbientState.setShadeExpanded(shadeExpanded);
2826         mStateAnimator.setShadeExpanded(shadeExpanded);
2827     }
2828 
2829     /**
2830      * Set the boundary for the bottom heads up position. The heads up will always be above this
2831      * position.
2832      *
2833      * @param height the height of the screen
2834      * @param bottomBarHeight the height of the bar on the bottom
2835      */
setHeadsUpBoundaries(int height, int bottomBarHeight)2836     public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
2837         mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
2838         mStateAnimator.setHeadsUpAppearHeightBottom(height);
2839         requestChildrenUpdate();
2840     }
2841 
setTrackingHeadsUp(boolean trackingHeadsUp)2842     public void setTrackingHeadsUp(boolean trackingHeadsUp) {
2843         mTrackingHeadsUp = trackingHeadsUp;
2844     }
2845 
setScrimController(ScrimController scrimController)2846     public void setScrimController(ScrimController scrimController) {
2847         mScrimController = scrimController;
2848     }
2849 
forceNoOverlappingRendering(boolean force)2850     public void forceNoOverlappingRendering(boolean force) {
2851         mForceNoOverlappingRendering = force;
2852     }
2853 
2854     @Override
hasOverlappingRendering()2855     public boolean hasOverlappingRendering() {
2856         return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
2857     }
2858 
2859     /**
2860      * A listener that is notified when some child locations might have changed.
2861      */
2862     public interface OnChildLocationsChangedListener {
onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout)2863         public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
2864     }
2865 
2866     /**
2867      * A listener that is notified when the empty space below the notifications is clicked on
2868      */
2869     public interface OnEmptySpaceClickListener {
onEmptySpaceClicked(float x, float y)2870         public void onEmptySpaceClicked(float x, float y);
2871     }
2872 
2873     /**
2874      * A listener that gets notified when the overscroll at the top has changed.
2875      */
2876     public interface OnOverscrollTopChangedListener {
2877 
2878         /**
2879          * Notifies a listener that the overscroll has changed.
2880          *
2881          * @param amount the amount of overscroll, in pixels
2882          * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
2883          *                     unrubberbanded motion to directly expand overscroll view (e.g expand
2884          *                     QS)
2885          */
onOverscrollTopChanged(float amount, boolean isRubberbanded)2886         public void onOverscrollTopChanged(float amount, boolean isRubberbanded);
2887 
2888         /**
2889          * Notify a listener that the scroller wants to escape from the scrolling motion and
2890          * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
2891          *
2892          * @param velocity The velocity that the Scroller had when over flinging
2893          * @param open Should the fling open or close the overscroll view.
2894          */
flingTopOverscroll(float velocity, boolean open)2895         public void flingTopOverscroll(float velocity, boolean open);
2896     }
2897 
2898     static class AnimationEvent {
2899 
2900         static AnimationFilter[] FILTERS = new AnimationFilter[] {
2901 
2902                 // ANIMATION_TYPE_ADD
2903                 new AnimationFilter()
2904                         .animateAlpha()
2905                         .animateHeight()
2906                         .animateTopInset()
2907                         .animateY()
2908                         .animateZ()
2909                         .hasDelays(),
2910 
2911                 // ANIMATION_TYPE_REMOVE
2912                 new AnimationFilter()
2913                         .animateAlpha()
2914                         .animateHeight()
2915                         .animateTopInset()
2916                         .animateY()
2917                         .animateZ()
2918                         .hasDelays(),
2919 
2920                 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
2921                 new AnimationFilter()
2922                         .animateAlpha()
2923                         .animateHeight()
2924                         .animateTopInset()
2925                         .animateY()
2926                         .animateZ()
2927                         .hasDelays(),
2928 
2929                 // ANIMATION_TYPE_TOP_PADDING_CHANGED
2930                 new AnimationFilter()
2931                         .animateAlpha()
2932                         .animateHeight()
2933                         .animateTopInset()
2934                         .animateY()
2935                         .animateDimmed()
2936                         .animateScale()
2937                         .animateZ(),
2938 
2939                 // ANIMATION_TYPE_START_DRAG
2940                 new AnimationFilter()
2941                         .animateAlpha(),
2942 
2943                 // ANIMATION_TYPE_SNAP_BACK
2944                 new AnimationFilter()
2945                         .animateAlpha()
2946                         .animateHeight(),
2947 
2948                 // ANIMATION_TYPE_ACTIVATED_CHILD
2949                 new AnimationFilter()
2950                         .animateScale()
2951                         .animateAlpha(),
2952 
2953                 // ANIMATION_TYPE_DIMMED
2954                 new AnimationFilter()
2955                         .animateY()
2956                         .animateScale()
2957                         .animateDimmed(),
2958 
2959                 // ANIMATION_TYPE_CHANGE_POSITION
2960                 new AnimationFilter()
2961                         .animateAlpha()
2962                         .animateHeight()
2963                         .animateTopInset()
2964                         .animateY()
2965                         .animateZ(),
2966 
2967                 // ANIMATION_TYPE_DARK
2968                 new AnimationFilter()
2969                         .animateDark()
2970                         .hasDelays(),
2971 
2972                 // ANIMATION_TYPE_GO_TO_FULL_SHADE
2973                 new AnimationFilter()
2974                         .animateAlpha()
2975                         .animateHeight()
2976                         .animateTopInset()
2977                         .animateY()
2978                         .animateDimmed()
2979                         .animateScale()
2980                         .animateZ()
2981                         .hasDelays(),
2982 
2983                 // ANIMATION_TYPE_HIDE_SENSITIVE
2984                 new AnimationFilter()
2985                         .animateHideSensitive(),
2986 
2987                 // ANIMATION_TYPE_VIEW_RESIZE
2988                 new AnimationFilter()
2989                         .animateAlpha()
2990                         .animateHeight()
2991                         .animateTopInset()
2992                         .animateY()
2993                         .animateZ(),
2994 
2995                 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
2996                 new AnimationFilter()
2997                         .animateAlpha()
2998                         .animateHeight()
2999                         .animateTopInset()
3000                         .animateY()
3001                         .animateZ(),
3002 
3003                 // ANIMATION_TYPE_HEADS_UP_APPEAR
3004                 new AnimationFilter()
3005                         .animateAlpha()
3006                         .animateHeight()
3007                         .animateTopInset()
3008                         .animateY()
3009                         .animateZ(),
3010 
3011                 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
3012                 new AnimationFilter()
3013                         .animateAlpha()
3014                         .animateHeight()
3015                         .animateTopInset()
3016                         .animateY()
3017                         .animateZ(),
3018 
3019                 // ANIMATION_TYPE_HEADS_UP_OTHER
3020                 new AnimationFilter()
3021                         .animateAlpha()
3022                         .animateHeight()
3023                         .animateTopInset()
3024                         .animateY()
3025                         .animateZ(),
3026 
3027                 // ANIMATION_TYPE_EVERYTHING
3028                 new AnimationFilter()
3029                         .animateAlpha()
3030                         .animateDark()
3031                         .animateScale()
3032                         .animateDimmed()
3033                         .animateHideSensitive()
3034                         .animateHeight()
3035                         .animateTopInset()
3036                         .animateY()
3037                         .animateZ(),
3038         };
3039 
3040         static int[] LENGTHS = new int[] {
3041 
3042                 // ANIMATION_TYPE_ADD
3043                 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
3044 
3045                 // ANIMATION_TYPE_REMOVE
3046                 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
3047 
3048                 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
3049                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3050 
3051                 // ANIMATION_TYPE_TOP_PADDING_CHANGED
3052                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3053 
3054                 // ANIMATION_TYPE_START_DRAG
3055                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3056 
3057                 // ANIMATION_TYPE_SNAP_BACK
3058                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3059 
3060                 // ANIMATION_TYPE_ACTIVATED_CHILD
3061                 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
3062 
3063                 // ANIMATION_TYPE_DIMMED
3064                 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
3065 
3066                 // ANIMATION_TYPE_CHANGE_POSITION
3067                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3068 
3069                 // ANIMATION_TYPE_DARK
3070                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3071 
3072                 // ANIMATION_TYPE_GO_TO_FULL_SHADE
3073                 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
3074 
3075                 // ANIMATION_TYPE_HIDE_SENSITIVE
3076                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3077 
3078                 // ANIMATION_TYPE_VIEW_RESIZE
3079                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3080 
3081                 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
3082                 StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED,
3083 
3084                 // ANIMATION_TYPE_HEADS_UP_APPEAR
3085                 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
3086 
3087                 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
3088                 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
3089 
3090                 // ANIMATION_TYPE_HEADS_UP_OTHER
3091                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3092 
3093                 // ANIMATION_TYPE_EVERYTHING
3094                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3095         };
3096 
3097         static final int ANIMATION_TYPE_ADD = 0;
3098         static final int ANIMATION_TYPE_REMOVE = 1;
3099         static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
3100         static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
3101         static final int ANIMATION_TYPE_START_DRAG = 4;
3102         static final int ANIMATION_TYPE_SNAP_BACK = 5;
3103         static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
3104         static final int ANIMATION_TYPE_DIMMED = 7;
3105         static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
3106         static final int ANIMATION_TYPE_DARK = 9;
3107         static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
3108         static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
3109         static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
3110         static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13;
3111         static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14;
3112         static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15;
3113         static final int ANIMATION_TYPE_HEADS_UP_OTHER = 16;
3114         static final int ANIMATION_TYPE_EVERYTHING = 17;
3115 
3116         static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
3117         static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
3118 
3119         final long eventStartTime;
3120         final View changingView;
3121         final int animationType;
3122         final AnimationFilter filter;
3123         final long length;
3124         View viewAfterChangingView;
3125         int darkAnimationOriginIndex;
3126         boolean headsUpFromBottom;
3127 
AnimationEvent(View view, int type)3128         AnimationEvent(View view, int type) {
3129             this(view, type, LENGTHS[type]);
3130         }
3131 
AnimationEvent(View view, int type, long length)3132         AnimationEvent(View view, int type, long length) {
3133             eventStartTime = AnimationUtils.currentAnimationTimeMillis();
3134             changingView = view;
3135             animationType = type;
3136             filter = FILTERS[type];
3137             this.length = length;
3138         }
3139 
3140         /**
3141          * Combines the length of several animation events into a single value.
3142          *
3143          * @param events The events of the lengths to combine.
3144          * @return The combined length. Depending on the event types, this might be the maximum of
3145          *         all events or the length of a specific event.
3146          */
combineLength(ArrayList<AnimationEvent> events)3147         static long combineLength(ArrayList<AnimationEvent> events) {
3148             long length = 0;
3149             int size = events.size();
3150             for (int i = 0; i < size; i++) {
3151                 AnimationEvent event = events.get(i);
3152                 length = Math.max(length, event.length);
3153                 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
3154                     return event.length;
3155                 }
3156             }
3157             return length;
3158         }
3159     }
3160 
3161 }
3162