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