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