NotificationStackScrollLayout.java revision 30c305ce6283ce1380ad91ef0d221696b32d5a6b
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
17package com.android.systemui.statusbar.stack;
18
19import android.content.Context;
20import android.content.res.Configuration;
21import android.graphics.Canvas;
22import android.graphics.Paint;
23import android.util.AttributeSet;
24import android.util.Log;
25import android.view.MotionEvent;
26import android.view.VelocityTracker;
27import android.view.View;
28import android.view.ViewConfiguration;
29import android.view.ViewGroup;
30import android.view.ViewTreeObserver;
31import android.view.animation.AnimationUtils;
32import android.widget.OverScroller;
33import com.android.systemui.ExpandHelper;
34import com.android.systemui.R;
35import com.android.systemui.SwipeHelper;
36import com.android.systemui.statusbar.ActivatableNotificationView;
37import com.android.systemui.statusbar.ExpandableNotificationRow;
38import com.android.systemui.statusbar.ExpandableView;
39import com.android.systemui.statusbar.SpeedBumpView;
40import com.android.systemui.statusbar.policy.ScrollAdapter;
41import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
42
43import java.util.ArrayList;
44
45/**
46 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
47 */
48public class NotificationStackScrollLayout extends ViewGroup
49        implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
50        ExpandableView.OnHeightChangedListener {
51
52    private static final String TAG = "NotificationStackScrollLayout";
53    private static final boolean DEBUG = false;
54    private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
55    private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
56    private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
57
58    /**
59     * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
60     */
61    private static final int INVALID_POINTER = -1;
62
63    private ExpandHelper mExpandHelper;
64    private SwipeHelper mSwipeHelper;
65    private boolean mSwipingInProgress;
66    private int mCurrentStackHeight = Integer.MAX_VALUE;
67    private int mOwnScrollY;
68    private int mMaxLayoutHeight;
69
70    private VelocityTracker mVelocityTracker;
71    private OverScroller mScroller;
72    private int mTouchSlop;
73    private int mMinimumVelocity;
74    private int mMaximumVelocity;
75    private int mOverflingDistance;
76    private float mMaxOverScroll;
77    private boolean mIsBeingDragged;
78    private int mLastMotionY;
79    private int mDownX;
80    private int mActivePointerId;
81
82    private int mSidePaddings;
83    private Paint mDebugPaint;
84    private int mContentHeight;
85    private int mCollapsedSize;
86    private int mBottomStackSlowDownHeight;
87    private int mBottomStackPeekSize;
88    private int mPaddingBetweenElements;
89    private int mPaddingBetweenElementsDimmed;
90    private int mPaddingBetweenElementsNormal;
91    private int mTopPadding;
92
93    /**
94     * The algorithm which calculates the properties for our children
95     */
96    private StackScrollAlgorithm mStackScrollAlgorithm;
97
98    /**
99     * The current State this Layout is in
100     */
101    private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
102    private AmbientState mAmbientState = new AmbientState();
103    private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>();
104    private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>();
105    private ArrayList<View> mSnappedBackChildren = new ArrayList<View>();
106    private ArrayList<View> mDragAnimPendingChildren = new ArrayList<View>();
107    private ArrayList<View> mChildrenChangingPositions = new ArrayList<View>();
108    private ArrayList<AnimationEvent> mAnimationEvents
109            = new ArrayList<AnimationEvent>();
110    private ArrayList<View> mSwipedOutViews = new ArrayList<View>();
111    private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
112    private boolean mAnimationsEnabled;
113    private boolean mChangePositionInProgress;
114
115    /**
116     * The raw amount of the overScroll on the top, which is not rubber-banded.
117     */
118    private float mOverScrolledTopPixels;
119
120    /**
121     * The raw amount of the overScroll on the bottom, which is not rubber-banded.
122     */
123    private float mOverScrolledBottomPixels;
124
125    private OnChildLocationsChangedListener mListener;
126    private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
127    private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
128    private boolean mNeedsAnimation;
129    private boolean mTopPaddingNeedsAnimation;
130    private boolean mDimmedNeedsAnimation;
131    private boolean mDarkNeedsAnimation;
132    private boolean mActivateNeedsAnimation;
133    private boolean mIsExpanded = true;
134    private boolean mChildrenUpdateRequested;
135    private SpeedBumpView mSpeedBumpView;
136    private boolean mIsExpansionChanging;
137    private boolean mExpandingNotification;
138    private boolean mExpandedInThisMotion;
139    private boolean mScrollingEnabled;
140
141    /**
142     * Was the scroller scrolled to the top when the down motion was observed?
143     */
144    private boolean mScrolledToTopOnFirstDown;
145
146    /**
147     * The minimal amount of over scroll which is needed in order to switch to the quick settings
148     * when over scrolling on a expanded card.
149     */
150    private float mMinTopOverScrollToEscape;
151    private int mIntrinsicPadding;
152    private int mNotificationTopPadding;
153    private float mTopPaddingOverflow;
154    private boolean mDontReportNextOverScroll;
155
156    /**
157     * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
158     * This is needed to avoid scrolling too far after the notification was collapsed in the same
159     * motion.
160     */
161    private int mMaxScrollAfterExpand;
162    private OnLongClickListener mLongClickListener;
163
164    /**
165     * Should in this touch motion only be scrolling allowed? It's true when the scroller was
166     * animating.
167     */
168    private boolean mOnlyScrollingInThisMotion;
169    private boolean mTouchEnabled = true;
170    private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
171            = new ViewTreeObserver.OnPreDrawListener() {
172        @Override
173        public boolean onPreDraw() {
174            updateChildren();
175            mChildrenUpdateRequested = false;
176            getViewTreeObserver().removeOnPreDrawListener(this);
177            return true;
178        }
179    };
180
181    public NotificationStackScrollLayout(Context context) {
182        this(context, null);
183    }
184
185    public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
186        this(context, attrs, 0);
187    }
188
189    public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
190        this(context, attrs, defStyleAttr, 0);
191    }
192
193    public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
194            int defStyleRes) {
195        super(context, attrs, defStyleAttr, defStyleRes);
196        initView(context);
197        if (DEBUG) {
198            setWillNotDraw(false);
199            mDebugPaint = new Paint();
200            mDebugPaint.setColor(0xffff0000);
201            mDebugPaint.setStrokeWidth(2);
202            mDebugPaint.setStyle(Paint.Style.STROKE);
203        }
204    }
205
206    @Override
207    protected void onDraw(Canvas canvas) {
208        if (DEBUG) {
209            int y = mCollapsedSize;
210            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
211            y = (int) (getLayoutHeight() - mBottomStackPeekSize
212                    - mBottomStackSlowDownHeight);
213            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
214            y = (int) (getLayoutHeight() - mBottomStackPeekSize);
215            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
216            y = (int) getLayoutHeight();
217            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
218            y = getHeight() - getEmptyBottomMargin();
219            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
220        }
221    }
222
223    private void initView(Context context) {
224        mScroller = new OverScroller(getContext());
225        setFocusable(true);
226        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
227        setClipChildren(false);
228        final ViewConfiguration configuration = ViewConfiguration.get(context);
229        mTouchSlop = configuration.getScaledTouchSlop();
230        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
231        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
232        mOverflingDistance = configuration.getScaledOverflingDistance();
233        float densityScale = getResources().getDisplayMetrics().density;
234        float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
235        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop);
236        mSwipeHelper.setLongPressListener(mLongClickListener);
237
238        mSidePaddings = context.getResources()
239                .getDimensionPixelSize(R.dimen.notification_side_padding);
240        mCollapsedSize = context.getResources()
241                .getDimensionPixelSize(R.dimen.notification_min_height);
242        mBottomStackPeekSize = context.getResources()
243                .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
244        mStackScrollAlgorithm = new StackScrollAlgorithm(context);
245        mStackScrollAlgorithm.setDimmed(mAmbientState.isDimmed());
246        mPaddingBetweenElementsDimmed = context.getResources()
247                .getDimensionPixelSize(R.dimen.notification_padding_dimmed);
248        mPaddingBetweenElementsNormal = context.getResources()
249                .getDimensionPixelSize(R.dimen.notification_padding);
250        updatePadding(mAmbientState.isDimmed());
251        int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
252        int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
253        mExpandHelper = new ExpandHelper(getContext(), this,
254                minHeight, maxHeight);
255        mExpandHelper.setEventSource(this);
256        mExpandHelper.setScrollAdapter(this);
257        mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
258                R.dimen.min_top_overscroll_to_qs);
259        mNotificationTopPadding = getResources().getDimensionPixelSize(
260                R.dimen.notifications_top_padding);
261    }
262
263    private void updatePadding(boolean dimmed) {
264        mPaddingBetweenElements = dimmed
265                ? mPaddingBetweenElementsDimmed
266                : mPaddingBetweenElementsNormal;
267        mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength();
268        updateContentHeight();
269        notifyHeightChangeListener(null);
270    }
271
272    private void notifyHeightChangeListener(ExpandableView view) {
273        if (mOnHeightChangedListener != null) {
274            mOnHeightChangedListener.onHeightChanged(view);
275        }
276    }
277
278    @Override
279    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
280        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
281        int mode = MeasureSpec.getMode(widthMeasureSpec);
282        int size = MeasureSpec.getSize(widthMeasureSpec);
283        int childMeasureSpec = MeasureSpec.makeMeasureSpec(size - 2 * mSidePaddings, mode);
284        measureChildren(childMeasureSpec, heightMeasureSpec);
285    }
286
287    @Override
288    protected void onLayout(boolean changed, int l, int t, int r, int b) {
289
290        // we layout all our children centered on the top
291        float centerX = getWidth() / 2.0f;
292        for (int i = 0; i < getChildCount(); i++) {
293            View child = getChildAt(i);
294            float width = child.getMeasuredWidth();
295            float height = child.getMeasuredHeight();
296            child.layout((int) (centerX - width / 2.0f),
297                    0,
298                    (int) (centerX + width / 2.0f),
299                    (int) height);
300        }
301        setMaxLayoutHeight(getHeight());
302        updateContentHeight();
303        updateScrollPositionIfNecessary();
304        requestChildrenUpdate();
305    }
306
307    public void updateSpeedBumpIndex(int newIndex) {
308        int currentIndex = indexOfChild(mSpeedBumpView);
309
310        // If we are currently layouted before the new speed bump index, we have to decrease it.
311        boolean validIndex = newIndex > 0;
312        if (newIndex > getChildCount() - 1) {
313            validIndex = false;
314            newIndex = -1;
315        }
316        if (validIndex && currentIndex != newIndex) {
317            changeViewPosition(mSpeedBumpView, newIndex);
318        }
319        updateSpeedBump(validIndex);
320        mAmbientState.setSpeedBumpIndex(newIndex);
321    }
322
323    public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
324        mListener = listener;
325    }
326
327    /**
328     * Returns the location the given child is currently rendered at.
329     *
330     * @param child the child to get the location for
331     * @return one of {@link ViewState}'s <code>LOCATION_*</code> constants
332     */
333    public int getChildLocation(View child) {
334        ViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
335        if (childViewState == null) {
336            return ViewState.LOCATION_UNKNOWN;
337        }
338        return childViewState.location;
339    }
340
341    private void setMaxLayoutHeight(int maxLayoutHeight) {
342        mMaxLayoutHeight = maxLayoutHeight;
343        updateAlgorithmHeightAndPadding();
344    }
345
346    private void updateAlgorithmHeightAndPadding() {
347        mStackScrollAlgorithm.setLayoutHeight(getLayoutHeight());
348        mStackScrollAlgorithm.setTopPadding(mTopPadding);
349    }
350
351    /**
352     * @return whether the height of the layout needs to be adapted, in order to ensure that the
353     *         last child is not in the bottom stack.
354     */
355    private boolean needsHeightAdaption() {
356        return getNotGoneChildCount() > 1;
357    }
358
359    /**
360     * Updates the children views according to the stack scroll algorithm. Call this whenever
361     * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
362     */
363    private void updateChildren() {
364        mAmbientState.setScrollY(mOwnScrollY);
365        mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
366        if (!isCurrentlyAnimating() && !mNeedsAnimation) {
367            applyCurrentState();
368        } else {
369            startAnimationToState();
370        }
371    }
372
373    private void requestChildrenUpdate() {
374        if (!mChildrenUpdateRequested) {
375            getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
376            mChildrenUpdateRequested = true;
377            invalidate();
378        }
379    }
380
381    private boolean isCurrentlyAnimating() {
382        return mStateAnimator.isRunning();
383    }
384
385    private void updateScrollPositionIfNecessary() {
386        int scrollRange = getScrollRange();
387        if (scrollRange < mOwnScrollY) {
388            mOwnScrollY = scrollRange;
389        }
390    }
391
392    public int getTopPadding() {
393        return mTopPadding;
394    }
395
396    private void setTopPadding(int topPadding, boolean animate) {
397        if (mTopPadding != topPadding) {
398            mTopPadding = topPadding;
399            updateAlgorithmHeightAndPadding();
400            updateContentHeight();
401            if (animate && mAnimationsEnabled && mIsExpanded) {
402                mTopPaddingNeedsAnimation = true;
403                mNeedsAnimation =  true;
404            }
405            requestChildrenUpdate();
406            notifyHeightChangeListener(null);
407        }
408    }
409
410    /**
411     * Update the height of the stack to a new height.
412     *
413     * @param height the new height of the stack
414     */
415    public void setStackHeight(float height) {
416        setIsExpanded(height > 0.0f);
417        int newStackHeight = (int) height;
418        int itemHeight = getItemHeight();
419        int bottomStackPeekSize = mBottomStackPeekSize;
420        int minStackHeight = itemHeight + bottomStackPeekSize;
421        int stackHeight;
422        if (newStackHeight - mTopPadding >= minStackHeight || getNotGoneChildCount() == 0) {
423            setTranslationY(mTopPaddingOverflow);
424            stackHeight = newStackHeight;
425        } else {
426
427            // We did not reach the position yet where we actually start growing,
428            // so we translate the stack upwards.
429            int translationY = (newStackHeight - minStackHeight);
430            // A slight parallax effect is introduced in order for the stack to catch up with
431            // the top card.
432            float partiallyThere = (float) (newStackHeight - mTopPadding) / minStackHeight;
433            partiallyThere = Math.max(0, partiallyThere);
434            translationY += (1 - partiallyThere) * bottomStackPeekSize;
435            setTranslationY(translationY - mTopPadding);
436            stackHeight = (int) (height - (translationY - mTopPadding));
437        }
438        if (stackHeight != mCurrentStackHeight) {
439            mCurrentStackHeight = stackHeight;
440            updateAlgorithmHeightAndPadding();
441            requestChildrenUpdate();
442        }
443    }
444
445    /**
446     * Get the current height of the view. This is at most the msize of the view given by a the
447     * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
448     *
449     * @return either the layout height or the externally defined height, whichever is smaller
450     */
451    private int getLayoutHeight() {
452        return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
453    }
454
455    public int getItemHeight() {
456        return mCollapsedSize;
457    }
458
459    public int getBottomStackPeekSize() {
460        return mBottomStackPeekSize;
461    }
462
463    public void setLongPressListener(View.OnLongClickListener listener) {
464        mSwipeHelper.setLongPressListener(listener);
465        mLongClickListener = listener;
466    }
467
468    public void onChildDismissed(View v) {
469        if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
470        final View veto = v.findViewById(R.id.veto);
471        if (veto != null && veto.getVisibility() != View.GONE) {
472            veto.performClick();
473        }
474        setSwipingInProgress(false);
475        if (mDragAnimPendingChildren.contains(v)) {
476            // We start the swipe and finish it in the same frame, we don't want any animation
477            // for the drag
478            mDragAnimPendingChildren.remove(v);
479        }
480        mSwipedOutViews.add(v);
481        mAmbientState.onDragFinished(v);
482    }
483
484    @Override
485    public void onChildSnappedBack(View animView) {
486        mAmbientState.onDragFinished(animView);
487        if (!mDragAnimPendingChildren.contains(animView)) {
488            if (mAnimationsEnabled) {
489                mSnappedBackChildren.add(animView);
490                mNeedsAnimation = true;
491            }
492            requestChildrenUpdate();
493        } else {
494            // We start the swipe and snap back in the same frame, we don't want any animation
495            mDragAnimPendingChildren.remove(animView);
496        }
497    }
498
499    @Override
500    public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
501        return false;
502    }
503
504    public void onBeginDrag(View v) {
505        setSwipingInProgress(true);
506        mAmbientState.onBeginDrag(v);
507        if (mAnimationsEnabled) {
508            mDragAnimPendingChildren.add(v);
509            mNeedsAnimation = true;
510        }
511        requestChildrenUpdate();
512    }
513
514    public void onDragCancelled(View v) {
515        setSwipingInProgress(false);
516    }
517
518    public View getChildAtPosition(MotionEvent ev) {
519        return getChildAtPosition(ev.getX(), ev.getY());
520    }
521
522    public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
523        int[] location = new int[2];
524        getLocationOnScreen(location);
525        return getChildAtPosition(touchX - location[0], touchY - location[1]);
526    }
527
528    public ExpandableView getChildAtPosition(float touchX, float touchY) {
529        // find the view under the pointer, accounting for GONE views
530        final int count = getChildCount();
531        for (int childIdx = 0; childIdx < count; childIdx++) {
532            ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
533            if (slidingChild.getVisibility() == GONE) {
534                continue;
535            }
536            float childTop = slidingChild.getTranslationY();
537            float top = childTop + slidingChild.getClipTopAmount();
538            float bottom = childTop + slidingChild.getActualHeight();
539            int left = slidingChild.getLeft();
540            int right = slidingChild.getRight();
541
542            if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
543                return slidingChild;
544            }
545        }
546        return null;
547    }
548
549    public boolean canChildBeExpanded(View v) {
550        return v instanceof ExpandableNotificationRow
551                && ((ExpandableNotificationRow) v).isExpandable();
552    }
553
554    public void setUserExpandedChild(View v, boolean userExpanded) {
555        if (v instanceof ExpandableNotificationRow) {
556            ((ExpandableNotificationRow) v).setUserExpanded(userExpanded);
557        }
558    }
559
560    public void setUserLockedChild(View v, boolean userLocked) {
561        if (v instanceof ExpandableNotificationRow) {
562            ((ExpandableNotificationRow) v).setUserLocked(userLocked);
563        }
564        removeLongPressCallback();
565        requestDisallowInterceptTouchEvent(true);
566    }
567
568    @Override
569    public void expansionStateChanged(boolean isExpanding) {
570        mExpandingNotification = isExpanding;
571        if (!mExpandedInThisMotion) {
572            mMaxScrollAfterExpand = mOwnScrollY;
573            mExpandedInThisMotion = true;
574        }
575    }
576
577    public void setScrollingEnabled(boolean enable) {
578        mScrollingEnabled = enable;
579    }
580
581    public void setExpandingEnabled(boolean enable) {
582        mExpandHelper.setEnabled(enable);
583    }
584
585    private boolean isScrollingEnabled() {
586        return mScrollingEnabled;
587    }
588
589    public View getChildContentView(View v) {
590        return v;
591    }
592
593    public boolean canChildBeDismissed(View v) {
594        final View veto = v.findViewById(R.id.veto);
595        return (veto != null && veto.getVisibility() != View.GONE);
596    }
597
598    private void setSwipingInProgress(boolean isSwiped) {
599        mSwipingInProgress = isSwiped;
600        if(isSwiped) {
601            requestDisallowInterceptTouchEvent(true);
602        }
603    }
604
605    @Override
606    protected void onConfigurationChanged(Configuration newConfig) {
607        super.onConfigurationChanged(newConfig);
608        float densityScale = getResources().getDisplayMetrics().density;
609        mSwipeHelper.setDensityScale(densityScale);
610        float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
611        mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
612        initView(getContext());
613    }
614
615    public void dismissRowAnimated(View child, int vel) {
616        mSwipeHelper.dismissChild(child, vel);
617    }
618
619    @Override
620    public boolean onTouchEvent(MotionEvent ev) {
621        if (!isEnabled()) {
622            return false;
623        }
624        boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
625                || ev.getActionMasked()== MotionEvent.ACTION_UP;
626        boolean expandWantsIt = false;
627        if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) {
628            if (isCancelOrUp) {
629                mExpandHelper.onlyObserveMovements(false);
630            }
631            boolean wasExpandingBefore = mExpandingNotification;
632            expandWantsIt = mExpandHelper.onTouchEvent(ev);
633            if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore) {
634                dispatchDownEventToScroller(ev);
635            }
636        }
637        boolean scrollerWantsIt = false;
638        if (!mSwipingInProgress && !mExpandingNotification) {
639            scrollerWantsIt = onScrollTouch(ev);
640        }
641        boolean horizontalSwipeWantsIt = false;
642        if (!mIsBeingDragged
643                && !mExpandingNotification
644                && !mExpandedInThisMotion
645                && !mOnlyScrollingInThisMotion) {
646            horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
647        }
648        return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
649    }
650
651    private void dispatchDownEventToScroller(MotionEvent ev) {
652        MotionEvent downEvent = MotionEvent.obtain(ev);
653        downEvent.setAction(MotionEvent.ACTION_DOWN);
654        onScrollTouch(downEvent);
655        downEvent.recycle();
656    }
657
658    private boolean onScrollTouch(MotionEvent ev) {
659        if (!isScrollingEnabled()) {
660            return false;
661        }
662        initVelocityTrackerIfNotExists();
663        mVelocityTracker.addMovement(ev);
664
665        final int action = ev.getAction();
666
667        switch (action & MotionEvent.ACTION_MASK) {
668            case MotionEvent.ACTION_DOWN: {
669                if (getChildCount() == 0 || !isInContentBounds(ev)) {
670                    return false;
671                }
672                boolean isBeingDragged = !mScroller.isFinished();
673                setIsBeingDragged(isBeingDragged);
674
675                /*
676                 * If being flinged and user touches, stop the fling. isFinished
677                 * will be false if being flinged.
678                 */
679                if (!mScroller.isFinished()) {
680                    mScroller.forceFinished(true);
681                }
682
683                // Remember where the motion event started
684                mLastMotionY = (int) ev.getY();
685                mDownX = (int) ev.getX();
686                mActivePointerId = ev.getPointerId(0);
687                break;
688            }
689            case MotionEvent.ACTION_MOVE:
690                final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
691                if (activePointerIndex == -1) {
692                    Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
693                    break;
694                }
695
696                final int y = (int) ev.getY(activePointerIndex);
697                final int x = (int) ev.getX(activePointerIndex);
698                int deltaY = mLastMotionY - y;
699                final int xDiff = Math.abs(x - mDownX);
700                final int yDiff = Math.abs(deltaY);
701                if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
702                    setIsBeingDragged(true);
703                    if (deltaY > 0) {
704                        deltaY -= mTouchSlop;
705                    } else {
706                        deltaY += mTouchSlop;
707                    }
708                }
709                if (mIsBeingDragged) {
710                    // Scroll to follow the motion event
711                    mLastMotionY = y;
712                    int range = getScrollRange();
713                    if (mExpandedInThisMotion) {
714                        range = Math.min(range, mMaxScrollAfterExpand);
715                    }
716
717                    float scrollAmount;
718                    if (deltaY < 0) {
719                        scrollAmount = overScrollDown(deltaY);
720                    } else {
721                        scrollAmount = overScrollUp(deltaY, range);
722                    }
723
724                    // Calling overScrollBy will call onOverScrolled, which
725                    // calls onScrollChanged if applicable.
726                    if (scrollAmount != 0.0f) {
727                        // The scrolling motion could not be compensated with the
728                        // existing overScroll, we have to scroll the view
729                        overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY,
730                                0, range, 0, getHeight() / 2, true);
731                    }
732                }
733                break;
734            case MotionEvent.ACTION_UP:
735                if (mIsBeingDragged) {
736                    final VelocityTracker velocityTracker = mVelocityTracker;
737                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
738                    int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
739
740                    if (shouldOverScrollFling(initialVelocity)) {
741                        onOverScrollFling(true, initialVelocity);
742                    } else {
743                        if (getChildCount() > 0) {
744                            if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
745                                float currentOverScrollTop = getCurrentOverScrollAmount(true);
746                                if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
747                                    fling(-initialVelocity);
748                                } else {
749                                    onOverScrollFling(false, initialVelocity);
750                                }
751                            } else {
752                                if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
753                                        getScrollRange())) {
754                                    postInvalidateOnAnimation();
755                                }
756                            }
757                        }
758                    }
759
760                    mActivePointerId = INVALID_POINTER;
761                    endDrag();
762                }
763
764                break;
765            case MotionEvent.ACTION_CANCEL:
766                if (mIsBeingDragged && getChildCount() > 0) {
767                    if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
768                        postInvalidateOnAnimation();
769                    }
770                    mActivePointerId = INVALID_POINTER;
771                    endDrag();
772                }
773                break;
774            case MotionEvent.ACTION_POINTER_DOWN: {
775                final int index = ev.getActionIndex();
776                mLastMotionY = (int) ev.getY(index);
777                mDownX = (int) ev.getX(index);
778                mActivePointerId = ev.getPointerId(index);
779                break;
780            }
781            case MotionEvent.ACTION_POINTER_UP:
782                onSecondaryPointerUp(ev);
783                mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
784                mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
785                break;
786        }
787        return true;
788    }
789
790    private void onOverScrollFling(boolean open, int initialVelocity) {
791        if (mOverscrollTopChangedListener != null) {
792            mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
793        }
794        mDontReportNextOverScroll = true;
795        setOverScrollAmount(0.0f, true, false);
796    }
797
798    /**
799     * Perform a scroll upwards and adapt the overscroll amounts accordingly
800     *
801     * @param deltaY The amount to scroll upwards, has to be positive.
802     * @return The amount of scrolling to be performed by the scroller,
803     *         not handled by the overScroll amount.
804     */
805    private float overScrollUp(int deltaY, int range) {
806        deltaY = Math.max(deltaY, 0);
807        float currentTopAmount = getCurrentOverScrollAmount(true);
808        float newTopAmount = currentTopAmount - deltaY;
809        if (currentTopAmount > 0) {
810            setOverScrollAmount(newTopAmount, true /* onTop */,
811                    false /* animate */);
812        }
813        // Top overScroll might not grab all scrolling motion,
814        // we have to scroll as well.
815        float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
816        float newScrollY = mOwnScrollY + scrollAmount;
817        if (newScrollY > range) {
818            if (!mExpandedInThisMotion) {
819                float currentBottomPixels = getCurrentOverScrolledPixels(false);
820                // We overScroll on the top
821                setOverScrolledPixels(currentBottomPixels + newScrollY - range,
822                        false /* onTop */,
823                        false /* animate */);
824            }
825            mOwnScrollY = range;
826            scrollAmount = 0.0f;
827        }
828        return scrollAmount;
829    }
830
831    /**
832     * Perform a scroll downward and adapt the overscroll amounts accordingly
833     *
834     * @param deltaY The amount to scroll downwards, has to be negative.
835     * @return The amount of scrolling to be performed by the scroller,
836     *         not handled by the overScroll amount.
837     */
838    private float overScrollDown(int deltaY) {
839        deltaY = Math.min(deltaY, 0);
840        float currentBottomAmount = getCurrentOverScrollAmount(false);
841        float newBottomAmount = currentBottomAmount + deltaY;
842        if (currentBottomAmount > 0) {
843            setOverScrollAmount(newBottomAmount, false /* onTop */,
844                    false /* animate */);
845        }
846        // Bottom overScroll might not grab all scrolling motion,
847        // we have to scroll as well.
848        float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
849        float newScrollY = mOwnScrollY + scrollAmount;
850        if (newScrollY < 0) {
851            float currentTopPixels = getCurrentOverScrolledPixels(true);
852            // We overScroll on the top
853            setOverScrolledPixels(currentTopPixels - newScrollY,
854                    true /* onTop */,
855                    false /* animate */);
856            mOwnScrollY = 0;
857            scrollAmount = 0.0f;
858        }
859        return scrollAmount;
860    }
861
862    private void onSecondaryPointerUp(MotionEvent ev) {
863        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
864                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
865        final int pointerId = ev.getPointerId(pointerIndex);
866        if (pointerId == mActivePointerId) {
867            // This was our active pointer going up. Choose a new
868            // active pointer and adjust accordingly.
869            // TODO: Make this decision more intelligent.
870            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
871            mLastMotionY = (int) ev.getY(newPointerIndex);
872            mActivePointerId = ev.getPointerId(newPointerIndex);
873            if (mVelocityTracker != null) {
874                mVelocityTracker.clear();
875            }
876        }
877    }
878
879    private void initVelocityTrackerIfNotExists() {
880        if (mVelocityTracker == null) {
881            mVelocityTracker = VelocityTracker.obtain();
882        }
883    }
884
885    private void recycleVelocityTracker() {
886        if (mVelocityTracker != null) {
887            mVelocityTracker.recycle();
888            mVelocityTracker = null;
889        }
890    }
891
892    private void initOrResetVelocityTracker() {
893        if (mVelocityTracker == null) {
894            mVelocityTracker = VelocityTracker.obtain();
895        } else {
896            mVelocityTracker.clear();
897        }
898    }
899
900    @Override
901    public void computeScroll() {
902        if (mScroller.computeScrollOffset()) {
903            // This is called at drawing time by ViewGroup.
904            int oldX = mScrollX;
905            int oldY = mOwnScrollY;
906            int x = mScroller.getCurrX();
907            int y = mScroller.getCurrY();
908
909            if (oldX != x || oldY != y) {
910                final int range = getScrollRange();
911                if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
912                    float currVelocity = mScroller.getCurrVelocity();
913                    if (currVelocity >= mMinimumVelocity) {
914                        mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
915                    }
916                }
917
918                overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
919                        0, (int) (mMaxOverScroll), false);
920                onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
921            }
922
923            // Keep on drawing until the animation has finished.
924            postInvalidateOnAnimation();
925        }
926    }
927
928    @Override
929    protected boolean overScrollBy(int deltaX, int deltaY,
930            int scrollX, int scrollY,
931            int scrollRangeX, int scrollRangeY,
932            int maxOverScrollX, int maxOverScrollY,
933            boolean isTouchEvent) {
934
935        int newScrollY = scrollY + deltaY;
936
937        final int top = -maxOverScrollY;
938        final int bottom = maxOverScrollY + scrollRangeY;
939
940        boolean clampedY = false;
941        if (newScrollY > bottom) {
942            newScrollY = bottom;
943            clampedY = true;
944        } else if (newScrollY < top) {
945            newScrollY = top;
946            clampedY = true;
947        }
948
949        onOverScrolled(0, newScrollY, false, clampedY);
950
951        return clampedY;
952    }
953
954    /**
955     * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
956     * overscroll effect based on numPixels. By default this will also cancel animations on the
957     * same overScroll edge.
958     *
959     * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
960     *                  the rubber-banding logic.
961     * @param onTop Should the effect be applied on top of the scroller.
962     * @param animate Should an animation be performed.
963     */
964    public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
965        setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
966    }
967
968    /**
969     * Set the effective overScroll amount which will be directly reflected in the layout.
970     * By default this will also cancel animations on the same overScroll edge.
971     *
972     * @param amount The amount to overScroll by.
973     * @param onTop Should the effect be applied on top of the scroller.
974     * @param animate Should an animation be performed.
975     */
976    public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
977        setOverScrollAmount(amount, onTop, animate, true);
978    }
979
980    /**
981     * Set the effective overScroll amount which will be directly reflected in the layout.
982     *
983     * @param amount The amount to overScroll by.
984     * @param onTop Should the effect be applied on top of the scroller.
985     * @param animate Should an animation be performed.
986     * @param cancelAnimators Should running animations be cancelled.
987     */
988    public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
989            boolean cancelAnimators) {
990        setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
991    }
992
993    /**
994     * Set the effective overScroll amount which will be directly reflected in the layout.
995     *
996     * @param amount The amount to overScroll by.
997     * @param onTop Should the effect be applied on top of the scroller.
998     * @param animate Should an animation be performed.
999     * @param cancelAnimators Should running animations be cancelled.
1000     * @param isRubberbanded The value which will be passed to
1001     *                     {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
1002     */
1003    public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1004            boolean cancelAnimators, boolean isRubberbanded) {
1005        if (cancelAnimators) {
1006            mStateAnimator.cancelOverScrollAnimators(onTop);
1007        }
1008        setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
1009    }
1010
1011    private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
1012            boolean isRubberbanded) {
1013        amount = Math.max(0, amount);
1014        if (animate) {
1015            mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
1016        } else {
1017            setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
1018            mAmbientState.setOverScrollAmount(amount, onTop);
1019            if (onTop) {
1020                notifyOverscrollTopListener(amount, isRubberbanded);
1021            }
1022            requestChildrenUpdate();
1023        }
1024    }
1025
1026    private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
1027        mExpandHelper.onlyObserveMovements(amount > 1.0f);
1028        if (mDontReportNextOverScroll) {
1029            mDontReportNextOverScroll = false;
1030            return;
1031        }
1032        if (mOverscrollTopChangedListener != null) {
1033            mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
1034        }
1035    }
1036
1037    public void setOverscrollTopChangedListener(
1038            OnOverscrollTopChangedListener overscrollTopChangedListener) {
1039        mOverscrollTopChangedListener = overscrollTopChangedListener;
1040    }
1041
1042    public float getCurrentOverScrollAmount(boolean top) {
1043        return mAmbientState.getOverScrollAmount(top);
1044    }
1045
1046    public float getCurrentOverScrolledPixels(boolean top) {
1047        return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
1048    }
1049
1050    private void setOverScrolledPixels(float amount, boolean onTop) {
1051        if (onTop) {
1052            mOverScrolledTopPixels = amount;
1053        } else {
1054            mOverScrolledBottomPixels = amount;
1055        }
1056    }
1057
1058    private void customScrollTo(int y) {
1059        mOwnScrollY = y;
1060        updateChildren();
1061    }
1062
1063    @Override
1064    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
1065        // Treat animating scrolls differently; see #computeScroll() for why.
1066        if (!mScroller.isFinished()) {
1067            final int oldX = mScrollX;
1068            final int oldY = mOwnScrollY;
1069            mScrollX = scrollX;
1070            mOwnScrollY = scrollY;
1071            if (clampedY) {
1072                springBack();
1073            } else {
1074                onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1075                invalidateParentIfNeeded();
1076                updateChildren();
1077                float overScrollTop = getCurrentOverScrollAmount(true);
1078                if (mOwnScrollY < 0) {
1079                    notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
1080                } else {
1081                    notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
1082                }
1083            }
1084        } else {
1085            customScrollTo(scrollY);
1086            scrollTo(scrollX, mScrollY);
1087        }
1088    }
1089
1090    private void springBack() {
1091        int scrollRange = getScrollRange();
1092        boolean overScrolledTop = mOwnScrollY <= 0;
1093        boolean overScrolledBottom = mOwnScrollY >= scrollRange;
1094        if (overScrolledTop || overScrolledBottom) {
1095            boolean onTop;
1096            float newAmount;
1097            if (overScrolledTop) {
1098                onTop = true;
1099                newAmount = -mOwnScrollY;
1100                mOwnScrollY = 0;
1101                mDontReportNextOverScroll = true;
1102            } else {
1103                onTop = false;
1104                newAmount = mOwnScrollY - scrollRange;
1105                mOwnScrollY = scrollRange;
1106            }
1107            setOverScrollAmount(newAmount, onTop, false);
1108            setOverScrollAmount(0.0f, onTop, true);
1109            mScroller.forceFinished(true);
1110        }
1111    }
1112
1113    private int getScrollRange() {
1114        int scrollRange = 0;
1115        ExpandableView firstChild = (ExpandableView) getFirstChildNotGone();
1116        if (firstChild != null) {
1117            int contentHeight = getContentHeight();
1118            int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
1119            scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
1120                    + mBottomStackSlowDownHeight);
1121            if (scrollRange > 0) {
1122                View lastChild = getLastChildNotGone();
1123                // We want to at least be able collapse the first item and not ending in a weird
1124                // end state.
1125                scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight - mCollapsedSize);
1126            }
1127        }
1128        return scrollRange;
1129    }
1130
1131    /**
1132     * @return the first child which has visibility unequal to GONE
1133     */
1134    private View getFirstChildNotGone() {
1135        int childCount = getChildCount();
1136        for (int i = 0; i < childCount; i++) {
1137            View child = getChildAt(i);
1138            if (child.getVisibility() != View.GONE) {
1139                return child;
1140            }
1141        }
1142        return null;
1143    }
1144
1145    /**
1146     * @return The first child which has visibility unequal to GONE which is currently below the
1147     *         given translationY or equal to it.
1148     */
1149    private View getFirstChildBelowTranlsationY(float translationY) {
1150        int childCount = getChildCount();
1151        for (int i = 0; i < childCount; i++) {
1152            View child = getChildAt(i);
1153            if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) {
1154                return child;
1155            }
1156        }
1157        return null;
1158    }
1159
1160    /**
1161     * @return the last child which has visibility unequal to GONE
1162     */
1163    public View getLastChildNotGone() {
1164        int childCount = getChildCount();
1165        for (int i = childCount - 1; i >= 0; i--) {
1166            View child = getChildAt(i);
1167            if (child.getVisibility() != View.GONE) {
1168                return child;
1169            }
1170        }
1171        return null;
1172    }
1173
1174    /**
1175     * @return the number of children which have visibility unequal to GONE
1176     */
1177    public int getNotGoneChildCount() {
1178        int childCount = getChildCount();
1179        int count = 0;
1180        for (int i = 0; i < childCount; i++) {
1181            View child = getChildAt(i);
1182            if (child.getVisibility() != View.GONE) {
1183                count++;
1184            }
1185        }
1186        return count;
1187    }
1188
1189    private int getMaxExpandHeight(View view) {
1190        if (view instanceof ExpandableNotificationRow) {
1191            ExpandableNotificationRow row = (ExpandableNotificationRow) view;
1192            return row.getIntrinsicHeight();
1193        }
1194        return view.getHeight();
1195    }
1196
1197    public int getContentHeight() {
1198        return mContentHeight;
1199    }
1200
1201    private void updateContentHeight() {
1202        int height = 0;
1203        for (int i = 0; i < getChildCount(); i++) {
1204            View child = getChildAt(i);
1205            if (child.getVisibility() != View.GONE) {
1206                if (height != 0) {
1207                    // add the padding before this element
1208                    height += mPaddingBetweenElements;
1209                }
1210                if (child instanceof ExpandableNotificationRow) {
1211                    ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1212                    height += row.getIntrinsicHeight();
1213                } else if (child instanceof ExpandableView) {
1214                    ExpandableView expandableView = (ExpandableView) child;
1215                    height += expandableView.getActualHeight();
1216                }
1217            }
1218        }
1219        mContentHeight = height + mTopPadding;
1220    }
1221
1222    /**
1223     * Fling the scroll view
1224     *
1225     * @param velocityY The initial velocity in the Y direction. Positive
1226     *                  numbers mean that the finger/cursor is moving down the screen,
1227     *                  which means we want to scroll towards the top.
1228     */
1229    private void fling(int velocityY) {
1230        if (getChildCount() > 0) {
1231            int scrollRange = getScrollRange();
1232
1233            float topAmount = getCurrentOverScrollAmount(true);
1234            float bottomAmount = getCurrentOverScrollAmount(false);
1235            if (velocityY < 0 && topAmount > 0) {
1236                mOwnScrollY -= (int) topAmount;
1237                mDontReportNextOverScroll = true;
1238                setOverScrollAmount(0, true, false);
1239                mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
1240                        * mOverflingDistance + topAmount;
1241            } else if (velocityY > 0 && bottomAmount > 0) {
1242                mOwnScrollY += bottomAmount;
1243                setOverScrollAmount(0, false, false);
1244                mMaxOverScroll = Math.abs(velocityY) / 1000f
1245                        * getRubberBandFactor(false /* onTop */) * mOverflingDistance
1246                        +  bottomAmount;
1247            } else {
1248                // it will be set once we reach the boundary
1249                mMaxOverScroll = 0.0f;
1250            }
1251            mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0,
1252                    Math.max(0, scrollRange), 0, Integer.MAX_VALUE / 2);
1253
1254            postInvalidateOnAnimation();
1255        }
1256    }
1257
1258    /**
1259     * @return Whether a fling performed on the top overscroll edge lead to the expanded
1260     * overScroll view (i.e QS).
1261     */
1262    private boolean shouldOverScrollFling(int initialVelocity) {
1263        float topOverScroll = getCurrentOverScrollAmount(true);
1264        return mScrolledToTopOnFirstDown
1265                && !mExpandedInThisMotion
1266                && topOverScroll > mMinTopOverScrollToEscape
1267                && initialVelocity > 0;
1268    }
1269
1270    public void updateTopPadding(float qsHeight, int scrollY, boolean animate) {
1271        float start = qsHeight - scrollY + mNotificationTopPadding;
1272        float stackHeight = getHeight() - start;
1273        int minStackHeight = getMinStackHeight();
1274        if (stackHeight <= minStackHeight) {
1275            float overflow = minStackHeight - stackHeight;
1276            stackHeight = minStackHeight;
1277            start = getHeight() - stackHeight;
1278            setTranslationY(overflow);
1279            mTopPaddingOverflow = overflow;
1280        } else {
1281            setTranslationY(0);
1282            mTopPaddingOverflow = 0;
1283        }
1284        setTopPadding(clampPadding((int) start), animate);
1285    }
1286
1287    public int getNotificationTopPadding() {
1288        return mNotificationTopPadding;
1289    }
1290
1291    public int getMinStackHeight() {
1292        return mCollapsedSize + mBottomStackPeekSize;
1293    }
1294
1295    public float getTopPaddingOverflow() {
1296        return mTopPaddingOverflow;
1297    }
1298
1299    public int getPeekHeight() {
1300        return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize;
1301    }
1302
1303    private int clampPadding(int desiredPadding) {
1304        return Math.max(desiredPadding, mIntrinsicPadding);
1305    }
1306
1307    private float getRubberBandFactor(boolean onTop) {
1308        if (!onTop) {
1309            return RUBBER_BAND_FACTOR_NORMAL;
1310        }
1311        if (mExpandedInThisMotion) {
1312            return RUBBER_BAND_FACTOR_AFTER_EXPAND;
1313        } else if (mIsExpansionChanging) {
1314            return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
1315        } else if (mScrolledToTopOnFirstDown) {
1316            return 1.0f;
1317        }
1318        return RUBBER_BAND_FACTOR_NORMAL;
1319    }
1320
1321    /**
1322     * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
1323     * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
1324     * overscroll view (e.g. expand QS).
1325     */
1326    private boolean isRubberbanded(boolean onTop) {
1327        return !onTop || mExpandedInThisMotion || mIsExpansionChanging
1328                || !mScrolledToTopOnFirstDown;
1329    }
1330
1331    private void endDrag() {
1332        setIsBeingDragged(false);
1333
1334        recycleVelocityTracker();
1335
1336        if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
1337            setOverScrollAmount(0, true /* onTop */, true /* animate */);
1338        }
1339        if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
1340            setOverScrollAmount(0, false /* onTop */, true /* animate */);
1341        }
1342    }
1343
1344    @Override
1345    public boolean onInterceptTouchEvent(MotionEvent ev) {
1346        initDownStates(ev);
1347        boolean expandWantsIt = false;
1348        if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) {
1349            expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
1350        }
1351        boolean scrollWantsIt = false;
1352        if (!mSwipingInProgress && !mExpandingNotification) {
1353            scrollWantsIt = onInterceptTouchEventScroll(ev);
1354        }
1355        boolean swipeWantsIt = false;
1356        if (!mIsBeingDragged
1357                && !mExpandingNotification
1358                && !mExpandedInThisMotion
1359                && !mOnlyScrollingInThisMotion) {
1360            swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
1361        }
1362        return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
1363    }
1364
1365    private void initDownStates(MotionEvent ev) {
1366        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
1367            mExpandedInThisMotion = false;
1368            mOnlyScrollingInThisMotion = !mScroller.isFinished();
1369        }
1370    }
1371
1372    @Override
1373    protected void onViewRemoved(View child) {
1374        super.onViewRemoved(child);
1375        mStackScrollAlgorithm.notifyChildrenChanged(this);
1376        if (mChangePositionInProgress) {
1377            // This is only a position change, don't do anything special
1378            return;
1379        }
1380        ((ExpandableView) child).setOnHeightChangedListener(null);
1381        mCurrentStackScrollState.removeViewStateForView(child);
1382        updateScrollStateForRemovedChild(child);
1383        boolean animationGenerated = generateRemoveAnimation(child);
1384        if (animationGenerated && !mSwipedOutViews.contains(child)) {
1385            // Add this view to an overlay in order to ensure that it will still be temporary
1386            // drawn when removed
1387            getOverlay().add(child);
1388        }
1389    }
1390
1391    /**
1392     * Generate a remove animation for a child view.
1393     *
1394     * @param child The view to generate the remove animation for.
1395     * @return Whether an animation was generated.
1396     */
1397    private boolean generateRemoveAnimation(View child) {
1398        if (mIsExpanded && mAnimationsEnabled) {
1399            if (!mChildrenToAddAnimated.contains(child)) {
1400                // Generate Animations
1401                mChildrenToRemoveAnimated.add(child);
1402                mNeedsAnimation = true;
1403                return true;
1404            } else {
1405                mChildrenToAddAnimated.remove(child);
1406                return false;
1407            }
1408        }
1409        return false;
1410    }
1411
1412    /**
1413     * Updates the scroll position when a child was removed
1414     *
1415     * @param removedChild the removed child
1416     */
1417    private void updateScrollStateForRemovedChild(View removedChild) {
1418        int startingPosition = getPositionInLinearLayout(removedChild);
1419        int childHeight = removedChild.getHeight() + mPaddingBetweenElements;
1420        int endPosition = startingPosition + childHeight;
1421        if (endPosition <= mOwnScrollY) {
1422            // This child is fully scrolled of the top, so we have to deduct its height from the
1423            // scrollPosition
1424            mOwnScrollY -= childHeight;
1425        } else if (startingPosition < mOwnScrollY) {
1426            // This child is currently being scrolled into, set the scroll position to the start of
1427            // this child
1428            mOwnScrollY = startingPosition;
1429        }
1430    }
1431
1432    private int getPositionInLinearLayout(View requestedChild) {
1433        int position = 0;
1434        for (int i = 0; i < getChildCount(); i++) {
1435            View child = getChildAt(i);
1436            if (child == requestedChild) {
1437                return position;
1438            }
1439            if (child.getVisibility() != View.GONE) {
1440                position += child.getHeight();
1441                if (i < getChildCount()-1) {
1442                    position += mPaddingBetweenElements;
1443                }
1444            }
1445        }
1446        return 0;
1447    }
1448
1449    @Override
1450    protected void onViewAdded(View child) {
1451        super.onViewAdded(child);
1452        mStackScrollAlgorithm.notifyChildrenChanged(this);
1453        ((ExpandableView) child).setOnHeightChangedListener(this);
1454        generateAddAnimation(child);
1455    }
1456
1457    public void setAnimationsEnabled(boolean animationsEnabled) {
1458        mAnimationsEnabled = animationsEnabled;
1459    }
1460
1461    public boolean isAddOrRemoveAnimationPending() {
1462        return mNeedsAnimation
1463                && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
1464    }
1465    /**
1466     * Generate an animation for an added child view.
1467     *
1468     * @param child The view to be added.
1469     */
1470    public void generateAddAnimation(View child) {
1471        if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
1472            // Generate Animations
1473            mChildrenToAddAnimated.add(child);
1474            mNeedsAnimation = true;
1475        }
1476    }
1477
1478    /**
1479     * Change the position of child to a new location
1480     *
1481     * @param child the view to change the position for
1482     * @param newIndex the new index
1483     */
1484    public void changeViewPosition(View child, int newIndex) {
1485        if (child != null && child.getParent() == this) {
1486            mChangePositionInProgress = true;
1487            removeView(child);
1488            addView(child, newIndex);
1489            mChangePositionInProgress = false;
1490            if (mIsExpanded && mAnimationsEnabled) {
1491                mChildrenChangingPositions.add(child);
1492                mNeedsAnimation = true;
1493            }
1494        }
1495    }
1496
1497    private void startAnimationToState() {
1498        if (mNeedsAnimation) {
1499            generateChildHierarchyEvents();
1500            mNeedsAnimation = false;
1501        }
1502        if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
1503            mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState);
1504            mAnimationEvents.clear();
1505        } else {
1506            applyCurrentState();
1507        }
1508    }
1509
1510    private void generateChildHierarchyEvents() {
1511        generateChildRemovalEvents();
1512        generateChildAdditionEvents();
1513        generatePositionChangeEvents();
1514        generateSnapBackEvents();
1515        generateDragEvents();
1516        generateTopPaddingEvent();
1517        generateActivateEvent();
1518        generateDimmedEvent();
1519        generateDarkEvent();
1520        mNeedsAnimation = false;
1521    }
1522
1523    private void generateSnapBackEvents() {
1524        for (View child : mSnappedBackChildren) {
1525            mAnimationEvents.add(new AnimationEvent(child,
1526                    AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
1527        }
1528        mSnappedBackChildren.clear();
1529    }
1530
1531    private void generateDragEvents() {
1532        for (View child : mDragAnimPendingChildren) {
1533            mAnimationEvents.add(new AnimationEvent(child,
1534                    AnimationEvent.ANIMATION_TYPE_START_DRAG));
1535        }
1536        mDragAnimPendingChildren.clear();
1537    }
1538
1539    private void generateChildRemovalEvents() {
1540        for (View child : mChildrenToRemoveAnimated) {
1541            boolean childWasSwipedOut = mSwipedOutViews.contains(child);
1542            int animationType = childWasSwipedOut
1543                    ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
1544                    : AnimationEvent.ANIMATION_TYPE_REMOVE;
1545            AnimationEvent event = new AnimationEvent(child, animationType);
1546
1547            // we need to know the view after this one
1548            event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY());
1549            mAnimationEvents.add(event);
1550        }
1551        mSwipedOutViews.clear();
1552        mChildrenToRemoveAnimated.clear();
1553    }
1554
1555    private void generatePositionChangeEvents() {
1556        for (View child : mChildrenChangingPositions) {
1557            mAnimationEvents.add(new AnimationEvent(child,
1558                    AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
1559        }
1560        mChildrenChangingPositions.clear();
1561    }
1562
1563    private void generateChildAdditionEvents() {
1564        for (View child : mChildrenToAddAnimated) {
1565            mAnimationEvents.add(new AnimationEvent(child,
1566                    AnimationEvent.ANIMATION_TYPE_ADD));
1567        }
1568        mChildrenToAddAnimated.clear();
1569    }
1570
1571    private void generateTopPaddingEvent() {
1572        if (mTopPaddingNeedsAnimation) {
1573            mAnimationEvents.add(
1574                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED));
1575        }
1576        mTopPaddingNeedsAnimation = false;
1577    }
1578
1579    private void generateActivateEvent() {
1580        if (mActivateNeedsAnimation) {
1581            mAnimationEvents.add(
1582                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
1583        }
1584        mActivateNeedsAnimation = false;
1585    }
1586
1587    private void generateDimmedEvent() {
1588        if (mDimmedNeedsAnimation) {
1589            mAnimationEvents.add(
1590                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
1591        }
1592        mDimmedNeedsAnimation = false;
1593    }
1594
1595    private void generateDarkEvent() {
1596        if (mDarkNeedsAnimation) {
1597            mAnimationEvents.add(
1598                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK));
1599        }
1600        mDarkNeedsAnimation = false;
1601    }
1602
1603    private boolean onInterceptTouchEventScroll(MotionEvent ev) {
1604        if (!isScrollingEnabled()) {
1605            return false;
1606        }
1607        /*
1608         * This method JUST determines whether we want to intercept the motion.
1609         * If we return true, onMotionEvent will be called and we do the actual
1610         * scrolling there.
1611         */
1612
1613        /*
1614        * Shortcut the most recurring case: the user is in the dragging
1615        * state and he is moving his finger.  We want to intercept this
1616        * motion.
1617        */
1618        final int action = ev.getAction();
1619        if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
1620            return true;
1621        }
1622
1623        switch (action & MotionEvent.ACTION_MASK) {
1624            case MotionEvent.ACTION_MOVE: {
1625                /*
1626                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1627                 * whether the user has moved far enough from his original down touch.
1628                 */
1629
1630                /*
1631                * Locally do absolute value. mLastMotionY is set to the y value
1632                * of the down event.
1633                */
1634                final int activePointerId = mActivePointerId;
1635                if (activePointerId == INVALID_POINTER) {
1636                    // If we don't have a valid id, the touch down wasn't on content.
1637                    break;
1638                }
1639
1640                final int pointerIndex = ev.findPointerIndex(activePointerId);
1641                if (pointerIndex == -1) {
1642                    Log.e(TAG, "Invalid pointerId=" + activePointerId
1643                            + " in onInterceptTouchEvent");
1644                    break;
1645                }
1646
1647                final int y = (int) ev.getY(pointerIndex);
1648                final int x = (int) ev.getX(pointerIndex);
1649                final int yDiff = Math.abs(y - mLastMotionY);
1650                final int xDiff = Math.abs(x - mDownX);
1651                if (yDiff > mTouchSlop && yDiff > xDiff) {
1652                    setIsBeingDragged(true);
1653                    mLastMotionY = y;
1654                    mDownX = x;
1655                    initVelocityTrackerIfNotExists();
1656                    mVelocityTracker.addMovement(ev);
1657                }
1658                break;
1659            }
1660
1661            case MotionEvent.ACTION_DOWN: {
1662                final int y = (int) ev.getY();
1663                if (getChildAtPosition(ev.getX(), y) == null) {
1664                    setIsBeingDragged(false);
1665                    recycleVelocityTracker();
1666                    break;
1667                }
1668
1669                /*
1670                 * Remember location of down touch.
1671                 * ACTION_DOWN always refers to pointer index 0.
1672                 */
1673                mLastMotionY = y;
1674                mDownX = (int) ev.getX();
1675                mActivePointerId = ev.getPointerId(0);
1676                mScrolledToTopOnFirstDown = isScrolledToTop();
1677
1678                initOrResetVelocityTracker();
1679                mVelocityTracker.addMovement(ev);
1680                /*
1681                * If being flinged and user touches the screen, initiate drag;
1682                * otherwise don't.  mScroller.isFinished should be false when
1683                * being flinged.
1684                */
1685                boolean isBeingDragged = !mScroller.isFinished();
1686                setIsBeingDragged(isBeingDragged);
1687                break;
1688            }
1689
1690            case MotionEvent.ACTION_CANCEL:
1691            case MotionEvent.ACTION_UP:
1692                /* Release the drag */
1693                setIsBeingDragged(false);
1694                mActivePointerId = INVALID_POINTER;
1695                recycleVelocityTracker();
1696                if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
1697                    postInvalidateOnAnimation();
1698                }
1699                break;
1700            case MotionEvent.ACTION_POINTER_UP:
1701                onSecondaryPointerUp(ev);
1702                break;
1703        }
1704
1705        /*
1706        * The only time we want to intercept motion events is if we are in the
1707        * drag mode.
1708        */
1709        return mIsBeingDragged;
1710    }
1711
1712    /**
1713     * @return Whether the specified motion event is actually happening over the content.
1714     */
1715    private boolean isInContentBounds(MotionEvent event) {
1716        return event.getY() < getHeight() - getEmptyBottomMargin();
1717    }
1718
1719    private void setIsBeingDragged(boolean isDragged) {
1720        mIsBeingDragged = isDragged;
1721        if (isDragged) {
1722            requestDisallowInterceptTouchEvent(true);
1723            removeLongPressCallback();
1724        }
1725    }
1726
1727    @Override
1728    public void onWindowFocusChanged(boolean hasWindowFocus) {
1729        super.onWindowFocusChanged(hasWindowFocus);
1730        if (!hasWindowFocus) {
1731            removeLongPressCallback();
1732        }
1733    }
1734
1735    public void removeLongPressCallback() {
1736        mSwipeHelper.removeLongPressCallback();
1737    }
1738
1739    @Override
1740    public boolean isScrolledToTop() {
1741        return mOwnScrollY == 0;
1742    }
1743
1744    @Override
1745    public boolean isScrolledToBottom() {
1746        return mOwnScrollY >= getScrollRange();
1747    }
1748
1749    @Override
1750    public View getHostView() {
1751        return this;
1752    }
1753
1754    public int getEmptyBottomMargin() {
1755        int emptyMargin = mMaxLayoutHeight - mContentHeight;
1756        if (needsHeightAdaption()) {
1757            emptyMargin = emptyMargin - mBottomStackSlowDownHeight - mBottomStackPeekSize;
1758        } else {
1759            emptyMargin = emptyMargin - mBottomStackPeekSize;
1760        }
1761        return Math.max(emptyMargin, 0);
1762    }
1763
1764    public void onExpansionStarted() {
1765        mIsExpansionChanging = true;
1766        mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
1767    }
1768
1769    public void onExpansionStopped() {
1770        mIsExpansionChanging = false;
1771        mStackScrollAlgorithm.onExpansionStopped();
1772    }
1773
1774    private void setIsExpanded(boolean isExpanded) {
1775        mIsExpanded = isExpanded;
1776        mStackScrollAlgorithm.setIsExpanded(isExpanded);
1777        if (!isExpanded) {
1778            mOwnScrollY = 0;
1779        }
1780    }
1781
1782    @Override
1783    public void onHeightChanged(ExpandableView view) {
1784        updateContentHeight();
1785        updateScrollPositionIfNecessary();
1786        notifyHeightChangeListener(view);
1787        requestChildrenUpdate();
1788    }
1789
1790    public void setOnHeightChangedListener(
1791            ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
1792        this.mOnHeightChangedListener = mOnHeightChangedListener;
1793    }
1794
1795    public void onChildAnimationFinished() {
1796        requestChildrenUpdate();
1797    }
1798
1799    /**
1800     * See {@link AmbientState#setDimmed}.
1801     */
1802    public void setDimmed(boolean dimmed, boolean animate) {
1803        mStackScrollAlgorithm.setDimmed(dimmed);
1804        mAmbientState.setDimmed(dimmed);
1805        updatePadding(dimmed);
1806        if (animate && mAnimationsEnabled) {
1807            mDimmedNeedsAnimation = true;
1808            mNeedsAnimation =  true;
1809        }
1810        requestChildrenUpdate();
1811    }
1812
1813    /**
1814     * See {@link AmbientState#setActivatedChild}.
1815     */
1816    public void setActivatedChild(ActivatableNotificationView activatedChild) {
1817        mAmbientState.setActivatedChild(activatedChild);
1818        if (mAnimationsEnabled) {
1819            mActivateNeedsAnimation = true;
1820            mNeedsAnimation =  true;
1821        }
1822        requestChildrenUpdate();
1823    }
1824
1825    public ActivatableNotificationView getActivatedChild() {
1826        return mAmbientState.getActivatedChild();
1827    }
1828
1829    private void applyCurrentState() {
1830        mCurrentStackScrollState.apply();
1831        if (mListener != null) {
1832            mListener.onChildLocationsChanged(this);
1833        }
1834    }
1835
1836    public void setSpeedBumpView(SpeedBumpView speedBumpView) {
1837        mSpeedBumpView = speedBumpView;
1838        addView(speedBumpView);
1839    }
1840
1841    private void updateSpeedBump(boolean visible) {
1842        boolean notGoneBefore = mSpeedBumpView.getVisibility() != GONE;
1843        if (visible != notGoneBefore) {
1844            int newVisibility = visible ? VISIBLE : GONE;
1845            mSpeedBumpView.setVisibility(newVisibility);
1846            if (visible) {
1847                // Make invisible to ensure that the appear animation is played.
1848                mSpeedBumpView.setInvisible();
1849                if (!mIsExpansionChanging) {
1850                    generateAddAnimation(mSpeedBumpView);
1851                }
1852            } else {
1853                mSpeedBumpView.performVisibilityAnimation(false);
1854                generateRemoveAnimation(mSpeedBumpView);
1855            }
1856        }
1857    }
1858
1859    public void goToFullShade() {
1860        updateSpeedBump(true);
1861    }
1862
1863    public void cancelExpandHelper() {
1864        mExpandHelper.cancel();
1865    }
1866
1867    public void setIntrinsicPadding(int intrinsicPadding) {
1868        mIntrinsicPadding = intrinsicPadding;
1869    }
1870
1871    public int getIntrinsicPadding() {
1872        return mIntrinsicPadding;
1873    }
1874
1875    /**
1876     * @return the y position of the first notification
1877     */
1878    public float getNotificationsTopY() {
1879        return mTopPadding + getTranslationY();
1880    }
1881
1882    public void setTouchEnabled(boolean touchEnabled) {
1883        mTouchEnabled = touchEnabled;
1884    }
1885
1886    @Override
1887    public boolean dispatchTouchEvent(MotionEvent ev) {
1888        if (!mTouchEnabled) {
1889            return false;
1890        }
1891        return super.dispatchTouchEvent(ev);
1892    }
1893
1894    @Override
1895    public boolean shouldDelayChildPressedState() {
1896        return true;
1897    }
1898
1899    public void setScrimAlpha(float progress) {
1900        mAmbientState.setScrimAmount(progress);
1901        requestChildrenUpdate();
1902    }
1903
1904    /**
1905     * See {@link AmbientState#setDark}.
1906     */
1907    public void setDark(boolean dark, boolean animate) {
1908        mAmbientState.setDark(dark);
1909        if (animate && mAnimationsEnabled) {
1910            mDarkNeedsAnimation = true;
1911            mNeedsAnimation =  true;
1912        }
1913        requestChildrenUpdate();
1914    }
1915
1916    /**
1917     * A listener that is notified when some child locations might have changed.
1918     */
1919    public interface OnChildLocationsChangedListener {
1920        public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
1921    }
1922
1923    /**
1924     * A listener that gets notified when the overscroll at the top has changed.
1925     */
1926    public interface OnOverscrollTopChangedListener {
1927
1928        /**
1929         * Notifies a listener that the overscroll has changed.
1930         *
1931         * @param amount the amount of overscroll, in pixels
1932         * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
1933         *                     unrubberbanded motion to directly expand overscroll view (e.g expand
1934         *                     QS)
1935         */
1936        public void onOverscrollTopChanged(float amount, boolean isRubberbanded);
1937
1938        /**
1939         * Notify a listener that the scroller wants to escape from the scrolling motion and
1940         * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
1941         *
1942         * @param velocity The velocity that the Scroller had when over flinging
1943         * @param open Should the fling open or close the overscroll view.
1944         */
1945        public void flingTopOverscroll(float velocity, boolean open);
1946    }
1947
1948    static class AnimationEvent {
1949
1950        static AnimationFilter[] FILTERS = new AnimationFilter[] {
1951
1952                // ANIMATION_TYPE_ADD
1953                new AnimationFilter()
1954                        .animateAlpha()
1955                        .animateHeight()
1956                        .animateTopInset()
1957                        .animateY()
1958                        .animateZ()
1959                        .hasDelays(),
1960
1961                // ANIMATION_TYPE_REMOVE
1962                new AnimationFilter()
1963                        .animateAlpha()
1964                        .animateHeight()
1965                        .animateTopInset()
1966                        .animateY()
1967                        .animateZ()
1968                        .hasDelays(),
1969
1970                // ANIMATION_TYPE_REMOVE_SWIPED_OUT
1971                new AnimationFilter()
1972                        .animateAlpha()
1973                        .animateHeight()
1974                        .animateTopInset()
1975                        .animateY()
1976                        .animateZ()
1977                        .hasDelays(),
1978
1979                // ANIMATION_TYPE_TOP_PADDING_CHANGED
1980                new AnimationFilter()
1981                        .animateAlpha()
1982                        .animateHeight()
1983                        .animateTopInset()
1984                        .animateY()
1985                        .animateDimmed()
1986                        .animateScale()
1987                        .animateZ(),
1988
1989                // ANIMATION_TYPE_START_DRAG
1990                new AnimationFilter()
1991                        .animateAlpha(),
1992
1993                // ANIMATION_TYPE_SNAP_BACK
1994                new AnimationFilter()
1995                        .animateAlpha(),
1996
1997                // ANIMATION_TYPE_ACTIVATED_CHILD
1998                new AnimationFilter()
1999                        .animateScale()
2000                        .animateAlpha(),
2001
2002                // ANIMATION_TYPE_DIMMED
2003                new AnimationFilter()
2004                        .animateY()
2005                        .animateScale()
2006                        .animateDimmed(),
2007
2008                // ANIMATION_TYPE_CHANGE_POSITION
2009                new AnimationFilter()
2010                        .animateAlpha()
2011                        .animateHeight()
2012                        .animateTopInset()
2013                        .animateY()
2014                        .animateZ(),
2015
2016                // ANIMATION_TYPE_DARK
2017                new AnimationFilter()
2018                        .animateDark(),
2019        };
2020
2021        static int[] LENGTHS = new int[] {
2022
2023                // ANIMATION_TYPE_ADD
2024                StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
2025
2026                // ANIMATION_TYPE_REMOVE
2027                StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
2028
2029                // ANIMATION_TYPE_REMOVE_SWIPED_OUT
2030                StackStateAnimator.ANIMATION_DURATION_STANDARD,
2031
2032                // ANIMATION_TYPE_TOP_PADDING_CHANGED
2033                StackStateAnimator.ANIMATION_DURATION_STANDARD,
2034
2035                // ANIMATION_TYPE_START_DRAG
2036                StackStateAnimator.ANIMATION_DURATION_STANDARD,
2037
2038                // ANIMATION_TYPE_SNAP_BACK
2039                StackStateAnimator.ANIMATION_DURATION_STANDARD,
2040
2041                // ANIMATION_TYPE_ACTIVATED_CHILD
2042                StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
2043
2044                // ANIMATION_TYPE_DIMMED
2045                StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
2046
2047                // ANIMATION_TYPE_CHANGE_POSITION
2048                StackStateAnimator.ANIMATION_DURATION_STANDARD,
2049
2050                // ANIMATION_TYPE_DARK
2051                StackStateAnimator.ANIMATION_DURATION_STANDARD,
2052        };
2053
2054        static final int ANIMATION_TYPE_ADD = 0;
2055        static final int ANIMATION_TYPE_REMOVE = 1;
2056        static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
2057        static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
2058        static final int ANIMATION_TYPE_START_DRAG = 4;
2059        static final int ANIMATION_TYPE_SNAP_BACK = 5;
2060        static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
2061        static final int ANIMATION_TYPE_DIMMED = 7;
2062        static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
2063        static final int ANIMATION_TYPE_DARK = 9;
2064
2065        final long eventStartTime;
2066        final View changingView;
2067        final int animationType;
2068        final AnimationFilter filter;
2069        final long length;
2070        View viewAfterChangingView;
2071
2072        AnimationEvent(View view, int type) {
2073            eventStartTime = AnimationUtils.currentAnimationTimeMillis();
2074            changingView = view;
2075            animationType = type;
2076            filter = FILTERS[type];
2077            length = LENGTHS[type];
2078        }
2079
2080        /**
2081         * Combines the length of several animation events into a single value.
2082         *
2083         * @param events The events of the lengths to combine.
2084         * @return The combined length. This is just the maximum of all length.
2085         */
2086        static long combineLength(ArrayList<AnimationEvent> events) {
2087            long length = 0;
2088            int size = events.size();
2089            for (int i = 0; i < size; i++) {
2090                length = Math.max(length, events.get(i).length);
2091            }
2092            return length;
2093        }
2094    }
2095
2096}
2097