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